Repository: conan-io/conan
Pull request #17359 (2024-11-21 11:48:56)

Add `--output` option to `conan new` command
Changelog: Feature: Add `--output` option to `conan new` command
Docs: Omit

We found that it was usueful when creating a mock multiproject

Only thing is that we recently removed the conan new command tests, and there's no good place to add one now :(.

I can confirm that it works locally for me though


Patch:
diff --git a/conan/api/subapi/new.py b/conan/api/subapi/new.py
index b9732daea..8b3089a0c 100644
--- a/conan/api/subapi/new.py
+++ b/conan/api/subapi/new.py
@@ -93,9 +93,11 @@ class NewAPI:
     @staticmethod
     def render(template_files, definitions):
         result = {}
-        name = definitions.get("name", "Pkg")
+        name = definitions.get("name", "pkg")
         if isinstance(name, list):
             raise ConanException(f"name argument can't be multiple: {name}")
+        if name != name.lower():
+            raise ConanException(f"name argument must be lowercase: {name}")
         definitions["conan_version"] = __version__
 
         def ensure_list(key):
diff --git a/conan/cli/commands/new.py b/conan/cli/commands/new.py
index 18b3b0c96..ecc961589 100644
--- a/conan/cli/commands/new.py
+++ b/conan/cli/commands/new.py
@@ -24,6 +24,8 @@ def new(conan_api, parser, *args):
     parser.add_argument("-d", "--define", action="append",
                         help="Define a template argument as key=value, e.g., -d name=mypkg")
     parser.add_argument("-f", "--force", action='store_true', help="Overwrite file if it already exists")
+    parser.add_argument("-o", "--output", help="Output folder for the generated files",
+                        default=os.getcwd())
 
     args = parser.parse_args(*args)
     # Manually parsing the remainder
@@ -59,10 +61,10 @@ def new(conan_api, parser, *args):
 
     # Saving the resulting files
     output = ConanOutput()
-    cwd = os.getcwd()
+    output_folder = args.output
     # Making sure they don't overwrite existing files
     for f, v in sorted(template_files.items()):
-        path = os.path.join(cwd, f)
+        path = os.path.join(output_folder, f)
         if os.path.exists(path) and not args.force:
             raise ConanException(f"File '{f}' already exists, and --force not defined, aborting")
         save(path, v)
@@ -70,7 +72,7 @@ def new(conan_api, parser, *args):
 
     # copy non-templates
     for f, v in sorted(non_template_files.items()):
-        path = os.path.join(cwd, f)
+        path = os.path.join(output_folder, f)
         if os.path.exists(path) and not args.force:
             raise ConanException(f"File '{f}' already exists, and --force not defined, aborting")
         shutil.copy2(v, path)
diff --git a/conan/tools/cmake/cmakedeps2/cmakedeps.py b/conan/tools/cmake/cmakedeps2/cmakedeps.py
index 295a9b25b..d8cfbe548 100644
--- a/conan/tools/cmake/cmakedeps2/cmakedeps.py
+++ b/conan/tools/cmake/cmakedeps2/cmakedeps.py
@@ -188,9 +188,10 @@ class _PathGenerator:
                     build_dir = dep.package_folder
                 pkg_folder = build_dir.replace("\\", "/") if build_dir else None
                 if pkg_folder:
-                    config_file = ConfigTemplate2(self._cmakedeps, dep).filename
-                    if os.path.isfile(os.path.join(pkg_folder, config_file)):
-                        pkg_paths[pkg_name] = pkg_folder
+                    f = self._cmakedeps.get_cmake_filename(dep)
+                    for filename in (f"{f}-config.cmake", f"{f}Config.cmake"):
+                        if os.path.isfile(os.path.join(pkg_folder, filename)):
+                            pkg_paths[pkg_name] = pkg_folder
                 continue
 
             # If CMakeDeps generated, the folder is this one


Test patch:
diff --git a/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new_paths.py b/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new_paths.py
index f3c04e165..945ea9c18 100644
--- a/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new_paths.py
+++ b/test/functional/toolchains/cmake/cmakedeps/test_cmakedeps_new_paths.py
@@ -47,10 +47,12 @@ def test_cmake_generated(client):
 
 
 @pytest.mark.tool("cmake")
-def test_cmake_in_package(client):
+@pytest.mark.parametrize("lowercase", [False, True])
+def test_cmake_in_package(client, lowercase):
     c = client
     # same, but in-package
