File: GeneratePkgConfig.cmake

package info (click to toggle)
mongo-c-driver 2.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 47,088 kB
  • sloc: ansic: 193,670; python: 7,780; cpp: 1,493; sh: 659; makefile: 78
file content (321 lines) | stat: -rw-r--r-- 13,633 bytes parent folder | download
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
include_guard(GLOBAL)

include(GNUInstallDirs)

define_property(
    TARGET PROPERTY pkg_config_REQUIRES INHERITED
    BRIEF_DOCS "pkg-config 'Requires:' items"
    FULL_DOCS "Specify 'Requires:' items for the targets' pkg-config file"
    )
define_property(
    TARGET PROPERTY pkg_config_NAME INHERITED
    BRIEF_DOCS "The 'Name' for pkg-config"
    FULL_DOCS "The 'Name' of the pkg-config target"
    )
define_property(
    TARGET PROPERTY pkg_config_DESCRIPTION INHERITED
    BRIEF_DOCS "The 'Description' pkg-config property"
    FULL_DOCS "The 'Description' property to add to a target's pkg-config file"
    )
define_property(
    TARGET PROPERTY pkg_config_VERSION INHERITED
    BRIEF_DOCS "The 'Version' pkg-config property"
    FULL_DOCS "The 'Version' property to add to a target's pkg-config file"
    )
define_property(
    TARGET PROPERTY pkg_config_CFLAGS INHERITED
    BRIEF_DOCS "The 'Cflags' pkg-config property"
    FULL_DOCS "Set a list of options to add to a target's pkg-config file 'Cflags' field"
    )
define_property(
    TARGET PROPERTY pkg_config_INCLUDE_DIRECTORIES INHERITED
    BRIEF_DOCS "Add -I options to the 'Cflags' pkg-config property"
    FULL_DOCS "Set a list of directories that will be added using -I for the 'Cflags' pkg-config field"
    )
define_property(
    TARGET PROPERTY pkg_config_LIBS INHERITED
    BRIEF_DOCS "Add linker options to the 'Libs' pkg-config field"
    FULL_DOCS "Set a list of linker options that will joined in a string for the 'Libs' pkg-config field"
    )

# Given a string, escape any generator-expression-special characters
function(_genex_escape out in)
    # Escape '>'
    string(REPLACE ">" "$<ANGLE-R>" str "${in}")
    # Escape "$"
    string(REPLACE "$" "$<1:$>" str "${str}")
    # Undo the escaping of the dollar for $<ANGLE-R>
    string(REPLACE "$<1:$><ANGLE-R>" "$<ANGLE-R>" str "${str}")
    # Escape ","
    string(REPLACE "," "$<COMMA>" str "${str}")
    # Escape ";"
    string(REPLACE ";" "$<SEMICOLON>" str "${str}")
    set("${out}" "${str}" PARENT_SCOPE)
endfunction()

# Create a generator expression that ensures the given input generator expression
# is evaluated within the context of the named target.
function(_bind_genex_to_target out target genex)
    _genex_escape(escaped "${genex}")
    set("${out}" $<TARGET_GENEX_EVAL:${target},${escaped}> PARENT_SCOPE)
endfunction()

#[==[
Generate a pkg-config .pc file for the Given CMake target, and optionally a
rule to install it::

    generate_pkg_config(
        <target>
        [FILENAME <filename>]
        [LIBDIR <libdir>]
        [INSTALL [DESTINATION <dir>] [RENAME <filename>]]
        [CONDITION <cond>]
    )

The `<target>` must name an existing target. The following options are accepted:

FILENAME <filename>
    - The generated .pc file will have the given `<filename>`. This name *must*
      be only the filename, and not a qualified path. If omitted, the default
      filename is generated based on the target name. If using a multi-config
      generator, the default filename will include the name of the configuration
      for which it was generated.

LIBDIR <libdir>
    - Specify the subdirectory of the install prefix in which the target binary
      will live. If unspecified, uses `CMAKE_INSTALL_LIBDIR`, which comes from
      the GNUInstallDirs module, which has a default of `lib`.

