Repository: mesonbuild/meson

Issue:
Cmake module does not sanitize variable names when generating meson.build
**Describe the bug**
If a subproject that uses CMake names a library with something outside of alphanumeric and underscore characters, the meson module does not sanitize the name, resulting in a failed build.

For example if the `CMakeLists.txt` file contains:
```
...
add_library(grpc++
  src/cpp/client/insecure_credentials.cc
...
  src/cpp/codegen/codegen_init.cc
)
set_target_properties(grpc++ PROPERTIES
  VERSION ${gRPC_CPP_VERSION}
  SOVERSION ${gRPC_CPP_SOVERSION}
)
...
```

**To Reproduce**
Encountered this with gRPC:

```
$ cd subprojects
$ git submodule add https://github.com/grpc/grpc.git
$ git submodule sync --recursive
$ git submodule update --init --recursive -- grpc
$ meson out
$ ninja -C out
...

subprojects/grpc/meson.build:0:0: ERROR: Invalid variable name: cm_grpc++_dir

A full log can be found at /mnt/data/extra/achang/src/chillfs/out/meson-logs/meson-log.txt
FAILED: build.ninja
```

Here's a snippet of `meson.build` to include it:
```
cmake = import('cmake')
grpc_proj = cmake_native.subproject('grpc')
grpc_dep = grpc_proj.dependency('grpc_dep')
deps += grpc_dep
executable(
  'myprogram',
  dependencies : deps,
  install : true
)
```

**Expected behavior**
Can build the library and use it as a subproject.

**system parameters**
* Is this a [cross build](https://mesonbuild.com/Cross-compilation.html) or just a plain native build (for the same computer)? **plain native build**
* what operating system (e.g. MacOS Catalina, Windows 10, CentOS 8.0, Ubuntu 18.04, etc.): **Archlinux, but likely an issue everywhere**
* what Python version are you using? **3.8.1**
* what `meson --version`? **0.53.0**
* what `ninja --version` if it's a Ninja build? **1.9.0**



Discussion:
Might be fixed with #6213

Resolving patch:
diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py
--- a/mesonbuild/cmake/interpreter.py
+++ b/mesonbuild/cmake/interpreter.py
@@ -125,10 +125,13 @@
     'advapi32.lib'
 ]
 
-generated_target_name_prefix = 'cm_'
-
 transfer_dependencies_from = ['header_only']
 
+_cmake_name_regex = re.compile(r'[^_a-zA-Z0-9]')
+def _sanitize_cmake_name(name: str) -> str:
+    name = _cmake_name_regex.sub('_', name)
+    return 'cm_' + name
+
 class OutputTargetMap:
     rm_so_version = re.compile(r'(\.[0-9]+)+$')
 
@@ -234,8 +237,7 @@ def __init__(self, target: CMakeTarget, env: Environment):
         self.override_options = []
 
         # Convert the target name to a valid meson target name
-        self.name = self.name.replace('-', '_')
-        self.name = generated_target_name_prefix + self.name
+        self.name = _sanitize_cmake_name(self.name)
 
         for i in target.files:
             # Determine the meson language
@@ -593,8 +595,7 @@ def __init__(self, target: CMakeGeneratorTarget):
         self.depends = []
 
         # Convert the target name to a valid meson target name
-        self.name = self.name.replace('-', '_')
-        self.name = generated_target_name_prefix + self.name
+        self.name = _sanitize_cmake_name(self.name)
 
     def __repr__(self) -> str:
         return '<{}: {} {}>'.format(self.__class__.__name__, self.name, self.outputs)
@@ -1192,6 +1193,7 @@ def resolve_source(x: T.Any) -> T.Any:
 
             root_cb.lines += [assign(tgt_var, function('custom_target', [tgt.name], tgt_kwargs))]
             processed[tgt.name] = {'inc': None, 'src': None, 'dep': None, 'tgt': tgt_var, 'func': 'custom_target'}
+            name_map[tgt.cmake_name] = tgt.name
 
         # Now generate the target function calls
         for i in self.custom_targets:
@@ -1208,7 +1210,7 @@ def resolve_source(x: T.Any) -> T.Any:
     def target_info(self, target: str) -> T.Optional[T.Dict[str, str]]:
         # Try resolving the target name
         # start by checking if there is a 100% match (excluding the name prefix)
-        prx_tgt = generated_target_name_prefix + target
+        prx_tgt = _sanitize_cmake_name(target)
         if prx_tgt in self.generated_targets:
             return self.generated_targets[prx_tgt]
         # check if there exists a name mapping
@@ -1219,11 +1221,7 @@ def target_info(self, target: str) -> T.Optional[T.Dict[str, str]]:
         return None
 
     def target_list(self) -> T.List[str]:
-        prx_str = generated_target_name_prefix
-        prx_len = len(prx_str)
-        res = [x for x in self.generated_targets.keys()]
-        res = [x[prx_len:] if x.startswith(prx_str) else x for x in res]
-        return res
+        return list(self.internal_name_map.keys())
 
     def _object_lib_workaround(self) -> bool:
         return 'link' in self.linkers and self.backend_name.startswith('vs')


Test patch:
diff --git a/test cases/cmake/1 basic/meson.build b/test cases/cmake/1 basic/meson.build
--- a/test cases/cmake/1 basic/meson.build	
+++ b/test cases/cmake/1 basic/meson.build	
@@ -3,10 +3,10 @@ project('cmakeSubTest', ['c', 'cpp'])
 cm = import('cmake')
 
 sub_pro = cm.subproject('cmMod')
-sub_dep = sub_pro.dependency('cmModLib')
+sub_dep = sub_pro.dependency('cmModLib++')
 
-assert(sub_pro.target_list() == ['cmModLib'], 'There should be exactly one target')
-assert(sub_pro.target_type('cmModLib') == 'shared_library', 'Target type should be shared_library')
+assert(sub_pro.target_list() == ['cmModLib++'], 'There should be exactly one target')
+assert(sub_pro.target_type('cmModLib++') == 'shared_library', 'Target type should be shared_library')
 
 exe1 = executable('main', ['main.cpp'], dependencies: [sub_dep])
 test('test1', exe1)
diff --git a/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt
--- a/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt	
+++ b/test cases/cmake/1 basic/subprojects/cmMod/CMakeLists.txt	
@@ -7,6 +7,6 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
 
 add_definitions("-DDO_NOTHING_JUST_A_FLAG=1")
 
-add_library(cmModLib SHARED cmMod.cpp)
+add_library(cmModLib++ SHARED cmMod.cpp)
 include(GenerateExportHeader)
-generate_export_header(cmModLib)
+generate_export_header(cmModLib++)
diff --git a/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp b/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp
--- a/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp	
+++ b/test cases/cmake/1 basic/subprojects/cmMod/cmMod.hpp	
@@ -1,13 +1,14 @@
 #pragma once
 
+#include "cmmodlib++_export.h"
 #include <string>
-#include "cmmodlib_export.h"
 
-class CMMODLIB_EXPORT cmModClass {
-  private:
-    std::string str;
-  public:
-    cmModClass(std::string foo);
+class CMMODLIB___EXPORT cmModClass {
+private:
+  std::string str;
 
-    std::string getStr() const;
+public:
+  cmModClass(std::string foo);
+
+  std::string getStr() const;
 };