-    dep = textwrap.dedent("""
+    f = "dep-config" if lowercase else "depConfig"
+    dep = textwrap.dedent(f"""
         import os
         from conan import ConanFile
         from conan.tools.files import save
@@ -60,7 +62,7 @@ def test_cmake_in_package(client):
 
             def package(self):
                 content = 'message(STATUS "Hello from dep dep-Config.cmake!!!!!")'
-                save(self, os.path.join(self.package_folder, "cmake", "dep-config.cmake"), content)
+                save(self, os.path.join(self.package_folder, "cmake", "{f}.cmake"), content)
             def package_info(self):
                 self.cpp_info.set_property("cmake_find_mode", "none")
                 self.cpp_info.builddirs = ["cmake"]
diff --git a/test/integration/command/new_test.py b/test/integration/command/new_test.py
index 0dbb71dea..55d5103b8 100644
--- a/test/integration/command/new_test.py
+++ b/test/integration/command/new_test.py
@@ -11,7 +11,7 @@ from conans.util.files import save
 
 class TestNewCommand:
     def test_new_cmake_lib(self):
-        client = TestClient()
+        client = TestClient(light=True)
         client.run("new cmake_lib -d name=pkg -d version=1.3")
         conanfile = client.load("conanfile.py")
         assert "CMakeToolchain" in conanfile
@@ -25,7 +25,7 @@ class TestNewCommand:
         assert "pkg/1.3@myuser/testing" in client.out
 
     def test_new_cmake_exe(self):
-        client = TestClient()
+        client = TestClient(light=True)
         client.run("new cmake_exe -d name=pkg -d version=1.3")
         conanfile = client.load("conanfile.py")
         assert "CMakeToolchain" in conanfile
@@ -37,7 +37,7 @@ class TestNewCommand:
         assert "pkg/1.3@myuser/testing" in client.out
 
     def test_new_basic_template(self):
-        tc = TestClient()
+        tc = TestClient(light=True)
         tc.run("new basic")
         assert '# self.requires("zlib/1.2.13")' in tc.load("conanfile.py")
 
@@ -48,7 +48,7 @@ class TestNewCommand:
         assert 'name = "mygame"' in conanfile
 
     def test_new_defaults(self):
-        c = TestClient()
+        c = TestClient(light=True)
         for t in ("cmake_lib", "cmake_exe", "meson_lib", "meson_exe", "msbuild_lib", "msbuild_exe",
                   "bazel_lib", "bazel_exe", "autotools_lib", "autotools_exe"):
             c.run(f"new {t} -f")
@@ -66,7 +66,7 @@ class TestNewCommandUserTemplate:
 
     @pytest.mark.parametrize("folder", ("mytemplate", "sub/mytemplate"))
     def test_user_template(self, folder):
-        client = TestClient()
+        client = TestClient(light=True)
         template1 = textwrap.dedent("""
             class Conan(ConanFile):
                 name = "{{name}}"
@@ -83,7 +83,7 @@ class TestNewCommandUserTemplate:
 
     def test_user_template_abs(self):
         tmp_folder = temp_folder()
-        client = TestClient()
+        client = TestClient(light=True)
         template1 = textwrap.dedent("""
             class Conan(ConanFile):
                 name = "{{name}}"
@@ -94,13 +94,13 @@ class TestNewCommandUserTemplate:
         assert 'name = "hello"' in conanfile
 
     def test_user_template_filenames(self):
-        client = TestClient()
+        client = TestClient(light=True)
         save(os.path.join(client.cache_folder, "templates/command/new/mytemplate/{{name}}"), "Hi!")
         client.run(f"new mytemplate -d name=pkg.txt")
         assert "Hi!" == client.load("pkg.txt")
 
     def test_skip_files(self):
-        client = TestClient()
+        client = TestClient(light=True)
         template1 = textwrap.dedent("""
             class Conan(ConanFile):
                 name = "{{name}}"
@@ -121,7 +121,7 @@ class TestNewCommandUserTemplate:
     def test_template_image_files(self):
         """ problematic files that we dont want to render with Jinja, like PNG or other binaries,
         have to be explicitly excluded from render"""
-        client = TestClient()
+        client = TestClient(light=True)
         template_dir = "templates/command/new/t_dir"
         png = "$&(){}{}{{}{}"
         save(os.path.join(client.cache_folder, template_dir, "myimage.png"), png)
@@ -137,12 +137,12 @@ class TestNewCommandUserTemplate:
 
 class TestNewErrors:
     def test_template_errors(self):
-        client = TestClient()
+        client = TestClient(light=True)
         client.run("new mytemplate", assert_error=True)
         assert "ERROR: Template doesn't exist" in client.out
 
     def test_forced(self):
-        client = TestClient()
+        client = TestClient(light=True)
         client.run("new cmake_lib -d name=hello -d version=0.1")
         client.run("new cmake_lib -d name=hello -d version=0.1", assert_error=True)
         client.run("new cmake_lib -d name=bye -d version=0.2 --force")
@@ -151,10 +151,20 @@ class TestNewErrors:
         assert 'version = "0.2"' in conanfile
 
     def test_duplicated(self):
-        client = TestClient()
+        client = TestClient(light=True)
         client.run("new cmake_lib -d name=hello -d name=0.1", assert_error=True)
         assert "ERROR: name argument can't be multiple: ['hello', '0.1']" in client.out
 
         # It will create a list and assign to it, but it will not fail ugly
         client.run("new cmake_lib -d name=pkg -d version=0.1 -d version=0.2", assert_error=True)
         assert "ERROR: version argument can't be multiple: ['0.1', '0.2']" in client.out
+
+    def test_name_uppercase(self):
+        client = TestClient(light=True)
+        client.run("new cmake_lib -d name=Hello", assert_error=True)
+        assert "ERROR: name argument must be lowercase: Hello" in client.out
+
+    def test_new_change_folder(self):
+        client = TestClient(light=True)
+        client.run("new cmake_lib -d name=hello -d version=0.1 -o=myfolder")
+        assert os.path.exists(os.path.join(client.current_folder, "myfolder", "conanfile.py"))