Repository: conan-io/conan

Issue:
[bug] `tools.build:defines` is broken in multiconfig setting
### Describe the bug

`tools.build:defines` is broken in multiconfig setting, e.g. with MSVC. The quoting in the generator expression is not correct in `blocks.py`.

### How to reproduce it

- Set `tools.build:defines=['A=1', 'B=2']`
- Observe how the generated toolchain file or afterwards generated vxproj by CMake does not contain the right definitions.

EDIT: See PR #15924 for more info

Discussion:
CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/conan-io/conan?pullRequest=15924) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/conan-io/conan?pullRequest=15924) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/conan-io/conan?pullRequest=15924) it.</sub>
memsharded: Thanks for the feedback.

I have added some checks in ``test_cmake_toolchain_cxxflags_multi_config`` test in https://github.com/conan-io/conan/pull/15924/commits/3000b886ba86aa5fc1fd9217fcd35d22d28f1e88

The overlook in that test was adding only 1 ``define``, while the changes are not correctly handling more than 1 ``define``. 
The test will make the PR fail, at least locally, the test was not passing with the current proposed changes.
memsharded: Uhmmm, there might be something there still not right.
I have tested with the previous:
```
add_compile_definitions($<$<CONFIG:{{config}}>:{% for define in defines %}" {{ define }}"{% endfor %}>)
```
and the test now passes with 2 values. 

We would need a failing test first, to make sure we are covering and correctly fixing it.
memsharded: I identified one issue with the test: the validation of the output was not fully correct.
I introduced a more detailed check, validating `` assert 'DEFINE conan_test_answer=42!' in c.out``, one definition per line, and then it started to fail.

I was checking ``add_compile_definitions()`` and I agree there is something weird there, it seem CMake was processing it wrong, it might be a bug in CMake. Then I realize that ``add_compile_definitions()`` is additive, it can be called many times, and then I changed the template to call it once per ``define``, and it seems to be working now, please check.
stevenwdv: @memsharded Thanks for adding the tests. I think the change introduces an issue, though. The value should be quoted: `add_compile_definitions("$<$<CONFIG:{{config}}>:{{ define }}>")`, maybe add a test with spaces in the define as well to catch this?
Apparently what went wrong with the previous approach is that there were no spaces between the quotes:
```cmake
add_compile_definitions("$<$<CONFIG:Release>:ABC=123>""$<$<CONFIG:Release>:DEF=456>")
```
should have been
```cmake
add_compile_definitions("$<$<CONFIG:Release>:ABC=123>" "$<$<CONFIG:Release>:DEF=456>")
```
which does work.
memsharded: Makes sense, thanks for the investigation and feedback.

I have pushed a fix for the broken test, quoting the defines, I think it should be ok now, please check.
I have kept the 1 define per ``add_compile_definitions()``, it seems more robust and even reads slightly cleaner in my opinion, compared with the quoted strings separated by spaces, I hope it is ok.

Patch:
diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py
index 36f725ae7..27a9c2260 100644
--- a/conan/tools/cmake/toolchain/blocks.py
+++ b/conan/tools/cmake/toolchain/blocks.py
@@ -154,7 +154,7 @@ class VSDebuggerEnvironment(Block):
 
         if not config_dict:
             return None
-        
+
         vs_debugger_path = ""
         for config, value in config_dict.items():
             vs_debugger_path += f"$<$<CONFIG:{config}>:{value}>"
@@ -613,7 +613,9 @@ class ExtraFlagsBlock(Block):
         {% endif %}
         {% if defines %}
         {% if config %}
-        add_compile_definitions($<$<CONFIG:{{config}}>:{% for define in defines %}" {{ define }}"{% endfor %}>)
+        {% for define in defines %}
+        add_compile_definitions($<$<CONFIG:{{config}}>:"{{ define }}">)
+        {% endfor %}
         {% else %}
         add_compile_definitions({% for define in defines %} "{{ define }}"{% endfor %})
         {% endif %}
diff --git a/conans/client/loader.py b/conans/client/loader.py
index 24146dfed..65f0c61bd 100644
--- a/conans/client/loader.py
+++ b/conans/client/loader.py
@@ -323,7 +323,7 @@ def _load_python_file(conan_file_path):
         raise NotFoundException("%s not found!" % conan_file_path)
 
     def new_print(*args, **kwargs):  # Make sure that all user python files print() goes to stderr
