1#
2# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230)
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6
7cmake_minimum_required(VERSION 3.8.2)
8
9# Wrapper function around find_file that generates a fatal error if it isn't found
10# Is equivalent to find_file except that it adds CMAKE_CURRENT_SOURCE_DIR as a path and sets
11# CMAKE_FIND_ROOT_PATH_BOTH
12function(RequireFile config_name file_name)
13    find_file(
14        ${config_name} "${file_name}"
15        PATHS "${CMAKE_CURRENT_SOURCE_DIR}"
16        CMAKE_FIND_ROOT_PATH_BOTH ${ARGV}
17    )
18    if("${${config_name}}" STREQUAL "${config_name}-NOTFOUND")
19        message(FATAL_ERROR "Failed to find required file ${file_name}")
20    endif()
21    mark_as_advanced(FORCE ${config_name})
22endfunction(RequireFile)
23
24# Helper function for converting a filename to an absolute path. It first converts to
25# an absolute path based in the current source directory, and if the results in a file
26# that doesn't exist it returns an absolute path based from the binary directory
27# This file check is done at generation time and is considered safe as source files
28# should not be being added as part of the build step (except into the build directory)
29function(get_absolute_source_or_binary output input)
30    get_filename_component(test "${input}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
31    if(NOT EXISTS "${test}")
32        get_filename_component(test "${input}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
33    endif()
34    set("${output}" "${test}" PARENT_SCOPE)
35endfunction(get_absolute_source_or_binary)
36
37function(get_absolute_list_source_or_binary output input)
38    get_filename_component(test "${input}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_LIST_DIR}")
39    if(NOT EXISTS "${test}")
40        get_absolute_source_or_binary(test ${input})
41    endif()
42    set("${output}" "${test}" PARENT_SCOPE)
43endfunction()
44
45# Generates a custom command that preprocesses an input file into an output file
46# Uses the current compilation settings as well as any EXTRA_FLAGS provided. Can also
47# be given any EXTRA_DEPS to depend upon
48# A target with the name `output_target` will be generated to create a target based dependency
49# for the output file
50# Output and input files will be converted to absolute paths based on the following rules
51#  * Output is assumed to be in CMAKE_CURRENT_BINARY_DIR
52#  * Input is assumed to be in CMAKE_CURRENT_SOURCE_DIR if it resolves to a file that exists
53#    otherwise it is assumed to be in CMAKE_CURRENT_BINARY_DIR
54function(cppfile output output_target input)
55    cmake_parse_arguments(PARSE_ARGV 3 "CPP" "" "EXACT_NAME" "EXTRA_DEPS;EXTRA_FLAGS")
56    if(NOT "${CPP_UNPARSED_ARGUMENTS}" STREQUAL "")
57        message(FATAL_ERROR "Unknown arguments to cppfile: ${CPP_UNPARSED_ARGUMENTS}")
58    endif()
59    get_absolute_source_or_binary(input "${input}")
60    set(file_copy_name "${output_target}_temp.c")
61    # If EXACT_NAME then we copy the input file to the name given by the caller. Otherwise
62    # generate a rule for copying the input file to a default name.
63    if(CPP_EXACT_NAME)
64        set(file_copy_name ${CPP_EXACT_NAME})
65    endif()
66    add_custom_command(
67        OUTPUT ${file_copy_name}
68        COMMAND
69            ${CMAKE_COMMAND} -E copy ${input} ${CMAKE_CURRENT_BINARY_DIR}/${file_copy_name}
70        COMMENT "Creating C input file for preprocessor"
71        DEPENDS ${CPP_EXTRA_DEPS} ${input}
72    )
73    add_custom_target(${output_target}_copy_in DEPENDS ${file_copy_name})
74    # Now generate an object library to persuade cmake to just do compilation and not try
75    # and link our 'object' files
76    add_library(${output_target}_temp_lib OBJECT ${file_copy_name})
77    add_dependencies(${output_target}_temp_lib ${output_target}_copy_in)
78    # Give the preprecess flag
79    target_compile_options(${output_target}_temp_lib PRIVATE -E)
80    # Give any other flags from the user
81    target_compile_options(${output_target}_temp_lib PRIVATE ${CPP_EXTRA_FLAGS})
82    # Now copy from the random name cmake gave our object file into the one desired by the user
83    add_custom_command(
84        OUTPUT ${output}
85        COMMAND
86            ${CMAKE_COMMAND} -E copy $<TARGET_OBJECTS:${output_target}_temp_lib> ${output}
87        DEPENDS ${output_target}_temp_lib $<TARGET_OBJECTS:${output_target}_temp_lib>
88    )
89    add_custom_target(${output_target} DEPENDS ${output})
90endfunction(cppfile)
91
92# Function to generate a custom command to process a bitfield file. The input
93# (pbf_path) is either a .bf file or, if you used pre-processor directives, a
94# pre-processed .bf file. As this invokes a python tool that places a file
95# in the current working directory a unqiue 'work_dir' needs to be provided
96# for this command to execute in
97# This function is not intended to be used directly, rather one of its wrappers
98# that is specialized to generate a specific kind of output should be used
99# These wrappers work by passing the additional 'args' that get passed on to
100# the bitfield generator
101function(GenBFCommand args target_name pbf_path pbf_target deps)
102    # Since we're going to change the working directory first convert any paths to absolute
103    get_filename_component(
104        target_name_absolute
105        "${target_name}"
106        ABSOLUTE
107        BASE_DIR
108        "${CMAKE_CURRENT_BINARY_DIR}"
109    )
110    get_absolute_source_or_binary(pbf_path_absolute "${pbf_path}")
111    add_custom_command(
112        OUTPUT "${target_name_absolute}"
113        COMMAND
114            "${PYTHON3}" "${BF_GEN_PATH}" "${args}" "${pbf_path_absolute}" "${target_name_absolute}"
115        DEPENDS
116            "${BF_GEN_PATH}"
117            "${pbf_path_absolute}"
118            "${pbf_target}"
119            ${deps}
120        COMMENT "Generating from ${pbf_path}" COMMAND_EXPAND_LISTS
121        VERBATIM
122    )
123endfunction(GenBFCommand)
124
125# Wrapper function for generating both a target and command to process a bitfield file
126function(GenBFTarget args target_name target_file pbf_path pbf_target deps)
127    GenBFCommand("${args}" "${target_file}" "${pbf_path}" "${pbf_target}" "${deps}")
128    add_custom_target(${target_name} DEPENDS "${target_file}")
129endfunction(GenBFTarget)
130
131# Wrapper around GenBFTarget for generating a C header file out of a bitfield specification
132# environment is empty for kernel generation and "libsel4" for generating non kernel headers
133# prunes is an optional list of files that will be passed as --prune options to the bitfield
134# generator
135function(GenHBFTarget environment target_name target_file pbf_path pbf_target prunes deps)
136    set(args "")
137    if(NOT "${environment}" STREQUAL "")
138        list(APPEND args --environment "${environment}")
139    endif()
140    foreach(prune IN LISTS prunes)
141        get_absolute_source_or_binary(prune_absolute "${prune}")
142        list(APPEND args "--prune" "${prune_absolute}")
143    endforeach()
144    list(APPEND deps ${prunes})
145    GenBFTarget("${args}" "${target_name}" "${target_file}" "${pbf_path}" "${pbf_target}" "${deps}")
146endfunction(GenHBFTarget)
147
148# Wrapper for generating different kinds of .thy files from bitfield specifications
149function(GenThyBFTarget args target_name target_file pbf_path pbf_target prunes deps)
150    get_filename_component(cspec_dir "${CSPEC_DIR}" ABSOLUTE BASE_DIR)
151    list(APPEND args --cspec-dir "${cspec_dir}")
152    if(SKIP_MODIFIES)
153        list(APPEND args "--skip_modifies")
154    endif()
155    foreach(prune IN LISTS prunes)
156        list(APPEND args "--prune" "${prune}")
157    endforeach()
158    GenBFTarget("${args}" "${target_name}" "${target_file}" "${pbf_path}" "${pbf_target}" "${deps}")
159endfunction(GenThyBFTarget)
160
161# Generate hol definitions from a bitfield specification
162function(GenDefsBFTarget target_name target_file pbf_path pbf_target prunes deps)
163    set(args "")
164    list(APPEND args --hol_defs)
165    GenThyBFTarget(
166        "${args}"
167        "${target_name}"
168        "${target_file}"
169        "${pbf_path}"
170        "${pbf_target}"
171        "${prunes}"
172        "${deps}"
173    )
174endfunction(GenDefsBFTarget)
175
176# Generate proofs from a bitfield specification
177function(GenProofsBFTarget target_name target_file pbf_path pbf_target prunes deps)
178    set(args "")
179    # Get an absolute path to cspec_dir so that the final theory file is portable
180    list(
181        APPEND
182            args
183            --hol_proofs
184            --umm_types
185            "${UMM_TYPES}"
186    )
187    if(SORRY_BITFIELD_PROOFS)
188        list(APPEND args "--sorry_lemmas")
189    endif()
190    list(
191        APPEND
192            args
193            "--toplevel;$<JOIN:$<TARGET_PROPERTY:kernel_config_target,TOPLEVELTYPES>,;--toplevel;>"
194    )
195    list(APPEND deps "${UMM_TYPES}")
196    GenThyBFTarget(
197        "${args}"
198        "${target_name}"
199        "${target_file}"
200        "${pbf_path}"
201        "${pbf_target}"
202        "${prunes}"
203        "${deps}"
204    )
205endfunction(GenProofsBFTarget)
206
207macro(cfg_str_add_disabled cfg_str name)
208    # include in liner files can't use double slash comments
209    list(APPEND ${cfg_str} "/* disabled: CONFIG_${name} */")
210endmacro()
211
212macro(cfg_str_add_define cfg_str name value comment)
213    set(cfg_define_str "#define CONFIG_${name}  ${value}")
214    if(NOT "${comment}" STREQUAL "")
215        # include in liner files can't use double slash comments
216        string(APPEND cfg_define_str "  /* ${comment} */")
217    endif()
218    list(APPEND ${cfg_str} ${cfg_define_str})
219endmacro()
220
221macro(cfg_str_add cfg_str name value)
222    if("${value}" STREQUAL "")
223        cfg_str_add_define(${cfg_str} ${name} "" "empty")
224    else()
225        cfg_str_add_define(${cfg_str} ${name} "${value}" "")
226    endif()
227endmacro()
228
229macro(cfg_str_add_as_1 cfg_str name var)
230    cfg_str_add_define(${cfg_str} ${name} "1" "${var}=${${var}}")
231endmacro()
232
233# config_option(cmake_option_name c_config_name doc DEFAULT default [DEPENDS deps] [DEFAULT_DISABLE default_disabled])
234# Defines a toggleable configuration option that will be present in the cache and the
235# cmake-gui
236#  optionname is the name of the cache variable that can be used directly in cmake scripts
237#   to get the value of the option
238#  configname is the name (prefixed with CONFIG_) that will appear in generated
239#   C configuration headers
240#  DEFAULT is the default value of the config that it should initially be set to
241#  doc Help string to explain the option in the cmake-gui
242# An additional DEPENDS arguments may be passed, which is a list of conditions to evaluate and if true,
243#  the option will exist. If the option doesn't exist it will be set to DEFAULT_DISABLED, or if
244#  that wasn't provided then just DEFAULT
245# If the option is true it adds to the global configure_string variable (see add_config_library)
246function(config_option optionname configname doc)
247    cmake_parse_arguments(PARSE_ARGV 3 "CONFIG" "" "DEPENDS;DEFAULT_DISABLED;DEFAULT" "")
248    if(NOT "${CONFIG_UNPARSED_ARGUMENTS}" STREQUAL "")
249        message(FATAL_ERROR "Unknown arguments to config_option")
250    endif()
251    if("${CONFIG_DEFAULT_DISABLED}" STREQUAL "")
252        set(CONFIG_DEFAULT_DISABLED "${CONFIG_DEFAULT}")
253    endif()
254    set(valid ON)
255    if(NOT "${CONFIG_DEPENDS}" STREQUAL "")
256        # Check the passed in dependencies. This loop and logic is inspired by the
257        # actual cmake_dependent_option code
258        foreach(test ${CONFIG_DEPENDS})
259            string(
260                REGEX
261                REPLACE
262                    " +"
263                    ";"
264                    test
265                    "${test}"
266            )
267            if(NOT (${test}))
268                set(valid OFF)
269                break()
270            endif()
271        endforeach()
272    endif()
273    if(valid)
274        # Check for an existing value, and set the option to that, otherwise use the default
275        # Also reset the default if we switched from disabled to enabled
276        if((DEFINED ${optionname}) AND (NOT DEFINED ${optionname}_DISABLED))
277            set(${optionname} "${${optionname}}" CACHE BOOL "${doc}" FORCE)
278        else()
279            set(${optionname} "${CONFIG_DEFAULT}" CACHE BOOL "${doc}" FORCE)
280            unset(${optionname}_DISABLED CACHE)
281        endif()
282        # This is a directory scope setting used to allow or prevent config options
283        # from appearing in the cmake config GUI
284        if(SEL4_CONFIG_DEFAULT_ADVANCED)
285            mark_as_advanced(${optionname})
286        endif()
287    else()
288        set(${optionname} "${CONFIG_DEFAULT_DISABLED}" CACHE INTERNAL "${doc}" FORCE)
289        set(${optionname}_DISABLED TRUE CACHE INTERNAL "" FORCE)
290    endif()
291    set(local_config_string "${configure_string}")
292    if(${optionname})
293        cfg_str_add_as_1(local_config_string ${configname} ${optionname})
294    else()
295        cfg_str_add_disabled(local_config_string ${configname})
296    endif()
297    set(configure_string "${local_config_string}" PARENT_SCOPE)
298endfunction(config_option)
299
300# Set a configuration option to a particular value. This value will not appear in
301# the cmake-gui, but will produce an internal cmake cache variable and generated
302# configuration headers.
303macro(config_set optionname configname value)
304    set(${optionname} "${value}" CACHE INTERNAL "" FORCE)
305    set(c_define "CONFIG_${configname}")
306    if("${value}" STREQUAL "OFF")
307        cfg_str_add_disabled(configure_string ${configname})
308    else()
309        if("${value}" STREQUAL "ON")
310            cfg_str_add_as_1(configure_string ${configname} ${optionname})
311        else()
312            # we have to quote ${value} here because it could be empty
313            cfg_str_add(configure_string ${configname} "${value}")
314        endif()
315    endif()
316endmacro(config_set)
317
318# config_cmake_string(cmake_option_name c_config_name doc DEFAULT default [DEPENDS dep]
319#   [DEFAULT_DISABLED default_disabled] [UNDEF_DISABLED] [QUOTE]
320# Defines a configuration option that is a user configurable string. Most parameters
321# are the same as config_option
322# UNQUOTE if specified says this is something with more semantics like a number or identifier
323#  and should not be quoted in the output
324# [UNDEF_DISABLED] can be specified to explicitly disable generation of any output value when
325#  the configuration dependencies are unmet
326# Adds to the global configure_string variable (see add_config_library)
327function(config_string optionname configname doc)
328    cmake_parse_arguments(
329        PARSE_ARGV
330        3
331        "CONFIG"
332        "UNQUOTE;UNDEF_DISABLED"
333        "DEPENDS;DEFAULT_DISABLED;DEFAULT"
334        ""
335    )
336    if(NOT "${CONFIG_UNPARSED_ARGUMENTS}" STREQUAL "")
337        message(FATAL_ERROR "Unknown arguments to config_option: ${CONFIG_UNPARSED_ARGUMENTS}")
338    endif()
339    if("${CONFIG_DEFAULT}" STREQUAL "")
340        message(FATAL_ERROR "No default specified for ${config_option}")
341    endif()
342    if("${CONFIG_DEFAULT_DISABLED}" STREQUAL "")
343        set(CONFIG_DEFAULT_DISABLED "${CONFIG_DEFAULT}")
344    endif()
345    set(valid ON)
346    set(local_config_string "${configure_string}")
347    if(NOT "${CONFIG_DEPENDS}" STREQUAL "")
348        # Check the passed in dependencies. This loop and logic is inspired by the
349        # actual cmake_dependent_option code
350        foreach(test ${CONFIG_DEPENDS})
351            string(
352                REGEX
353                REPLACE
354                    " +"
355                    ";"
356                    test
357                    "${test}"
358            )
359            if(NOT (${test}))
360                set(valid OFF)
361                break()
362            endif()
363        endforeach()
364    endif()
365    set(cfg_tag_option "")
366    if(valid)
367        # See if we transitioned from disabled to enabled. We do this by having an
368        # _UNAVAILABLE variable. We want to ensure that if the option previously had
369        # unmet conditions that we reset its value to 'default'. This is needed
370        # because whilst the option had unmet conditions it still potentially had
371        # a value in the form of the optional disabled_value
372        set(force "")
373        if(${optionname}_UNAVAILABLE)
374            set(force "FORCE")
375            unset(${optionname}_UNAVAILABLE CACHE)
376        endif()
377        set(${optionname} "${CONFIG_DEFAULT}" CACHE STRING "${doc}" ${force})
378        set(cfg_tag_option ${optionname})
379        # This is a directory scope setting used to allow or prevent config options
380        # from appearing in the cmake config GUI
381        if(SEL4_CONFIG_DEFAULT_ADVANCED)
382            mark_as_advanced(${optionname})
383        endif()
384    else()
385        if(CONFIG_UNDEF_DISABLED)
386            unset(${optionname} CACHE)
387        else()
388            # Forcively change the value to its disabled_value
389            set(${optionname} "${CONFIG_DEFAULT_DISABLED}" CACHE INTERNAL "" FORCE)
390            set(cfg_tag_option ${optionname})
391        endif()
392        # Sset _UNAVAILABLE so we can detect when the option because enabled again
393        set(${optionname}_UNAVAILABLE ON CACHE INTERNAL "" FORCE)
394    endif()
395    if(cfg_tag_option)
396        if(CONFIG_UNQUOTE)
397            set(quote "")
398        else()
399            set(quote "\"")
400        endif()
401        cfg_str_add(local_config_string ${configname} "${quote}@${cfg_tag_option}@${quote}")
402    endif()
403    set(configure_string "${local_config_string}" PARENT_SCOPE)
404endfunction(config_string)
405
406# Defines a multi choice / select configuration option
407#  optionname is the name of the cache variable that can be used directly in cmake scripts
408#   to get the value of the option
409#  configname is the name (prefixed with CONFIG_) that will appear in generated
410#   C configuration headers and is set to the string of the selected config
411#  doc Help string to explain the option in the cmake-gui
412# Then any number of additional arguments may be supplied each describing one of the potential
413# configuration choices. Each additional argument is a list of (option_value, option_cache,
414# option_config, [condition]...)
415#  option_value is the string that represents this option. this is what the user will see
416#   in the cmake-gui and what configname will get defined to if this option is selected
417#  option_cache is like optionname and is set to ON when this option is selected and OFF
418#   if it is not
419#  condition may be repeated as many times and all conditions must be true for this choice
420#   to appear in the list
421# If no valid choices are given (either because none are given or the ones that were given
422# did not have their conditions met) then this option will be disabled and not appear in
423# the cmake-gui
424# Adds to the global configure_string variable (see add_config_library)
425function(config_choice optionname configname doc)
426    # Cannot use ARGN because each argument itself is a list
427    math(EXPR limit "${ARGC} - 1")
428    set(local_config_string "${configure_string}")
429    # force_default represents whether we need to force a new value or not. We would need
430    # to force a new value for example if we detect that the current selected choice is
431    # no longer (due to conditions) a valid choice
432    set(force_default "")
433    # Used to track the first time we see a valid enabled choice. The first valid choice
434    # becomes the default and if we never find a valid choice then we know to disable this config
435    set(first ON)
436    # This tracks whether or not the current selected choice is one of the ones that we
437    # have been passed. If we fail to find the currently selected choice then, similar to
438    # if the current choice is invalid to do an unment condition, we must switch to some
439    # valid default
440    set(found_current OFF)
441    foreach(i RANGE 3 ${limit})
442        set(option "${ARGV${i}}")
443        # Extract the constant parts of the choice information and just leave any
444        # conditional information
445        list(GET option 0 option_value)
446        list(GET option 1 option_cache)
447        list(GET option 2 option_config)
448        list(
449            REMOVE_AT
450                option
451                0
452                1
453                2
454        )
455        # Construct a list of all of our options
456        list(APPEND all_strings "${option_value}")
457        # By default we assume is valid, we may change our mind after checking dependencies
458        # (if there are any). This loop is again based off the one in cmake_dependent_option
459        set(valid ON)
460        foreach(truth IN LISTS option)
461            string(
462                REGEX
463                REPLACE
464                    " +"
465                    ";"
466                    truth
467                    "${truth}"
468            )
469            if(NOT (${truth}))
470                # This choice isn't valid due to unmet conditions so we must check if we have
471                # currently selected this choice. If so trigger the force_default
472                if("${${optionname}}" STREQUAL "${option_value}")
473                    set(force_default "FORCE")
474                endif()
475                set(valid OFF)
476            endif()
477        endforeach()
478        if(valid)
479            # Is a valid option, add to the strings list
480            list(APPEND strings "${option_value}")
481            if(first)
482                set(first OFF)
483                set(first_cache "${option_cache}")
484                set(first_config "${option_config}")
485                # Use the first valid option we find as the default. This default is will be
486                # used if there is no current value, or for some reason we need to override
487                # the current value (see force_default above)
488                set(default "${option_value}")
489            endif()
490            # Check if this option is the one that is currently set
491            if("${${optionname}}" STREQUAL "${option_value}")
492                set(${option_cache} ON CACHE INTERNAL "" FORCE)
493                cfg_str_add_as_1(local_config_string ${option_config} ${option_cache})
494                set(found_current ON)
495            else()
496                set(${option_cache} OFF CACHE INTERNAL "" FORCE)
497                cfg_str_add_disabled(local_config_string ${option_config})
498            endif()
499        else()
500            # Remove this config as it's not valid
501            unset(${option_cache} CACHE)
502        endif()
503    endforeach()
504    if(NOT found_current)
505        # Currently selected option wasn't found so reset to a default that we know is valid
506        set(force_default "FORCE")
507    endif()
508    if(first)
509        # None of the choices were valid. Remove this option so its not visible
510        unset(${optionname} CACHE)
511    else()
512        cfg_str_add(local_config_string ${configname} "@${optionname}@")
513        set(configure_string "${local_config_string}" PARENT_SCOPE)
514        set(${optionname} "${default}" CACHE STRING "${doc}" ${force_default})
515        # This is a directory scope setting used to allow or prevent config options
516        # from appearing in the cmake config GUI
517        if(SEL4_CONFIG_DEFAULT_ADVANCED)
518            mark_as_advanced(${optionname})
519        endif()
520        set_property(CACHE ${optionname} PROPERTY STRINGS ${strings})
521        if(NOT found_current)
522            # The option is actually enabled, but we didn't enable the correct
523            # choice earlier, since we didn't know we were going to revert to
524            # the default. So add the option setting here
525            set(${first_cache} ON CACHE INTERNAL "" FORCE)
526            cfg_str_add_as_1(local_config_string ${first_config} ${first_cache})
527        endif()
528    endif()
529    # Save all possible options to an internal value.  This is to allow enumerating the options elsewhere.
530    # We create a new variable because cmake doesn't support arbitrary properties on cache variables.
531    set(${optionname}_all_strings ${all_strings} CACHE INTERNAL "" FORCE)
532    set(configure_string "${local_config_string}" PARENT_SCOPE)
533endfunction(config_choice)
534
535# Defines a target for a 'configuration' library, which generates a header based
536# upon current state of cache/variables and a provided template string. Additionally
537# the generated library gets added to a known global list of 'configuration' libraries
538# This list can be used if someone wants all the configurations
539# Whilst this function takes an explicit configure_template, generally this will always
540# be '${configure_string}' as that is the global variable automatically appended to
541# by the config_ helper macros and functions above
542# This generates a  library that can be linked against with
543# target_link_library(<target> ${prefix}_Config)
544# Which will allow you to do #include <${prefix}/gen_config.h>
545function(add_config_library prefix configure_template)
546    set(config_dir "${CMAKE_CURRENT_BINARY_DIR}/gen_config")
547    set(config_file "${config_dir}/${prefix}/gen_config.h")
548    string(CONFIGURE "${configure_template}" config_header_contents)
549    # Turn the list of configurations into a valid C file of different lines
550    string(
551        REPLACE
552            ";"
553            "\n"
554            config_header_contents
555            "${config_header_contents}"
556    )
557    file(GENERATE OUTPUT "${config_file}" CONTENT "\n#pragma once\n\n${config_header_contents}")
558    add_custom_target(${prefix}_Gen DEPENDS "${config_file}")
559    add_library(${prefix}_Config INTERFACE)
560    target_include_directories(${prefix}_Config INTERFACE "${config_dir}")
561    add_dependencies(${prefix}_Config ${prefix}_Gen ${config_file})
562    set_property(GLOBAL APPEND PROPERTY CONFIG_LIBRARIES "${prefix}")
563    # Set a property on the library that is a list of the files we generated. This
564    # allows custom build commands to easily get a file dependency list so they can
565    # 'depend' upon this target easily
566    set_property(TARGET ${prefix}_Gen APPEND PROPERTY GENERATED_FILES "${config_file}")
567endfunction(add_config_library)
568
569macro(get_generated_files output target)
570    get_property(${output} TARGET ${target} PROPERTY GENERATED_FILES)
571endmacro(get_generated_files)
572
573# This rule tries to emulate an 'autoconf' header. autoconf generated headers
574# were previously used as configuration, so this rule provides a way for previous
575# applications and libraries to build without modification. The config_list
576# is a list of 'prefix' values that have been passed to add_config_library
577# This generates a library with ${targetname} that when linked against
578# will allow code to simply #include <autoconf.h>
579function(generate_autoconf targetname config_list)
580    set(link_list "")
581    set(gen_list "")
582    set(config_header_contents "\n#pragma once\n\n")
583    foreach(config IN LISTS config_list)
584        list(APPEND link_list "${config}_Config")
585        get_generated_files(gens ${config}_Gen)
586        list(APPEND gen_list ${gens})
587        string(APPEND config_header_contents "#include <${config}/gen_config.h>\n")
588    endforeach()
589    set(config_dir "${CMAKE_CURRENT_BINARY_DIR}/autoconf")
590    set(config_file "${config_dir}/autoconf.h")
591
592    file(GENERATE OUTPUT "${config_file}" CONTENT "${config_header_contents}")
593    add_custom_target(${targetname}_Gen DEPENDS "${config_file}")
594    add_library(${targetname} INTERFACE)
595    target_link_libraries(${targetname} INTERFACE ${link_list})
596    target_include_directories(${targetname} INTERFACE "${config_dir}")
597    add_dependencies(${targetname} ${targetname}_Gen ${config_file})
598    # Set our GENERATED_FILES property to include the GENERATED_FILES of all of our input
599    # configurations, as well as the files we generated
600    set_property(
601        TARGET ${targetname}_Gen
602        APPEND
603        PROPERTY GENERATED_FILES "${config_file}" ${gen_list}
604    )
605endfunction(generate_autoconf)
606
607# Macro that allows for appending to a specified list only if all the supplied conditions are true
608macro(list_append_if list dep)
609    set(list_append_local_list ${${list}})
610    set(list_append_valid ON)
611    foreach(truth IN ITEMS ${dep})
612        string(
613            REGEX
614            REPLACE
615                " +"
616                ";"
617                truth
618                "${truth}"
619        )
620        if(NOT (${truth}))
621            set(list_append_valid OFF)
622            break()
623        endif()
624    endforeach()
625    if(list_append_valid)
626        list(APPEND list_append_local_list ${ARGN})
627    endif()
628    set(${list} ${list_append_local_list} PARENT_SCOPE)
629endmacro(list_append_if)
630
631# Checks if a file is older than its dependencies
632# Will set `stale` to TRUE if outfile doesn't exist,
633# or if outfile is older than any file in `deps_list`.
634# Will also set `stale` to TRUE if the arguments given to this macro
635# change compared to the previous invocation.
636# stale: A variable to overwrite with TRUE or FALSE
637# outfile: A value that is a valid file path
638# deps_list: A variable that holds a list of file paths
639# arg_cache: A variable that holds a file to store arguments to
640# e.g:
641#  set(dts_list "filea" "fileb" "filec")
642#  set(KernelDTBPath "${CMAKE_CURRENT_BINARY_DIR}/kernel.dtb")
643#  check_outfile_stale(regen ${KernelDTBPath} dts_list ${CMAKE_CURRENT_BINARY_DIR}/dts.cmd
644#  if (regen)
645#    regen_file(${KernelDTBPath})
646#  endif()
647#
648# The above call will set regen to TRUE if the file referred
649# to by KernelDTBPath doesn't exist, or is older than any files
650# in KernelDTSIntermediate or if regen, ${KernelDTBPath} and dts_list resolve to different files.
651macro(check_outfile_stale stale outfile deps_list arg_cache)
652    set(_outfile_command "${stale} ${outfile} ${${deps_list}}")
653    if(NOT EXISTS "${arg_cache}")
654        set(_prev_command "")
655    else()
656        file(READ "${arg_cache}" _prev_command)
657    endif()
658    if(NOT "${_outfile_command}" STREQUAL "${_prev_command}")
659        set(${stale} TRUE)
660    else()
661        set(${stale} FALSE)
662    endif()
663    if(EXISTS ${outfile} AND NOT ${stale})
664        set(${stale} FALSE)
665        foreach(dep IN LISTS ${deps_list})
666            if("${dep}" IS_NEWER_THAN "${outfile}")
667                set(${stale} TRUE)
668                break()
669            endif()
670        endforeach()
671    else()
672        set(${stale} TRUE)
673    endif()
674    if(${stale})
675        file(WRITE "${arg_cache}" "${_outfile_command}")
676    endif()
677endmacro()
678
679# This macro only works when cmake is invoked with -P (script mode) on a kernel
680# verified configuration. The result is configuring and building a verified kernel.
681# CMAKE_ARGC and CMAKE_ARGV# contain command line argument information.
682# It runs the following commands to produce kernel.elf and kernel_all_pp.c:
683# cmake -G Ninja ${args} -C ${CMAKE_ARGV2} ${CMAKE_CURRENT_LIST_DIR}/..
684# ninja kernel.elf
685# ninja kernel_all_pp_wrapper
686macro(cmake_script_build_kernel)
687    if(NOT "${CMAKE_ARGC}" STREQUAL "")
688        set(args "")
689        foreach(i RANGE 3 ${CMAKE_ARGC})
690            if("${CMAKE_ARGV${i}}" STREQUAL "FORCE")
691                # Consume arg and force reinit of build dir by deleting CMakeCache.txt
692                file(REMOVE CMakeCache.txt)
693                file(REMOVE gcc.cmake)
694            else()
695                list(APPEND args ${CMAKE_ARGV${i}})
696            endif()
697        endforeach()
698        execute_process(
699            COMMAND
700                cmake -G Ninja ${args} -C ${CMAKE_ARGV2} ${CMAKE_CURRENT_LIST_DIR}/..
701            INPUT_FILE /dev/stdin
702            OUTPUT_FILE /dev/stdout
703            ERROR_FILE /dev/stderr
704        )
705        execute_process(
706            COMMAND ninja kernel.elf
707            INPUT_FILE /dev/stdin
708            OUTPUT_FILE /dev/stdout
709            ERROR_FILE /dev/stderr
710        )
711        execute_process(
712            COMMAND ninja kernel_all_pp_wrapper
713            INPUT_FILE /dev/stdin
714            OUTPUT_FILE /dev/stdout
715            ERROR_FILE /dev/stderr
716        )
717        return()
718    endif()
719endmacro()
720