aboutsummaryrefslogtreecommitdiffstats
path: root/cmake/QtIRParsingHelpers.cmake
blob: d7d3f20e9d3363cc4cba290e20a9ba86a8d0b1e6 (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
236
237
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause

# Retrieves the contents of either .git/config or .gitmodules files for further parsing.
function(qt_ir_get_git_config_contents out_var)
    set(options
        READ_GITMODULES
        READ_GIT_CONFIG
        READ_GIT_CONFIG_LOCAL
    )
    set(oneValueArgs
        WORKING_DIRECTORY
    )
    set(multiValueArgs "")
    cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    if(arg_READ_GITMODULES)
        set(args -f .gitmodules)
        set(file_message ".gitmodules")
    elseif(arg_READ_GIT_CONFIG)
        set(args "")
        set(file_message ".git/config")
    elseif(arg_READ_GIT_CONFIG_LOCAL)
        set(args "--local")
        set(file_message ".local .git/config")
    else()
        message(FATAL_ERROR "qt_ir_get_git_config_contents: No option specified")
    endif()

    qt_ir_get_working_directory_from_arg(working_directory)

    qt_ir_execute_process_and_log_and_handle_error(
        FORCE_QUIET
        COMMAND_ARGS git config --list ${args}
        OUT_OUTPUT_VAR git_output
        WORKING_DIRECTORY "${working_directory}"
        ERROR_MESSAGE "Failed to get ${file_message} contents for parsing")

    string(STRIP "${git_output}" git_output)
    set(${out_var} "${git_output}" PARENT_SCOPE)
endfunction()

# Checks whether the given url has a scheme like https:// or is just a
# relative path.
function(qt_ir_has_url_scheme url out_var)
    string(REGEX MATCH "^[a-z][a-z0-9+\-.]*://" has_url_scheme "${url}")

    if(has_url_scheme)
        set(${out_var} TRUE PARENT_SCOPE)
    else()
        set(${out_var} FALSE PARENT_SCOPE)
    endif()
endfunction()

# Parses a key-value line from a .git/config or .gitmodules file
macro(qt_ir_parse_git_key_value)
    string(REGEX REPLACE "^submodule\\.([^.=]+)\\.([^.=]+)=(.*)$" "\\1;\\2;\\3"
        parsed_line "${line}")

    list(LENGTH parsed_line parsed_line_length)
    set(submodule_name "")
    set(key "")
    set(value "")
    if(parsed_line_length EQUAL 3)
        list(GET parsed_line 0 submodule_name)
        list(GET parsed_line 1 key)
        list(GET parsed_line 2 value)
    endif()
endmacro()

# Parses a url line from a .gitmodules file
#   e.g. line - 'submodule.qtbase.url=../qtbase.git'
#
# Arguments
#
# submodule_name
#   submodule name, the key in 'submodule.${submodule_name}.url'
#   e.g. 'qtbase'
# url_value
#   the url where to clone a repo from
#   in perl script it was called $base
#   e.g. '../qtbase.git', 'https://code.qt.io/playground/qlitehtml.git'
# parent_repo_base_git_path
#   the base git path of the parent of the submodule
#   it is either a relative dir or a full url
#   in the perl script it was called $my_repo_base,
#   it was passed as first arg to git_clone_all_submodules,
#   it was passed the value of $subbases{$module} when doing recursive submodule cloning
#   e.g. 'qt5', 'tqtc-qt5', 'qtdeclarative.git', 'https://code.qt.io/playground/qlitehtml.git'
#
# Outputs
#
# ${out_var_prefix}_${submodule_name}_url
#   just the value of ${url_value}
# ${out_var_prefix}_${submodule_name}_base_git_path
#   the whole url if it has a scheme, otherwise it's the value of
#   ${url_value} relative to ${parent_repo_base_git_path}, so all the ../ are collapsed
#   e.g. 'qtdeclarative.git'
#        'https://code.qt.io/playground/qlitehtml.git',
macro(qt_ir_parse_git_url_key out_var_prefix submodule_name url_value parent_repo_base_git_path)
    qt_ir_has_url_scheme("${url_value}" has_url_scheme)
    if(NOT has_url_scheme)
        set(base_git_path "${parent_repo_base_git_path}/${url_value}")

        # The exact code perl code was while ($base =~ s,(?!\.\./)[^/]+/\.\./,,g) {}
        # That got rid of ../ and ../../ in the path, but it broke down
        # when more than two ../ were present.
        # We just use ABSOLUTE to resolve the path and get rid of all ../
        # Note the empty BASE_DIR is important, otherwise the path is relative to
        # ${CMAKE_CURRENT_SOURCE_DIR}.
        get_filename_component(base_git_path "${base_git_path}" ABSOLUTE BASE_DIR "")
    else()
        set(base_git_path "${url_value}")
    endif()

    set(${out_var_prefix}_${submodule_name}_url "${url_value}" PARENT_SCOPE)
    set(${out_var_prefix}_${submodule_name}_base_git_path "${base_git_path}" PARENT_SCOPE)
endmacro()

# Parses a .git/config or .gitmodules file contents and sets variables for each submodule
# starting with ${out_var_prefix}_
# These include:
#   ${out_var_prefix}_${submodule_name}_path
#     the path to the submodule relative to the parent repo
#   ${out_var_prefix}_${submodule_name}_branch
#     the branch that should be checked out when the branch option is used
#   ${out_var_prefix}_${submodule_name}_url
#     the url key as encountered in the config
#   ${out_var_prefix}_${submodule_name}_base_git_path
#     the git base path of the submodule, either a full url or a relative path
#   ${out_var_prefix}_${submodule_name}_update
#     the status of the submodule, can be 'none'
#   ${out_var_prefix}_${submodule_name}_status
#     the status of the submodule, can be 'essential', 'addon', etc
#   ${out_var_prefix}_${submodule_name}_depends
#     the list of submodules that this submodule depends on
#   ${out_var_prefix}_${submodule_name}_recommends
#     the list of submodules that this submodule recommends to be used with
#   ${out_var_prefix}_submodules
#     a list of all known submodule names encountered in the file
#   ${out_var_prefix}_submodules_to_remove
#     a list of all submodules to remove due to update == 'none'
#   ${out_var_prefix}_statuses to
#     a list of all known submodule statuses like 'essential', 'addon', etc
#   ${out_var_prefix}_status_${status}_submodules
#     a list of all submodules with the specific status
function(qt_ir_parse_git_config_file_contents out_var_prefix)
    set(options
        READ_GITMODULES
        READ_GIT_CONFIG
        READ_GIT_CONFIG_LOCAL
    )
    set(oneValueArgs
        PARENT_REPO_BASE_GIT_PATH
        WORKING_DIRECTORY
    )
    set(multiValueArgs "")
    cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    qt_ir_get_working_directory_from_arg(working_directory)

    if(NOT arg_PARENT_REPO_BASE_GIT_PATH)
        message(FATAL_ERROR
            "qt_ir_parse_git_config_file_contents: No base PARENT_REPO_BASE_GIT_PATH specified")
    endif()
    set(parent_repo_base_git_path "${arg_PARENT_REPO_BASE_GIT_PATH}")

    if(arg_READ_GITMODULES)
        set(read_git_config READ_GITMODULES)
    elseif(arg_READ_GIT_CONFIG)
        set(read_git_config READ_GIT_CONFIG)
    elseif(arg_READ_GIT_CONFIG_LOCAL)
        set(read_git_config READ_GIT_CONFIG_LOCAL)
    else()
        message(FATAL_ERROR
            "qt_ir_parse_gitmodules_file_contents: No valid git config file specified")
    endif()

    qt_ir_get_git_config_contents(contents
        ${read_git_config}
        WORKING_DIRECTORY "${working_directory}"
    )
    string(REPLACE "\n" ";" lines "${contents}")

    set(known_submodules "")
    set(statuses "")
    set(submodules_to_remove "")

    foreach(line IN LISTS lines)
        qt_ir_parse_git_key_value()
        if(NOT submodule_name OR NOT key OR value STREQUAL "")
            continue()
        endif()

        list(APPEND known_submodules "${submodule_name}")

        if(key STREQUAL "path")
            set(${out_var_prefix}_${submodule_name}_path "${value}" PARENT_SCOPE)
        elseif(key STREQUAL "branch")
            set(${out_var_prefix}_${submodule_name}_branch "${value}" PARENT_SCOPE)
        elseif(key STREQUAL "url")
            qt_ir_parse_git_url_key(
                "${out_var_prefix}" "${submodule_name}" "${value}" "${parent_repo_base_git_path}")
        elseif(key STREQUAL "update")
            # Some repo submodules had a update = none key in their .gitmodules
            # in which case we're supposed to skip initialzing those submodules,
            # which the perl script did by adding -${submodule_name} to the subset.
            # See qtdeclarative Change-Id: I633404f1c00d83dcbdca06a1d287623190323028
            set(${out_var_prefix}_${submodule_name}_update "${value}" PARENT_SCOPE)
            if(value STREQUAL "none")
                list(APPEND submodules_to_remove "-${submodule_name}")
            endif()
        elseif(key STREQUAL "status")
            set(status_submodules "${${out_var_prefix}_status_${value}_submodules}")
            list(APPEND status_submodules "${submodule_name}")
            list(REMOVE_DUPLICATES status_submodules)
            list(APPEND statuses "${value}")

            set(${out_var_prefix}_status_${value}_submodules "${status_submodules}")
            set(${out_var_prefix}_status_${value}_submodules "${status_submodules}" PARENT_SCOPE)
            set(${out_var_prefix}_${submodule_name}_status "${value}" PARENT_SCOPE)
        elseif(key STREQUAL "depends")
            string(REPLACE " " ";" value "${value}")
            set(${out_var_prefix}_${submodule_name}_depends "${value}" PARENT_SCOPE)
        elseif(key STREQUAL "recommends")
            string(REPLACE " " ";" value "${value}")
            set(${out_var_prefix}_${submodule_name}_recommends "${value}" PARENT_SCOPE)
        endif()
    endforeach()

    list(REMOVE_DUPLICATES known_submodules)
    list(REMOVE_DUPLICATES submodules_to_remove)
    list(REMOVE_DUPLICATES statuses)
    set(${out_var_prefix}_submodules "${known_submodules}" PARENT_SCOPE)
    set(${out_var_prefix}_submodules_to_remove "${submodules_to_remove}" PARENT_SCOPE)
    set(${out_var_prefix}_statuses "${statuses}" PARENT_SCOPE)
endfunction()