-        kwargs['file'] = sys.stderr
+        kwargs.setdefault("file", sys.stderr)
         print(*args, **kwargs)
 
     module_id = str(uuid.uuid1())


Test patch:
diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
index 497cfe018..2463c2f97 100644
--- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -1833,7 +1833,7 @@ def test_cmake_toolchain_cxxflags_multi_config():
     profile_release = textwrap.dedent(r"""
         include(default)
         [conf]
-        tools.build:defines=["answer=42"]
+        tools.build:defines=["conan_test_answer=42", "conan_test_other=24"]
         tools.build:cxxflags=["/Zc:__cplusplus"]
         """)
     profile_debug = textwrap.dedent(r"""
@@ -1841,7 +1841,7 @@ def test_cmake_toolchain_cxxflags_multi_config():
         [settings]
         build_type=Debug
         [conf]
-        tools.build:defines=["answer=123"]
+        tools.build:defines=["conan_test_answer=123"]
         tools.build:cxxflags=["/W4"]
         """)
 
@@ -1868,10 +1868,13 @@ def test_cmake_toolchain_cxxflags_multi_config():
         #include <stdio.h>
 
         #define STR(x)   #x
-        #define SHOW_DEFINE(x) printf("%s=%s\n", #x, STR(x))
+        #define SHOW_DEFINE(x) printf("DEFINE %s=%s!\n", #x, STR(x))
 
         int main() {
-            SHOW_DEFINE(answer);
+            SHOW_DEFINE(conan_test_answer);
+            #ifdef conan_test_other
+            SHOW_DEFINE(conan_test_other);
+            #endif
             char a = 123L;  // to trigger warnings
 
             #if __cplusplus
@@ -1902,9 +1905,11 @@ def test_cmake_toolchain_cxxflags_multi_config():
         assert "warning C4189" in c.out
 
     c.run_command(r"build\Release\example.exe")
-    assert 'answer=42' in c.out
+    assert 'DEFINE conan_test_answer=42!' in c.out
+    assert 'DEFINE conan_test_other=24!' in c.out
     assert "CPLUSPLUS: __cplusplus20" in c.out
 
     c.run_command(r"build\Debug\example.exe")
-    assert 'answer=123' in c.out
+    assert 'DEFINE conan_test_answer=123' in c.out
+    assert 'other=' not in c.out
     assert "CPLUSPLUS: __cplusplus19" in c.out
diff --git a/conans/test/unittests/model/conanfile_test.py b/conans/test/unittests/model/conanfile_test.py
index 1ec20ae6a..6d1be5d91 100644
--- a/conans/test/unittests/model/conanfile_test.py
+++ b/conans/test/unittests/model/conanfile_test.py
@@ -1,3 +1,4 @@
+import textwrap
 import unittest
 
 from conans.model.conan_file import ConanFile
@@ -40,12 +41,30 @@ class Pkg(ConanFile):
 
     def test_conanfile_new_print(self):
         client = TestClient()
-        conanfile = """from conan import ConanFile
-import sys
-class Pkg(ConanFile):
-    def source(self):
-        print("Test", file=sys.stderr)
-        print("Test")
-"""
+        conanfile = textwrap.dedent("""
+            from conan import ConanFile
+            import sys
+            class Pkg(ConanFile):
+                def source(self):
+                    print("Test1", file=sys.stderr)
+                    print("Test2")
+            """)
+        client.save({"conanfile.py": conanfile})
+        client.run("source .")
+        assert "Test1" in client.stderr
+        assert "Test2" in client.stderr
+        assert "" == client.stdout
+
+    def test_conanfile_new_print_save(self):
+        client = TestClient()
+        conanfile = textwrap.dedent("""
+            from conan import ConanFile
+            import sys
+            class Pkg(ConanFile):
+                def source(self):
+                    with open("myfile.txt", "w") as f:
+                        print("Test1", file=f)
+            """)
         client.save({"conanfile.py": conanfile})
-        client.run("source .")
\ No newline at end of file
+        client.run("source .")
+        assert "Test1" in client.load("myfile.txt")