summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/markdown/Snippets-module.md111
-rw-r--r--docs/markdown/snippets/symbol_visibility_header.md10
-rw-r--r--docs/sitemap.txt1
-rw-r--r--docs/theme/extra/templates/navbar_links.html1
-rw-r--r--docs/yaml/functions/_build_target_base.yaml4
-rw-r--r--mesonbuild/interpreter/primitives/string.py4
-rw-r--r--mesonbuild/modules/snippets.py101
-rw-r--r--mesonbuild/utils/universal.py3
-rwxr-xr-xrun_mypy.py1
-rwxr-xr-xrun_project_tests.py9
-rw-r--r--test cases/snippets/1 symbol visibility header/main-static-only.c3
-rw-r--r--test cases/snippets/1 symbol visibility header/main.c3
-rw-r--r--test cases/snippets/1 symbol visibility header/meson.build13
-rw-r--r--test cases/snippets/1 symbol visibility header/subprojects/sub/meson.build5
-rw-r--r--test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib-static-only.c3
-rw-r--r--test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib-static-only.h3
-rw-r--r--test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib.c3
-rw-r--r--test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib.h3
-rw-r--r--test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/meson.build39
-rw-r--r--test cases/snippets/1 symbol visibility header/test.json15
20 files changed, 328 insertions, 7 deletions
diff --git a/docs/markdown/Snippets-module.md b/docs/markdown/Snippets-module.md
new file mode 100644
index 000000000..15d5e9f74
--- /dev/null
+++ b/docs/markdown/Snippets-module.md
@@ -0,0 +1,111 @@
+---
+short-description: Code snippets module
+...
+
+# Snippets module
+
+*(new in 1.10.0)*
+
+This module provides helpers to generate commonly useful code snippets.
+
+## Functions
+
+### symbol_visibility_header()
+
+```meson
+snippets.symbol_visibility_header(header_name,
+ namespace: str
+ api: str
+ compilation: str
+ static_compilation: str
+ static_only: bool
+)
+```
+
+Generate a header file that defines macros to be used to mark all public APIs
+of a library. Depending on the platform, this will typically use
+`__declspec(dllexport)`, `__declspec(dllimport)` or
+`__attribute__((visibility("default")))`. It is compatible with C, C++,
+ObjC and ObjC++ languages. The content of the header is static regardless
+of the compiler used.
+
+The first positional argument is the name of the header file to be generated.
+It also takes the following keyword arguments:
+
+- `namespace`: Prefix for generated macros, defaults to the current project name.
+ It will be converted to upper case with all non-alphanumeric characters replaced
+ by an underscore `_`. It is only used for `api`, `compilation` and
+ `static_compilation` default values.
+- `api`: Name of the macro used to mark public APIs. Defaults to `<NAMESPACE>_API`.
+- `compilation`: Name of a macro defined only when compiling the library.
+ Defaults to `<NAMESPACE>_COMPILATION`.
+- `static_compilation`: Name of a macro defined only when compiling or using
+ static library. Defaults to `<NAMESPACE>_STATIC_COMPILATION`.
+- `static_only`: If set to true, `<NAMESPACE>_STATIC_COMPILATION` is defined
+ inside the generated header. In that case the header can only be used for
+ building a static library. By default it is `true` when `default_library=static`,
+ and `false` otherwise. [See below for more information](#static_library)
+
+Projects that define multiple shared libraries should typically have
+one header per library, with a different namespace.
+
+The generated header file should be installed using `install_headers()`.
+
+`meson.build`:
+```meson
+project('mylib', 'c')
+subdir('mylib')
+```
+
+`mylib/meson.build`:
+```meson
+snippets = import('snippets')
+apiheader = snippets.symbol_visibility_header('apiconfig.h')
+install_headers(apiheader, 'lib.h', subdir: 'mylib')
+lib = library('mylib', 'lib.c',
+ gnu_symbol_visibility: 'hidden',
+ c_args: ['-DMYLIB_COMPILATION'],
+)
+```
+
+`mylib/lib.h`
+```c
+#include <mylib/apiconfig.h>
+MYLIB_API int do_stuff();
+```
+
+`mylib/lib.c`
+```c
+#include "lib.h"
+int do_stuff() {
+ return 0;
+}
+```
+
+#### Static library
+
+When building both static and shared libraries on Windows (`default_library=both`),
+`-D<NAMESPACE>_STATIC_COMPILATION` must be defined only for the static library,
+using `c_static_args`. This causes Meson to compile the library twice.
+
+```meson
+if host_system == 'windows'
+ static_arg = ['-DMYLIB_STATIC_COMPILATION']
+else
+ static_arg = []
+endif
+lib = library('mylib', 'lib.c',
+ gnu_symbol_visibility: 'hidden',
+ c_args: ['-DMYLIB_COMPILATION'],
+ c_static_args: static_arg
+)
+```
+
+`-D<NAMESPACE>_STATIC_COMPILATION` C-flag must be defined when compiling
+applications that use the library API. It typically should be defined in
+`declare_dependency(..., compile_args: [])` and
+`pkgconfig.generate(..., extra_cflags: [])`.
+
+Note that when building both shared and static libraries on Windows,
+applications cannot currently rely on `pkg-config` to define this macro.
+See https://github.com/mesonbuild/meson/pull/14829.
diff --git a/docs/markdown/snippets/symbol_visibility_header.md b/docs/markdown/snippets/symbol_visibility_header.md
new file mode 100644
index 000000000..75aae786d
--- /dev/null
+++ b/docs/markdown/snippets/symbol_visibility_header.md
@@ -0,0 +1,10 @@
+## New method to handle GNU and Windows symbol visibility for C/C++/ObjC/ObjC++
+
+Defining public API of a cross platform C/C++/ObjC/ObjC++ library is often
+painful and requires copying macro snippets into every projects, typically using
+`__declspec(dllexport)`, `__declspec(dllimport)` or
+`__attribute__((visibility("default")))`.
+
+Meson can now generate a header file that defines exactly what's needed for
+all supported platforms:
+[`snippets.symbol_visibility_header()`](Snippets-module.md#symbol_visibility_header).
diff --git a/docs/sitemap.txt b/docs/sitemap.txt
index b3967a27f..fa4768746 100644
--- a/docs/sitemap.txt
+++ b/docs/sitemap.txt
@@ -56,6 +56,7 @@ index.md
Qt6-module.md
Rust-module.md
Simd-module.md
+ Snippets-module.md
SourceSet-module.md
Windows-module.md
i18n-module.md
diff --git a/docs/theme/extra/templates/navbar_links.html b/docs/theme/extra/templates/navbar_links.html
index f16489896..ac4a6fe10 100644
--- a/docs/theme/extra/templates/navbar_links.html
+++ b/docs/theme/extra/templates/navbar_links.html
@@ -26,6 +26,7 @@
("Qt6-module.html","Qt6"), \
("Rust-module.html","Rust"), \
("Simd-module.html","Simd"), \
+ ("Snippets-module.html","Snippets"), \
("SourceSet-module.html","SourceSet"), \
("Wayland-module.html","Wayland"), \
("Windows-module.html","Windows")]:
diff --git a/docs/yaml/functions/_build_target_base.yaml b/docs/yaml/functions/_build_target_base.yaml
index e0fd8b839..4cd91affe 100644
--- a/docs/yaml/functions/_build_target_base.yaml
+++ b/docs/yaml/functions/_build_target_base.yaml
@@ -255,7 +255,9 @@ kwargs:
`default`, `internal`, `hidden`, `protected` or `inlineshidden`, which
is the same as `hidden` but also includes things like C++ implicit
constructors as specified in the GCC manual. Ignored on compilers that
- do not support GNU visibility arguments.
+ do not support GNU visibility arguments. See also
+ [`snippets.symbol_visibility_header()`](Snippets-module.md#symbol_visibility_header)
+ method to help with defining public API.
d_import_dirs:
type: array[inc | str]
diff --git a/mesonbuild/interpreter/primitives/string.py b/mesonbuild/interpreter/primitives/string.py
index 49dd71660..df10c568d 100644
--- a/mesonbuild/interpreter/primitives/string.py
+++ b/mesonbuild/interpreter/primitives/string.py
@@ -7,7 +7,7 @@ import os
import typing as T
-from ...mesonlib import version_compare, version_compare_many
+from ...mesonlib import version_compare, version_compare_many, underscorify
from ...interpreterbase import (
InterpreterObject,
MesonOperator,
@@ -151,7 +151,7 @@ class StringHolder(ObjectHolder[str]):
@noPosargs
@InterpreterObject.method('underscorify')
def underscorify_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str:
- return re.sub(r'[^a-zA-Z0-9]', '_', self.held_object)
+ return underscorify(self.held_object)
@noKwargs
@InterpreterObject.method('version_compare')
diff --git a/mesonbuild/modules/snippets.py b/mesonbuild/modules/snippets.py
new file mode 100644
index 000000000..f93a754a8
--- /dev/null
+++ b/mesonbuild/modules/snippets.py
@@ -0,0 +1,101 @@
+# Copyright 2025 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+import textwrap
+import typing as T
+
+from pathlib import Path
+
+from . import NewExtensionModule, ModuleInfo
+from ..interpreterbase import KwargInfo, typed_kwargs, typed_pos_args
+from ..interpreter.type_checking import NoneType
+from .. import mesonlib
+
+if T.TYPE_CHECKING:
+ from typing_extensions import TypedDict
+ from . import ModuleState
+
+ class SymbolVisibilityHeaderKW(TypedDict):
+ namespace: T.Optional[str]
+ api: T.Optional[str]
+ compilation: T.Optional[str]
+ static_compilation: T.Optional[str]
+ static_only: bool
+
+
+class SnippetsModule(NewExtensionModule):
+
+ INFO = ModuleInfo('snippets', '1.10.0')
+
+ def __init__(self) -> None:
+ super().__init__()
+ self.methods.update({
+ 'symbol_visibility_header': self.symbol_visibility_header_method,
+ })
+
+ @typed_kwargs('snippets.symbol_visibility_header',
+ KwargInfo('namespace', (str, NoneType)),
+ KwargInfo('api', (str, NoneType)),
+ KwargInfo('compilation', (str, NoneType)),
+ KwargInfo('static_compilation', (str, NoneType)),
+ KwargInfo('static_only', (bool, NoneType)))
+ @typed_pos_args('snippets.symbol_visibility_header', str)
+ def symbol_visibility_header_method(self, state: ModuleState, args: T.Tuple[str], kwargs: 'SymbolVisibilityHeaderKW') -> mesonlib.File:
+ header_name = args[0]
+ namespace = kwargs['namespace'] or state.project_name
+ namespace = mesonlib.underscorify(namespace).upper()
+ if namespace[0].isdigit():
+ namespace = f'_{namespace}'
+ api = kwargs['api'] or f'{namespace}_API'
+ compilation = kwargs['compilation'] or f'{namespace}_COMPILATION'
+ static_compilation = kwargs['static_compilation'] or f'{namespace}_STATIC_COMPILATION'
+ static_only = kwargs['static_only']
+ if static_only is None:
+ default_library = state.get_option('default_library')
+ static_only = default_library == 'static'
+ content = textwrap.dedent('''\
+ // SPDX-license-identifier: 0BSD OR CC0-1.0 OR WTFPL OR Apache-2.0 OR LGPL-2.0-or-later
+ #pragma once
+ ''')
+ if static_only:
+ content += textwrap.dedent(f'''
+ #ifndef {static_compilation}
+ # define {static_compilation}
+ #endif /* {static_compilation} */
+ ''')
+ content += textwrap.dedent(f'''
+ #if (defined(_WIN32) || defined(__CYGWIN__)) && !defined({static_compilation})
+ # define {api}_EXPORT __declspec(dllexport)
+ # define {api}_IMPORT __declspec(dllimport)
+ #elif __GNUC__ >= 4
+ # define {api}_EXPORT __attribute__((visibility("default")))
+ # define {api}_IMPORT
+ #else
+ # define {api}_EXPORT
+ # define {api}_IMPORT
+ #endif
+
+ #ifdef {compilation}
+ # define {api} {api}_EXPORT extern
+ #else
+ # define {api} {api}_IMPORT extern
+ #endif
+ ''')
+ header_path = Path(state.environment.get_build_dir(), state.subdir, header_name)
+ header_path.write_text(content, encoding='utf-8')
+ return mesonlib.File.from_built_file(state.subdir, header_name)
+
+def initialize(*args: T.Any, **kwargs: T.Any) -> SnippetsModule:
+ return SnippetsModule()
diff --git a/mesonbuild/utils/universal.py b/mesonbuild/utils/universal.py
index e117f862f..d8e80068f 100644
--- a/mesonbuild/utils/universal.py
+++ b/mesonbuild/utils/universal.py
@@ -152,6 +152,7 @@ __all__ = [
'set_meson_command',
'split_args',
'stringlistify',
+ 'underscorify',
'substitute_values',
'substring_is_in_list',
'typeslistify',
@@ -1692,6 +1693,8 @@ def typeslistify(item: 'T.Union[_T, T.Sequence[_T]]',
def stringlistify(item: T.Union[T.Any, T.Sequence[T.Any]]) -> T.List[str]:
return typeslistify(item, str)
+def underscorify(item: str) -> str:
+ return re.sub(r'[^a-zA-Z0-9]', '_', item)
def expand_arguments(args: T.Iterable[str]) -> T.Optional[T.List[str]]:
expended_args: T.List[str] = []
diff --git a/run_mypy.py b/run_mypy.py
index 08f079aa8..eb004c381 100755
--- a/run_mypy.py
+++ b/run_mypy.py
@@ -70,6 +70,7 @@ modules = [
'mesonbuild/modules/qt6.py',
'mesonbuild/modules/rust.py',
'mesonbuild/modules/simd.py',
+ 'mesonbuild/modules/snippets.py',
'mesonbuild/modules/sourceset.py',
'mesonbuild/modules/wayland.py',
'mesonbuild/modules/windows.py',
diff --git a/run_project_tests.py b/run_project_tests.py
index 7067bfa0b..c92df2b9f 100755
--- a/run_project_tests.py
+++ b/run_project_tests.py
@@ -79,7 +79,7 @@ ALL_TESTS = ['cmake', 'common', 'native', 'warning-meson', 'failing-meson', 'fai
'keyval', 'platform-osx', 'platform-windows', 'platform-linux', 'platform-android',
'java', 'C#', 'vala', 'cython', 'rust', 'd', 'objective c', 'objective c++',
'fortran', 'swift', 'cuda', 'python3', 'python', 'fpga', 'frameworks', 'nasm', 'wasm', 'wayland',
- 'format',
+ 'format', 'snippets',
]
@@ -355,15 +355,15 @@ def setup_commands(optbackend: str) -> None:
def platform_fix_name(fname: str, canonical_compiler: str, env: environment.Environment) -> str:
if '?lib' in fname:
if env.machines.host.is_windows() and canonical_compiler == 'msvc':
- fname = re.sub(r'lib/\?lib(.*)\.', r'bin/\1.', fname)
+ fname = re.sub(r'lib/\?lib(.*)$', r'bin/\1', fname)
fname = re.sub(r'/\?lib/', r'/bin/', fname)
elif env.machines.host.is_windows():
- fname = re.sub(r'lib/\?lib(.*)\.', r'bin/lib\1.', fname)
+ fname = re.sub(r'lib/\?lib(.*)$', r'bin/lib\1', fname)
fname = re.sub(r'\?lib(.*)\.dll$', r'lib\1.dll', fname)
fname = re.sub(r'/\?lib/', r'/bin/', fname)
elif env.machines.host.is_cygwin():
fname = re.sub(r'lib/\?lib(.*)\.so$', r'bin/cyg\1.dll', fname)
- fname = re.sub(r'lib/\?lib(.*)\.', r'bin/cyg\1.', fname)
+ fname = re.sub(r'lib/\?lib(.*)$', r'bin/cyg\1', fname)
fname = re.sub(r'\?lib(.*)\.dll$', r'cyg\1.dll', fname)
fname = re.sub(r'/\?lib/', r'/bin/', fname)
else:
@@ -1145,6 +1145,7 @@ def detect_tests_to_run(only: T.Dict[str, T.List[str]], use_tmp: bool) -> T.List
TestCategory('wasm', 'wasm', shutil.which('emcc') is None or backend is not Backend.ninja),
TestCategory('wayland', 'wayland', should_skip_wayland()),
TestCategory('format', 'format'),
+ TestCategory('snippets', 'snippets'),
]
categories = [t.category for t in all_tests]
diff --git a/test cases/snippets/1 symbol visibility header/main-static-only.c b/test cases/snippets/1 symbol visibility header/main-static-only.c
new file mode 100644
index 000000000..26f5b060f
--- /dev/null
+++ b/test cases/snippets/1 symbol visibility header/main-static-only.c
@@ -0,0 +1,3 @@
+#include <mylib/lib-static-only.h>
+
+int main(void) { return do_stuff(); }
diff --git a/test cases/snippets/1 symbol visibility header/main.c b/test cases/snippets/1 symbol visibility header/main.c
new file mode 100644
index 000000000..1d32f622e
--- /dev/null
+++ b/test cases/snippets/1 symbol visibility header/main.c
@@ -0,0 +1,3 @@
+#include <mylib/lib.h>
+
+int main(void) { return do_stuff(); }
diff --git a/test cases/snippets/1 symbol visibility header/meson.build b/test cases/snippets/1 symbol visibility header/meson.build
new file mode 100644
index 000000000..9a6c27ae3
--- /dev/null
+++ b/test cases/snippets/1 symbol visibility header/meson.build
@@ -0,0 +1,13 @@
+project('symbol visibility header', 'c')
+
+sta_dep = dependency('mylib-sta', fallback: 'sub')
+exe = executable('exe-sta', 'main.c', dependencies: sta_dep)
+test('test-sta', exe)
+
+sha_dep = dependency('mylib-sha', fallback: 'sub')
+exe = executable('exe-sha', 'main.c', dependencies: sha_dep)
+test('test-sha', exe)
+
+static_only_dep = dependency('static-only', fallback: 'sub')
+exe = executable('exe-static-only', 'main-static-only.c', dependencies: static_only_dep)
+test('test-static-only', exe)
diff --git a/test cases/snippets/1 symbol visibility header/subprojects/sub/meson.build b/test cases/snippets/1 symbol visibility header/subprojects/sub/meson.build
new file mode 100644
index 000000000..83b797019
--- /dev/null
+++ b/test cases/snippets/1 symbol visibility header/subprojects/sub/meson.build
@@ -0,0 +1,5 @@
+project('my lib', 'c')
+
+pkg = import('pkgconfig')
+
+subdir('mylib')
diff --git a/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib-static-only.c b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib-static-only.c
new file mode 100644
index 000000000..b7662cd94
--- /dev/null
+++ b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib-static-only.c
@@ -0,0 +1,3 @@
+#include "lib-static-only.h"
+
+int do_stuff(void) { return 0; }
diff --git a/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib-static-only.h b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib-static-only.h
new file mode 100644
index 000000000..ebe4524a3
--- /dev/null
+++ b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib-static-only.h
@@ -0,0 +1,3 @@
+#include <mylib/apiconfig-static-only.h>
+
+MY_LIB_API int do_stuff(void);
diff --git a/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib.c b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib.c
new file mode 100644
index 000000000..117172070
--- /dev/null
+++ b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib.c
@@ -0,0 +1,3 @@
+#include "lib.h"
+
+int do_stuff(void) { return 0; }
diff --git a/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib.h b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib.h
new file mode 100644
index 000000000..bea53afb3
--- /dev/null
+++ b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/lib.h
@@ -0,0 +1,3 @@
+#include <mylib/apiconfig.h>
+
+MY_LIB_API int do_stuff(void);
diff --git a/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/meson.build b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/meson.build
new file mode 100644
index 000000000..1e7b45e6b
--- /dev/null
+++ b/test cases/snippets/1 symbol visibility header/subprojects/sub/mylib/meson.build
@@ -0,0 +1,39 @@
+snippets = import('snippets')
+
+lib_incdir = include_directories('..')
+lib_args = ['-DMY_LIB_COMPILATION']
+lib_static_args = ['-DMY_LIB_STATIC_COMPILATION']
+
+h = snippets.symbol_visibility_header('apiconfig.h')
+install_headers(h, 'lib.h', subdir: 'mylib')
+mylib = both_libraries('mylib', 'lib.c',
+ include_directories: lib_incdir,
+ gnu_symbol_visibility: 'hidden',
+ c_args: lib_args,
+ c_static_args: lib_static_args,
+ install: true)
+mylib_sta_dep = declare_dependency(link_with: mylib.get_static_lib(),
+ include_directories: lib_incdir,
+ compile_args: lib_static_args)
+mylib_sha_dep = declare_dependency(link_with: mylib.get_shared_lib(),
+ include_directories: lib_incdir)
+meson.override_dependency('mylib-sta', mylib_sta_dep)
+meson.override_dependency('mylib-sha', mylib_sha_dep)
+pkg.generate(mylib,
+ extra_cflags: lib_static_args,
+)
+
+# When using static_only, we don't need lib_static_args because
+# MY_LIB_STATIC_COMPILATION gets defined in the generated header.
+h = snippets.symbol_visibility_header('apiconfig-static-only.h',
+ static_only: true)
+install_headers(h, 'lib-static-only.h', subdir: 'mylib')
+libstaticonly = static_library('static-only', 'lib-static-only.c',
+ include_directories: lib_incdir,
+ gnu_symbol_visibility: 'hidden',
+ c_args: lib_args,
+ install: true)
+static_only_dep = declare_dependency(link_with: libstaticonly,
+ include_directories: lib_incdir)
+meson.override_dependency('static-only', static_only_dep)
+pkg.generate(libstaticonly)
diff --git a/test cases/snippets/1 symbol visibility header/test.json b/test cases/snippets/1 symbol visibility header/test.json
new file mode 100644
index 000000000..0d0be4ec5
--- /dev/null
+++ b/test cases/snippets/1 symbol visibility header/test.json
@@ -0,0 +1,15 @@
+{
+ "installed": [
+ {"type": "file", "file": "usr/include/mylib/apiconfig-static-only.h"},
+ {"type": "file", "file": "usr/include/mylib/apiconfig.h"},
+ {"type": "file", "file": "usr/include/mylib/lib-static-only.h"},
+ {"type": "file", "file": "usr/include/mylib/lib.h"},
+ {"type": "file", "file": "usr/lib/libmylib.a"},
+ {"type": "expr", "file": "usr/lib/?libmylib?so"},
+ {"type": "implib", "file": "usr/lib/libmylib"},
+ {"type": "pdb", "file": "usr/bin/mylib"},
+ {"type": "file", "file": "usr/lib/libstatic-only.a"},
+ {"type": "file", "file": "usr/lib/pkgconfig/mylib.pc"},
+ {"type": "file", "file": "usr/lib/pkgconfig/static-only.pc"}
+ ]
+}