aboutsummaryrefslogtreecommitdiffstats
path: root/cmake/QtIRHelpers.cmake
blob: 9f37293287dfc4844b9ace7b0359f02dab267487 (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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause

# Includes all helper files for access to necessary functions.
macro(qt_ir_include_all_helpers)
    include(QtIRCommandLineHelpers)
    include(QtIRGitHelpers)
    include(QtIROptionsHelpers)
    include(QtIRParsingHelpers)
    include(QtIRProcessHelpers)
    include(QtIRTestHelpers)
    include(QtTopLevelHelpers)
endmacro()

# Convenience macro to get the working directory from the arguments passed to
# cmake_parse_arguments. Saves a few lines and makes reading the code slightly
# easier.
macro(qt_ir_get_working_directory_from_arg out_var)
    if(NOT arg_WORKING_DIRECTORY)
        message(FATAL_ERROR "No working directory specified")
    endif()
    set(${out_var} "${arg_WORKING_DIRECTORY}")
endmacro()

# Convenience function to set the variable to the name of cmake_parse_arguments
# flag option if it is active.
function(qt_ir_get_cmake_flag flag_name out_var)
    if(arg_${flag_name})
        set(${out_var} "${flag_name}" PARENT_SCOPE)
    else()
        set(${out_var} "" PARENT_SCOPE)
    endif()
endfunction()

# There are some init-repository options that we do not want to allow when called from
# configure. Make sure we error out when they are set by the user.
function(qt_ir_validate_options_for_configure)
    set(disallowed_options
        # Disallow mirror options, because users should set up a proper git mirror manually,
        # not via configure.
        mirror
        oslo
        berlin
    )
    foreach(disallowed_option IN LISTS disallowed_options)
        qt_ir_get_option_value(${disallowed_option} value)
        if(value)
            set(msg
                "Initialization option '${disallowed_option}' is not supported by configure. "
                "If you think this option should be supported, please let us know at "
                "https://bugreports.qt.io/"
            )
            message(FATAL_ERROR ${msg})
        endif()
    endforeach()
endfunction()

# Handle the case when init-repository is called from the configure script.
function(qt_ir_handle_called_from_configure top_level_src_path out_var_exit_reason)
    # Nothing special to do if we're not called from configure.
    qt_ir_is_called_from_configure(is_called_from_configure)
    if(NOT is_called_from_configure)
        set(${out_var_exit_reason} FALSE PARENT_SCOPE)
        return()
    endif()

    # Check whether qtbase was cloned, if not, tell the user how to initialize
    # the repos as part of the configure script.
    qt_ir_get_option_value(init-submodules init_submodules)
    set(configure_script "${top_level_src_path}/qtbase/configure")
    if(NOT EXISTS "${configure_script}" AND NOT init_submodules)
        set(msg "Oops. It looks like you didn't initialize any submodules yet.\nCall configure "
            "with the -init-submodules option to automatically clone a default set of "
            "submodules before configuring Qt.\nYou can also pass "
            "-submodules submodule2,submodule3 to clone a particular set of submodules "
            "and their dependencies. See ./init-repository --help for more information on values "
            "accepted by --module-subset (which gets its values from -submodules).")
        message(${msg})
        set(${out_var_exit_reason} NEED_INIT_SUBMODULES PARENT_SCOPE)
        return()
    endif()

    # Don't do init-repository things when called from configure, qtbase exists and the
    # -init-submodules option is not passed. We assume the repo was already
    # initialized.
    if(NOT init_submodules)
        set(${out_var_exit_reason} ALREADY_INITIALIZED PARENT_SCOPE)
        return()
    endif()

    qt_ir_validate_options_for_configure()

    # -init_submodules implies --force
    qt_ir_set_option_value(force TRUE)

    set(${out_var_exit_reason} FALSE PARENT_SCOPE)
endfunction()

# Returns a list of command line arguments with the init-repository specific
# options removed, which are not recognized by configure.
# It also handles -submodules values like 'essential', 'existing' and '-qtsvg' and transforms them
# into the final list of submodules to be included and excluded, which are then translated
# to configure -submodules and -skip options.
function(qt_ir_get_args_from_optfile_configure_filtered optfile_path out_var)
    cmake_parse_arguments(arg "ALREADY_INITIALIZED" "" "" ${ARGV})

    # Get args unknown to init-repository, and pass them to configure as-is.
    qt_ir_get_unknown_args(unknown_args)

    set(filtered_args ${unknown_args})
    set(extra_configure_args "")
    set(extra_cmake_args "")

    # If the -submodules or --module-subset options were specified, transform
    # the values into something configure understands and pass them to configure.
    qt_ir_get_option_value(module-subset submodules)
    if(submodules)
        qt_ir_get_top_level_submodules(include_submodules exclude_submodules)
        if(NOT include_submodules AND arg_ALREADY_INITIALIZED)
            set(include_submodules "${submodules}")
        endif()

        # qtrepotools is always implicitly cloned, but it doesn't actually
        # have a CMakeLists.txt, so remove it.
        list(REMOVE_ITEM include_submodules "qtrepotools")

        # Make sure to explicitly pass -DBUILD_<module>=ON, in case they were
        # skipped before, otherwise configure might fail.
        if(include_submodules)
            set(explicit_build_submodules "${include_submodules}")
            list(TRANSFORM explicit_build_submodules PREPEND "-DBUILD_")
            list(TRANSFORM explicit_build_submodules APPEND "=ON")
            list(APPEND extra_cmake_args ${explicit_build_submodules})
        endif()

        list(JOIN include_submodules "," include_submodules)
        list(JOIN exclude_submodules "," exclude_submodules)

        # Handle case when the -skip argument is already passed.
        # In that case read the passed values, merge with new ones,
        # remove both the -skip and its values, and re-add it later.
        list(FIND filtered_args "-skip" skip_index)
        if(exclude_submodules AND skip_index GREATER -1)
            list(LENGTH filtered_args filtered_args_length)
            math(EXPR skip_args_index "${skip_index} + 1")

            if(skip_args_index LESS filtered_args_length)
                list(GET filtered_args "${skip_args_index}" skip_args)
                string(REPLACE "," ";" skip_args "${skip_args}")
                list(APPEND skip_args ${exclude_submodules})
                list(REMOVE_DUPLICATES skip_args)
                list(JOIN skip_args "," exclude_submodules)
                list(REMOVE_AT filtered_args "${skip_args_index}")
                list(REMOVE_AT filtered_args "${skip_index}")
            endif()
        endif()

        # Handle case when only '-submodules existing' is passed and the
        # subset ends up empty.
        if(include_submodules)
            list(APPEND extra_configure_args "-submodules" "${include_submodules}")
        endif()
        if(exclude_submodules)
            list(APPEND extra_configure_args "-skip" "${exclude_submodules}")
        endif()
    endif()

    # Insert the extra arguments into the proper positions before and after '--'.
    list(FIND filtered_args "--" cmake_args_index)

    # -- is not found
    if(cmake_args_index EQUAL -1)
        # Append extra configure args if present
        if(extra_configure_args)
            list(APPEND filtered_args ${extra_configure_args})
        endif()
        # Append extra cmake args if present, but make sure to add -- first at the end
        if(extra_cmake_args)
            list(APPEND filtered_args "--")
            list(APPEND filtered_args ${extra_cmake_args})
        endif()
    else()
        # -- is found, that means we probably have cmake args
        # Insert extra configure args if present, before the -- index.
        if(extra_configure_args)
            list(INSERT filtered_args "${cmake_args_index}" ${extra_configure_args})
        endif()
        # Find the -- index again, because it might have moved
        list(FIND filtered_args "--" cmake_args_index)
        # Compute the index of the argument after the --.
        math(EXPR cmake_args_index "${cmake_args_index} + 1")
        # Insert extra cmake args if present, after the -- index.
        if(extra_cmake_args)
            list(INSERT filtered_args "${cmake_args_index}" ${extra_cmake_args})
        endif()
    endif()

    # Pass --help if it was requested.
    qt_ir_is_help_requested(show_help)
    if(show_help)
        list(APPEND filtered_args "-help")
    endif()

    set(${out_var} "${filtered_args}" PARENT_SCOPE)
endfunction()

# Checks whether any of the arguments passed on the command line are options
# that are marked as unsupported in the cmake port of init-repository.
function(qt_ir_check_if_unsupported_options_used out_var out_var_option_name)
    qt_ir_get_unsupported_options(unsupported_options)

    set(unsupported_options_used FALSE)
    foreach(unsupported_option IN LISTS unsupported_options)
        qt_ir_get_option_value(${unsupported_option} value)
        if(value)
            set(${out_var_option_name} "${unsupported_option}" PARENT_SCOPE)
            set(unsupported_options_used TRUE)
            break()
        endif()
    endforeach()
    set(${out_var} "${unsupported_options_used}" PARENT_SCOPE)
endfunction()

# When an unsupported option is used, show an error message and tell the user
# to run the perly script manually.
function(qt_ir_show_error_how_to_run_perl opt_file unsupported_option_name)
    qt_ir_get_raw_args_from_optfile("${opt_file}" args)
    string(REPLACE ";" " " args "${args}")

    set(perl_cmd "perl ./init-repository.pl ${args}")

    message(FATAL_ERROR
        "Option '${unsupported_option_name}' is not implemented in the cmake "
        "port of init-repository. Please let us know if this option is really "
        "important for you at https://bugreports.qt.io/. Meanwhile, you can "
        "still run the perl script directly. \n ${perl_cmd}")
endfunction()

# Check whether help was requested.
function(qt_ir_is_help_requested out_var)
    qt_ir_get_option_value(help value)
    set(${out_var} "${value}" PARENT_SCOPE)
endfunction()

# Check whether the verbose option was used.
function(qt_ir_is_verbose out_var)
    qt_ir_get_option_value(verbose value)
    set(${out_var} "${value}" PARENT_SCOPE)
endfunction()

function(qt_ir_is_called_from_configure out_var)
    qt_ir_get_option_value(from-configure value)
    set(${out_var} "${value}" PARENT_SCOPE)
endfunction()

# Main logic of the script.
function(qt_ir_run_after_args_parsed top_level_src_path out_var_exit_reason)
    set(${out_var_exit_reason} FALSE PARENT_SCOPE)

    qt_ir_is_called_from_configure(is_called_from_configure)

    qt_ir_is_help_requested(show_help)
    if(show_help AND NOT is_called_from_configure)
        qt_ir_show_help()
        set(${out_var_exit_reason} SHOWED_HELP PARENT_SCOPE)
        return()
    endif()

    set(working_directory "${top_level_src_path}")

    qt_ir_handle_if_already_initialized(already_initialized "${working_directory}")
    if(already_initialized)
        set(${out_var_exit_reason} ALREADY_INITIALIZED PARENT_SCOPE)
        return()
    endif()

    # This will be used by the module subset processing to determine whether we
    # should re-initialize the previously initialized (existing) subset.
    qt_ir_check_if_already_initialized_cmake_style(is_initialized
        "${working_directory}" FORCE_QUIET)
    set(previously_initialized_option "")
    if(is_initialized)
        set(previously_initialized_option PREVIOUSLY_INITIALIZED)
    endif()


    # Ge the name of the qt5 repo (tqtc- or not) and the base url for all other repos
    qt_ir_get_qt5_repo_name_and_base_url(
        OUT_VAR_QT5_REPO_NAME qt5_repo_name
        OUT_VAR_BASE_URL base_url
        WORKING_DIRECTORY "${working_directory}")

    qt_ir_get_already_initialized_submodules("${prefix}"
        already_initialized_submodules
        "${qt5_repo_name}"
        "${working_directory}")

    # Get some additional options to pass down.
    qt_ir_get_option_value(alternates alternates)
    qt_ir_get_option_as_cmake_flag_option(branch "CHECKOUT_BRANCH" checkout_branch_option)

    # The prefix for the cmake-style 'dictionary' that will be used by various functions.
    set(prefix "ir_top")

    # Initialize and clone the submodules
    qt_ir_handle_init_submodules("${prefix}"
        ALTERNATES "${alternates}"
        ALREADY_INITIALIZED_SUBMODULES "${already_initialized_submodules}"
        BASE_URL "${base_url}"
        PARENT_REPO_BASE_GIT_PATH "${qt5_repo_name}"
        PROCESS_SUBMODULES_FROM_COMMAND_LINE
        WORKING_DIRECTORY "${working_directory}"
        ${checkout_branch_option}
        ${previously_initialized_option}
    )

    # Add gerrit remotes.
    qt_ir_add_git_remotes("${qt5_repo_name}" "${working_directory}")

    # Install commit and other various hooks.
    qt_ir_install_git_hooks(
        PARENT_REPO_BASE_GIT_PATH "${qt5_repo_name}"
        TOP_LEVEL_SRC_PATH "${top_level_src_path}"
        WORKING_DIRECTORY "${working_directory}"
    )

    # Mark the repo as being initialized.
    qt_ir_set_is_initialized("${working_directory}")
endfunction()

# Entrypoint of the init-repository script.
function(qt_ir_run_main_script top_level_src_path out_var_exit_reason)
    set(${out_var_exit_reason} FALSE PARENT_SCOPE)

    # Windows passes backslash paths.
    file(TO_CMAKE_PATH "${top_level_src_path}" top_level_src_path)

    qt_ir_set_known_command_line_options()

    # If called from configure, there might be arguments that init-repository doesn't know about
    # because they are meant for configure. In that case ignore unknown arguments.
    qt_ir_get_option_value(from-configure from_configure)
    if(from_configure)
        set(ignore_unknown_args "IGNORE_UNKNOWN_ARGS")
    else()
        set(ignore_unknown_args "")
    endif()

    qt_ir_process_args_from_optfile("${OPTFILE}" "${ignore_unknown_args}")

    qt_ir_handle_called_from_configure("${top_level_src_path}" exit_reason)
    if(exit_reason)
        set(${out_var_exit_reason} "${exit_reason}" PARENT_SCOPE)
        return()
    endif()

    qt_ir_check_if_unsupported_options_used(
        unsupported_options_used option_name)
    if(unsupported_options_used)
        qt_ir_show_error_how_to_run_perl("${OPTFILE}" "${option_name}")
    endif()

    qt_ir_run_after_args_parsed("${top_level_src_path}" exit_reason)
    set(${out_var_exit_reason} "${exit_reason}" PARENT_SCOPE)

    # TODO: Consider using cmake_language(EXIT <exit-code>) when cmake 3.29 is released.
endfunction()