INSTALL [DESTINATION <dir>] [RENAME <filename>]
    - Generate a rule to install the generated pkg-config file. This is better
      than using a `file(INSTALL)` on the generated file directly, since it
      ensures that the installed .pc file will have the correct install prefix
      value. The following additional arguments are also accepted:

      DESTINATION <dir>
        - If provided, specify the *directory* (relative to the install-prefix)
          in which the generated file will be installed. If unspecified, the
          default destination is `<libdir>/pkgconfig`

      RENAME <filename>
        - If provided, set the filename of the installed pkg-config file. If
          unspecified, the top-level `<filename>` will be used. (Note that the
          default top-level `<filename>` will include the configuration type
          when built/installed using a multi-config generator!)

CONDITION <cond>
    - The file will only be generated/installed if the condition `<cond>`
      results in the string "1" after evaluating generator expressions.

All named parameters accept generator expressions.

]==]
function(mongo_generate_pkg_config target)
    list(APPEND CMAKE_MESSAGE_CONTEXT "mongo_generate_pkg_config" "${target}")
    # Collect some target properties:
    # The name:
    _genex_escape(proj_name "${PROJECT_NAME}")
    _genex_escape(proj_desc "${PROJECT_DESCRIPTION}")
    set(tgt_name $<TARGET_PROPERTY:pkg_config_NAME>)
    set(tgt_version $<TARGET_PROPERTY:pkg_config_VERSION>)
    set(tgt_desc $<TARGET_PROPERTY:pkg_config_DESCRIPTION>)
    string(CONCAT gx_name
        $<IF:$<STREQUAL:,${tgt_name}>,
             ${proj_name},
             ${tgt_name}>)
    # Version:
    string(CONCAT gx_version
        $<IF:$<STREQUAL:,${tgt_version}>,
             ${PROJECT_VERSION},
             ${tgt_version}>)
    # Description:
    string(CONCAT gx_desc
        $<IF:$<STREQUAL:,${tgt_desc}>,
             ${proj_desc},
             ${tgt_desc}>)

    # Parse and validate arguments:
    cmake_parse_arguments(PARSE_ARGV 1 ARG "" "FILENAME;LIBDIR;CONDITION" "INSTALL")

    # Compute the default FILENAME
    if(NOT DEFINED ARG_FILENAME)
        # No filename given. Pick a default:
        if(DEFINED CMAKE_CONFIGURATION_TYPES)
            # Multi-conf: We may want to generate more than one, so qualify the
            # filename with the configuration type:
            set(ARG_FILENAME "$<TARGET_FILE_BASE_NAME:${target}>-$<LOWER_CASE:$<CONFIG>>.pc")
        else()
            # Just generate a file based on the basename of the target:
            set(ARG_FILENAME "$<TARGET_FILE_BASE_NAME:${target}>.pc")
        endif()
    endif()
    message(DEBUG "FILENAME: ${ARG_FILENAME}")

    # The defalut CONDITION is just "1" (true)
    if(NOT DEFINED ARG_CONDITION)
        set(ARG_CONDITION 1)
    endif()
    message(DEBUG "CONDITION: ${ARG_CONDITION}")
    _bind_genex_to_target(gx_cond ${target} "${ARG_CONDITION}")

    # The default LIBDIR comes from GNUInstallDirs.cmake
    if(NOT ARG_LIBDIR)
        set(ARG_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
    endif()
    message(DEBUG "LIBDIR: ${ARG_LIBDIR}")
    _bind_genex_to_target(gx_libdir ${target} "${ARG_LIBDIR}")

    # Evaluate the filename genex in the context of the target:
    _bind_genex_to_target(gx_filename ${target} "${ARG_FILENAME}")
    if(IS_ABSOLUTE "${ARG_FILENAME}")
        set(gx_output "${gx_filename}")
    else()
        get_filename_component(gx_output "${CMAKE_CURRENT_BINARY_DIR}/${gx_filename}" ABSOLUTE)
    endif()
    message(DEBUG "Generating build-tree file: ${gx_output}")

    # Generate the content of the file:
    _generate_pkg_config_content(content
        NAME "${gx_name}"
        VERSION "${gx_version}"
        DESCRIPTION "${gx_desc}"
        PREFIX "%INSTALL_PLACEHOLDER%"
        LIBDIR "${gx_libdir}"
        GENEX_TARGET "${target}"
        )
    _bind_genex_to_target(gx_content ${target} "${content}")
    string(REPLACE "%INSTALL_PLACEHOLDER%" "${CMAKE_INSTALL_PREFIX}" gx_with_prefix "${gx_content}")
    # Now, generate the file:
    file(GENERATE
         OUTPUT "${gx_output}"
         CONTENT "${gx_with_prefix}"
         CONDITION "${gx_cond}")
    if(NOT "INSTALL" IN_LIST ARGN)
        # Nothing more to do here.
        message(DEBUG "(Not installing)")
        return()
    endif()

    # Installation handling:
    # Use file(GENERATE) to generate a temporary file to be picked up at install-time.
    # (For some reason, injecting the content directly into install(CODE) fails in corner cases)
    set(gx_tmpfile "${CMAKE_CURRENT_BINARY_DIR}/_pkgconfig/${target}-$<LOWER_CASE:$<CONFIG>>-for-install.txt")
    message(DEBUG "Generate for-install: ${gx_tmpfile}")
    file(GENERATE OUTPUT "${gx_tmpfile}"
         CONTENT "${gx_content}"
         CONDITION "${gx_cond}")
    # Parse the install args that we will inspect:
    cmake_parse_arguments(inst "" "DESTINATION;RENAME" "" ${ARG_INSTALL})
    if(NOT DEFINED inst_DESTINATION)
        # Install based on the libdir:
        set(inst_DESTINATION "${gx_libdir}/pkgconfig")
    endif()
    if(NOT DEFINED inst_RENAME)
        set(inst_RENAME "${ARG_FILENAME}")
    endif()
    message(DEBUG "INSTALL DESTINATION: ${inst_DESTINATION}")
    message(DEBUG "INSTALL RENAME: ${inst_RENAME}")
    # install(CODE) will write a simple temporary file:
    set(inst_tmp "${CMAKE_CURRENT_BINARY_DIR}/${target}-pkg-config-tmp.txt")
    _genex_escape(esc_cond "${ARG_CONDITION}")
    string(CONFIGURE [==[
        $<@gx_cond@:
            # Installation of pkg-config for target @target@
            message(STATUS "Generating pkg-config file: @inst_RENAME@")
            file(READ [[@gx_tmpfile@]] content)
            # Insert the install prefix:
            string(REPLACE "%INSTALL_PLACEHOLDER%" "${CMAKE_INSTALL_PREFIX}" content "${content}")
            # Write it before installing again. Lock the file to sync with parallel installs.
            file(LOCK [[@inst_tmp@.lock]] GUARD PROCESS)
            file(WRITE [[@inst_tmp@]] "${content}")
        >
        $<$<NOT:@gx_cond@>:
            # Installation was disabled for this generation.
            message(STATUS "Skipping install of file [@inst_RENAME@]: Disabled by CONDITION “@esc_cond@”")
        >
    ]==] code @ONLY)
    install(CODE "${code}")
    _bind_genex_to_target(gx_dest ${target} "${inst_DESTINATION}")
    if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.20")
        _bind_genex_to_target(gx_rename ${target} "${inst_RENAME}")
    else()
        # Note: CMake 3.20 is required for using generator expresssions in install(RENAME).
        # if we are older than that, just treat RENAME as a plain value.
        set(gx_rename "${inst_RENAME}")
    endif()
    # Wrap the filename to install with the same condition used to generate it. If the condition
    # is not met, then the FILES list will be empty, and nothing will be installed.
    install(FILES "$<${gx_cond}:${inst_tmp}>"
            DESTINATION ${gx_dest}
            RENAME ${gx_rename}
            ${inst_UNPARSED_ARGUMENTS})
endfunction()

# Generates the actual content of a .pc file.
function(_generate_pkg_config_content out)
    cmake_parse_arguments(PARSE_ARGV 1 ARG "" "PREFIX;NAME;VERSION;DESCRIPTION;GENEX_TARGET;LIBDIR" "")
    if(ARG_UNPARSED_ARGUMENTS)
        message(FATAL_ERROR "Unknown arguments: ${ARG_UNPARSED_ARGUMENTS}")
    endif()
    set(content)
    string(APPEND content
        "# pkg-config .pc file generated by CMake ${CMAKE_VERSION} for ${ARG_NAME}-${ARG_VERSION}. DO NOT EDIT!\n"
        "prefix=${ARG_PREFIX}\n"
        "exec_prefix=\${prefix}\n"
        "libdir=\${exec_prefix}/${gx_libdir}\n"
        "\n"
        "Name: ${ARG_NAME}\n"
        "Description: ${ARG_DESCRIPTION}\n"
        "Version: ${ARG_VERSION}"
        )
    # Add Requires:
    set(requires_joiner "\nRequires: ")
    set(gx_requires $<GENEX_EVAL:$<TARGET_PROPERTY:pkg_config_REQUIRES>>)
    set(has_requires $<NOT:$<STREQUAL:,${gx_requires}>>)
    string(APPEND content "$<${has_requires}:${requires_joiner}$<JOIN:${gx_requires},${requires_joiner}>>\n")
    string(APPEND content "\n")
    # Add "Libs:"
    set(libs)
    # Link options:
    set(gx_libs
        $<IF:$<STREQUAL:$<TARGET_PROPERTY:TYPE>,STATIC_LIBRARY>,
            # If linking static, emit the full path to the static library file
            \${libdir}/$<TARGET_FILE_NAME:${target}>,
            # Otherwise, link to the dynamic library namelink
            -L\${libdir} -l$<TARGET_PROPERTY:OUTPUT_NAME>>
        $<GENEX_EVAL:$<TARGET_PROPERTY:pkg_config_LIBS>>
        $<REMOVE_DUPLICATES:$<TARGET_PROPERTY:INTERFACE_LINK_OPTIONS>>
        )

    # XXX: Could we define a genex that can transform the INTERFACE_LINK_LIBRARIES to a list of
    #      pkg-config-compatible "-l"-flags? That would remove the need to populate pkg_config_LIBS
    #      manually, and instead rely on target properties to handle transitive dependencies.
    string(APPEND libs "$<JOIN:${gx_libs}, >")

    # Cflags:
    set(cflags)
    set(gx_flags
        $<REMOVE_DUPLICATES:$<GENEX_EVAL:$<TARGET_PROPERTY:pkg_config_CFLAGS>>>
        $<REMOVE_DUPLICATES:$<TARGET_PROPERTY:INTERFACE_COMPILE_OPTIONS>>
        )
    string(APPEND cflags "$<JOIN:${gx_flags}, >")
    # Definitions:
    set(gx_defs $<REMOVE_DUPLICATES:$<TARGET_PROPERTY:INTERFACE_COMPILE_DEFINITIONS>>)
    set(has_defs $<NOT:$<STREQUAL:,${gx_defs}>>)
    set(def_joiner " -D")
    string(APPEND cflags $<${has_defs}:${def_joiner}$<JOIN:${gx_defs},${def_joiner}>>)
    # Includes:
    set(gx_inc $<GENEX_EVAL:$<TARGET_PROPERTY:pkg_config_INCLUDE_DIRECTORIES>>)
    set(gx_inc "$<REMOVE_DUPLICATES:${gx_inc}>")
    set(gx_abs_inc "$<FILTER:${gx_inc},INCLUDE,^/>")
    set(gx_rel_inc "$<FILTER:${gx_inc},EXCLUDE,^/>")
    set(has_abs_inc $<NOT:$<STREQUAL:,${gx_abs_inc}>>)
    set(has_rel_inc $<NOT:$<STREQUAL:,${gx_rel_inc}>>)
    string(APPEND cflags $<${has_rel_inc}: " -I\${prefix}/"
                            $<JOIN:${gx_rel_inc}, " -I\${prefix}/" >>
                         $<${has_abs_inc}: " -I"
                            $<JOIN:${gx_abs_inc}, " -I" >>)
    string(APPEND content "Libs: ${libs}\n")
    string(APPEND content "Cflags: ${cflags}\n")
    set("${out}" "${content}" PARENT_SCOPE)
endfunction()