1import os
2import re
3
4from jinja2 import Template
5
6from conans import __version__ as client_version
7from conans.client.cmd.new_ci import ci_get_files
8from conans.errors import ConanException
9from conans.model.ref import ConanFileReference, get_reference_fields
10from conans.util.files import load
11
12
13conanfile = """from conans import ConanFile, CMake, tools
14
15
16class {package_name}Conan(ConanFile):
17    name = "{name}"
18    version = "{version}"
19    license = "<Put the package license here>"
20    author = "<Put your name here> <And your email here>"
21    url = "<Package recipe repository url here, for issues about the package>"
22    description = "<Description of {package_name} here>"
23    topics = ("<Put some tag here>", "<here>", "<and here>")
24    settings = "os", "compiler", "build_type", "arch"
25    options = {{"shared": [True, False], "fPIC": [True, False]}}
26    default_options = {{"shared": False, "fPIC": True}}
27    generators = "cmake"
28
29    def config_options(self):
30        if self.settings.os == "Windows":
31            del self.options.fPIC
32
33    def source(self):
34        self.run("git clone https://github.com/conan-io/hello.git")
35        # This small hack might be useful to guarantee proper /MT /MD linkage
36        # in MSVC if the packaged project doesn't have variables to set it
37        # properly
38        tools.replace_in_file("hello/CMakeLists.txt", "PROJECT(HelloWorld)",
39                              '''PROJECT(HelloWorld)
40include(${{CMAKE_BINARY_DIR}}/conanbuildinfo.cmake)
41conan_basic_setup()''')
42
43    def build(self):
44        cmake = CMake(self)
45        cmake.configure(source_folder="hello")
46        cmake.build()
47
48        # Explicit way:
49        # self.run('cmake %s/hello %s'
50        #          % (self.source_folder, cmake.command_line))
51        # self.run("cmake --build . %s" % cmake.build_config)
52
53    def package(self):
54        self.copy("*.h", dst="include", src="hello")
55        self.copy("*hello.lib", dst="lib", keep_path=False)
56        self.copy("*.dll", dst="bin", keep_path=False)
57        self.copy("*.so", dst="lib", keep_path=False)
58        self.copy("*.dylib", dst="lib", keep_path=False)
59        self.copy("*.a", dst="lib", keep_path=False)
60
61    def package_info(self):
62        self.cpp_info.libs = ["hello"]
63
64"""
65
66conanfile_bare = """from conans import ConanFile, tools
67
68
69class {package_name}Conan(ConanFile):
70    name = "{name}"
71    version = "{version}"
72    settings = "os", "compiler", "build_type", "arch"
73    description = "<Description of {package_name} here>"
74    url = "None"
75    license = "None"
76    author = "None"
77    topics = None
78
79    def package(self):
80        self.copy("*")
81
82    def package_info(self):
83        self.cpp_info.libs = tools.collect_libs(self)
84"""
85
86conanfile_sources = """from conans import ConanFile, CMake
87
88
89class {package_name}Conan(ConanFile):
90    name = "{name}"
91    version = "{version}"
92    license = "<Put the package license here>"
93    author = "<Put your name here> <And your email here>"
94    url = "<Package recipe repository url here, for issues about the package>"
95    description = "<Description of {package_name} here>"
96    topics = ("<Put some tag here>", "<here>", "<and here>")
97    settings = "os", "compiler", "build_type", "arch"
98    options = {{"shared": [True, False], "fPIC": [True, False]}}
99    default_options = {{"shared": False, "fPIC": True}}
100    generators = "cmake"
101    exports_sources = "src/*"
102{configure}
103    def config_options(self):
104        if self.settings.os == "Windows":
105            del self.options.fPIC
106
107    def build(self):
108        cmake = CMake(self)
109        cmake.configure(source_folder="src")
110        cmake.build()
111
112        # Explicit way:
113        # self.run('cmake %s/hello %s'
114        #          % (self.source_folder, cmake.command_line))
115        # self.run("cmake --build . %s" % cmake.build_config)
116
117    def package(self):
118        self.copy("*.h", dst="include", src="src")
119        self.copy("*.lib", dst="lib", keep_path=False)
120        self.copy("*.dll", dst="bin", keep_path=False)
121        self.copy("*.dylib*", dst="lib", keep_path=False)
122        self.copy("*.so", dst="lib", keep_path=False)
123        self.copy("*.a", dst="lib", keep_path=False)
124
125    def package_info(self):
126        self.cpp_info.libs = ["{name}"]
127"""
128
129conanfile_header = """import os
130
131from conans import ConanFile, tools
132
133
134class {package_name}Conan(ConanFile):
135    name = "{name}"
136    version = "{version}"
137    license = "<Put the package license here>"
138    author = "<Put your name here> <And your email here>"
139    url = "<Package recipe repository url here, for issues about the package>"
140    description = "<Description of {package_name} here>"
141    topics = ("<Put some tag here>", "<here>", "<and here>")
142    no_copy_source = True
143    # No settings/options are necessary, this is header only
144
145    def source(self):
146        '''retrieval of the source code here. Remember you can also put the code
147        in the folder and use exports instead of retrieving it with this
148        source() method
149        '''
150        # self.run("git clone ...") or
151        # tools.download("url", "file.zip")
152        # tools.unzip("file.zip" )
153
154    def package(self):
155        self.copy("*.h", "include")
156
157    def package_id(self):
158        self.info.header_only()
159"""
160
161
162test_conanfile = """import os
163
164from conans import ConanFile, CMake, tools
165
166
167class {package_name}TestConan(ConanFile):
168    settings = "os", "compiler", "build_type", "arch"
169    generators = "cmake"
170
171    def build(self):
172        cmake = CMake(self)
173        # Current dir is "test_package/build/<build_id>" and CMakeLists.txt is
174        # in "test_package"
175        cmake.configure()
176        cmake.build()
177
178    def imports(self):
179        self.copy("*.dll", dst="bin", src="bin")
180        self.copy("*.dylib*", dst="bin", src="lib")
181        self.copy('*.so*', dst='bin', src='lib')
182
183    def test(self):
184        if not tools.cross_building(self):
185            os.chdir("bin")
186            self.run(".%sexample" % os.sep)
187"""
188
189test_cmake = """cmake_minimum_required(VERSION 3.1)
190project(PackageTest CXX)
191
192include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
193conan_basic_setup()
194
195add_executable(example example.cpp)
196target_link_libraries(example ${CONAN_LIBS})
197
198# CTest is a testing tool that can be used to test your project.
199# enable_testing()
200# add_test(NAME example
201#          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/bin
202#          COMMAND example)
203"""
204
205test_cmake_pure_c = """cmake_minimum_required(VERSION 3.1)
206project(PackageTest C)
207
208include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
209conan_basic_setup()
210
211add_executable(example example.c)
212target_link_libraries(example ${CONAN_LIBS})
213
214# CTest is a testing tool that can be used to test your project.
215# enable_testing()
216# add_test(NAME example
217#          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/bin
218#          COMMAND example)
219"""
220
221test_main = """#include "{name}.h"
222
223int main() {{
224    {name}();
225}}
226"""
227
228hello_c = """ #include <stdio.h>
229#include "{name}.h"
230
231void {name}() {{
232    int class = 0;  //This will be an error in C++
233    #ifdef NDEBUG
234        printf("{name}/{version}-(pure C): Hello World Release!\\n");
235    #else
236        printf("{name}/{version}-(pure C): Hello World Debug!\\n");
237    #endif
238}}
239"""
240
241hello_h = """#pragma once
242
243#ifdef WIN32
244  #define {name}_EXPORT __declspec(dllexport)
245#else
246  #define {name}_EXPORT
247#endif
248
249{name}_EXPORT void {name}();
250"""
251
252hello_cpp = """#include <iostream>
253#include "{name}.h"
254
255void {name}(){{
256    #ifdef NDEBUG
257    std::cout << "{name}/{version}: Hello World Release!" <<std::endl;
258    #else
259    std::cout << "{name}/{version}: Hello World Debug!" <<std::endl;
260    #endif
261}}
262"""
263
264cmake_pure_c = """cmake_minimum_required(VERSION 3.1)
265project({name} C)
266
267include(${{CMAKE_BINARY_DIR}}/conanbuildinfo.cmake)
268conan_basic_setup()
269
270add_library({name} {name}.c)
271"""
272
273cmake = """cmake_minimum_required(VERSION 3.1)
274project({name} CXX)
275
276include(${{CMAKE_BINARY_DIR}}/conanbuildinfo.cmake)
277conan_basic_setup()
278
279add_library({name} {name}.cpp)
280"""
281
282gitignore_template = """
283*.pyc
284test_package/build
285
286"""
287
288
289def _render_template(text, name, version, package_name, defines):
290    context = {'name': name,
291               'version': version,
292               'package_name': package_name,
293               'conan_version': client_version}
294    context.update(defines)
295    t = Template(text, keep_trailing_newline=True)
296    return t.render(**context)
297
298
299def _get_files_from_template_dir(template_dir, name, version, package_name, defines):
300    files = []
301    for d, _, fs in os.walk(template_dir):
302        for f in fs:
303            rel_d = os.path.relpath(d, template_dir)
304            rel_f = os.path.join(rel_d, f)
305            files.append(rel_f)
306
307    out_files = dict()
308    for f in files:
309        f_path = os.path.join(template_dir, f)
310        rendered_path = _render_template(f, name=name, version=version, package_name=package_name,
311                                         defines=defines)
312        rendered_file = _render_template(load(f_path), name=name, version=version,
313                                         package_name=package_name, defines=defines)
314        out_files[rendered_path] = rendered_file
315
316    return out_files
317
318
319def cmd_new(ref, header=False, pure_c=False, test=False, exports_sources=False, bare=False,
320            visual_versions=None, linux_gcc_versions=None, linux_clang_versions=None,
321            osx_clang_versions=None, shared=None, upload_url=None, gitignore=None,
322            gitlab_gcc_versions=None, gitlab_clang_versions=None,
323            circleci_gcc_versions=None, circleci_clang_versions=None, circleci_osx_versions=None,
324            template=None, cache=None, defines=None):
325    try:
326        name, version, user, channel, revision = get_reference_fields(ref, user_channel_input=False)
327        # convert "package_name" -> "PackageName"
328        package_name = re.sub(r"(?:^|[\W_])(\w)", lambda x: x.group(1).upper(), name)
329    except ValueError:
330        raise ConanException("Bad parameter, please use full package name,"
331                             "e.g.: MyLib/1.2.3@user/testing")
332
333    # Validate it is a valid reference
334    ConanFileReference(name, version, user, channel)
335
336    if header and exports_sources:
337        raise ConanException("'header' and 'sources' are incompatible options")
338    if pure_c and header:
339        raise ConanException("'pure_c' is incompatible with 'header'")
340    if pure_c and not exports_sources:
341        raise ConanException("'pure_c' requires the use of --source")
342    if bare and (header or exports_sources):
343        raise ConanException("'bare' is incompatible with 'header' and 'sources'")
344    if template and (header or exports_sources or bare or pure_c):
345        raise ConanException("'template' is incompatible with 'header', "
346                             "'sources', 'pure-c' and 'bare'")
347
348    defines = defines or dict()
349
350    if header:
351        files = {"conanfile.py": conanfile_header.format(name=name, version=version,
352                                                         package_name=package_name)}
353    elif exports_sources:
354        if not pure_c:
355            files = {"conanfile.py": conanfile_sources.format(name=name, version=version,
356                                                              package_name=package_name,
357                                                              configure=""),
358                     "src/{}.cpp".format(name): hello_cpp.format(name=name, version=version),
359                     "src/{}.h".format(name): hello_h.format(name=name, version=version),
360                     "src/CMakeLists.txt": cmake.format(name=name, version=version)}
361        else:
362            config = ("\n    def configure(self):\n"
363                      "        del self.settings.compiler.libcxx\n"
364                      "        del self.settings.compiler.cppstd\n")
365            files = {"conanfile.py": conanfile_sources.format(name=name, version=version,
366                                                              package_name=package_name,
367                                                              configure=config),
368                     "src/{}.c".format(name): hello_c.format(name=name, version=version),
369                     "src/{}.h".format(name): hello_h.format(name=name, version=version),
370                     "src/CMakeLists.txt": cmake_pure_c.format(name=name, version=version)}
371    elif bare:
372        files = {"conanfile.py": conanfile_bare.format(name=name, version=version,
373                                                       package_name=package_name)}
374    elif template:
375        is_file_template = os.path.basename(template).endswith('.py')
376        if is_file_template:
377            if not os.path.isabs(template):
378                # FIXME: Conan 2.0. The old path should be removed
379                old_path = os.path.join(cache.cache_folder, "templates", template)
380                new_path = os.path.join(cache.cache_folder, "templates", "command/new", template)
381                template = new_path if os.path.isfile(new_path) else old_path
382            if not os.path.isfile(template):
383                raise ConanException("Template doesn't exist: %s" % template)
384            replaced = _render_template(load(template),
385                                        name=name,
386                                        version=version,
387                                        package_name=package_name,
388                                        defines=defines)
389            files = {"conanfile.py": replaced}
390        elif template == "cmake_lib":
391            from conans.assets.templates.new_v2_cmake import get_cmake_lib_files
392            files = get_cmake_lib_files(name, version, package_name)
393        elif template == "cmake_exe":
394            from conans.assets.templates.new_v2_cmake import get_cmake_exe_files
395            files = get_cmake_exe_files(name, version, package_name)
396        else:
397            if not os.path.isabs(template):
398                template = os.path.join(cache.cache_folder, "templates", "command/new", template)
399            if not os.path.isdir(template):
400                raise ConanException("Template doesn't exist: {}".format(template))
401            template = os.path.normpath(template)
402            files = _get_files_from_template_dir(template_dir=template,
403                                                 name=name,
404                                                 version=version,
405                                                 package_name=package_name,
406                                                 defines=defines)
407    else:
408        files = {"conanfile.py": conanfile.format(name=name, version=version,
409                                                  package_name=package_name)}
410
411    if test:
412        files["test_package/conanfile.py"] = test_conanfile.format(name=name, version=version,
413                                                                   user=user, channel=channel,
414                                                                   package_name=package_name)
415        if pure_c:
416            files["test_package/example.c"] = test_main.format(name=name)
417            files["test_package/CMakeLists.txt"] = test_cmake_pure_c
418        else:
419            include_name = name if exports_sources else "hello"
420            files["test_package/example.cpp"] = test_main.format(name=include_name)
421            files["test_package/CMakeLists.txt"] = test_cmake
422
423    if gitignore:
424        files[".gitignore"] = gitignore_template
425
426    files.update(ci_get_files(name, version, user, channel, visual_versions,
427                              linux_gcc_versions, linux_clang_versions,
428                              osx_clang_versions, shared, upload_url,
429                              gitlab_gcc_versions, gitlab_clang_versions,
430                              circleci_gcc_versions, circleci_clang_versions,
431                              circleci_osx_versions))
432    return files
433