Repository: conan-io/conan

Issue:
Generated conanfile.py should comply with PEP8 and be friendly to static code analyzers
As of 1.1.1, `conanfile.py` for the main and test packages automatically generated through `conan new -t foo/0.1@username/testing` don't comply with PEP8. This is a bit annoying if you care about style to have to fix the same errors again, or avoid code generation features.

Here's the current state:
```
$ pip install --user pycodestyle
$ pycodestyle conanfile.py test_package/conanfile.py 
conanfile.py:18:80: E501 line too long (85 > 79 characters)
conanfile.py:30:80: E501 line too long (82 > 79 characters)
test_package/conanfile.py:4:1: E302 expected 2 blank lines, found 1
test_package/conanfile.py:10:80: E501 line too long (96 > 79 characters)
```

3 out of 4 warnings are about [maximum line length](https://www.python.org/dev/peps/pep-0008/#maximum-line-length). The 4th is a missing blank line.

Another one that is not mentionned by the `pycodestyle` tool but is good practice is about the [imports](https://www.python.org/dev/peps/pep-0008/#imports). The order is wrong, and import groups should be separated by a newline:

**bad**
```
from conans import ConanFile, CMake, tools
import os
```

**good**
```
import os

from conans import ConanFile, CMake, tools
```

As an additional style remark (unrelated to PEP8), I find this code in the test_package to have poor readability:
```
self.run(".%sexample" % os.sep)
```

```
import os.path
...
self.run(os.path.join('.', 'example'))
```
A `git grep -w example` will fail on the first version, as the formatting is mixed with the content.

Seen on Conan 1.1.1 on Ubuntu 14.04

- [X] I've read the [CONTRIBUTING guide](https://raw.githubusercontent.com/conan-io/conan/develop/.github/CONTRIBUTING.md).
- [X] I've specified the Conan version, operating system version and any tool that can be relevant.
- [X] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.


Discussion:
Agree.

But we are supposed to be using 100 chars all along conan codebase (sure it is infringed in many places).
The pep8 is not very strict about this and allows relaxing it.

Also, it is not ``conan create`` but ``conan new``.

This should be easy to change, I suggest:
- Import order,
- lines limit to 100 chars
- blank line
Thanks for your answer. You're right for `conan new`, I fixed it in the initial message.

As for relaxing the maximum line length, PEP8 says:

> Some teams strongly prefer a longer line length. For code maintained exclusively or primarily by a team that can reach agreement on this issue, it is okay to increase the nominal line length from 80 to 100 characters (effectively increasing the maximum length to 99 characters), provided that comments and docstrings are still wrapped at 72 characters.

The generated files are not maitained primarily by one team, it's quite the opposite, as you encourage people to package their app using conan. Another thing is that the infringing lines are comments and according to the guidelines, they should still be wrapped at 72 characters.

Now that we have seen `pycodestyle`, let's take a look at `pylint` checks:

```
$ pylint conanfile.py test_package/conanfile.py 
No config file found, using default configuration
************* Module conanfile
C:  1, 0: Missing module docstring (missing-docstring)
C:  4, 0: Missing class docstring (missing-docstring)
E: 34, 8: self.copy is not callable (not-callable)
E: 35, 8: self.copy is not callable (not-callable)
E: 36, 8: self.copy is not callable (not-callable)
E: 37, 8: self.copy is not callable (not-callable)
E: 38, 8: self.copy is not callable (not-callable)
E: 39, 8: self.copy is not callable (not-callable)
C:  1, 0: Missing module docstring (missing-docstring)
C:  4, 0: Missing class docstring (missing-docstring)
C: 14, 4: Missing method docstring (missing-docstring)
E: 15, 8: self.copy is not callable (not-callable)
E: 16, 8: self.copy is not callable (not-callable)
E: 17, 8: self.copy is not callable (not-callable)
C:  2, 0: standard import "import os" should be placed before "from conans import ConanFile, CMake, tools" (wrong-import-order)

--------------------------------------------------------------------
Your code has been rated at -1.33/10 (previous run: -1.33/10, +0.00)
```
Not sure about what the self.copy error is, but I think you see the point. Generated code should not trigger warnings or errors by usual static analyzers, because that puts the burden of the work on the shoulders of **each** package maintainer that cares about those metrics and use those tools.

To make it short: if the template is broken, fix the template.

Resolving patch:
diff --git a/conans/client/cmd/new.py b/conans/client/cmd/new.py
--- a/conans/client/cmd/new.py
+++ b/conans/client/cmd/new.py
@@ -21,9 +21,11 @@ class {package_name}Conan(ConanFile):
     def source(self):
         self.run("git clone https://github.com/memsharded/hello.git")
         self.run("cd hello && git checkout static_shared")
-        # This small hack might be useful to guarantee proper /MT /MD linkage in MSVC
-        # if the packaged project doesn't have variables to set it properly
-        tools.replace_in_file("hello/CMakeLists.txt", "PROJECT(MyHello)", '''PROJECT(MyHello)
+        # This small hack might be useful to guarantee proper /MT /MD linkage
+        # in MSVC if the packaged project doesn't have variables to set it
+        # properly
+        tools.replace_in_file("hello/CMakeLists.txt", "PROJECT(MyHello)",
+                              '''PROJECT(MyHello)
 include(${{CMAKE_BINARY_DIR}}/conanbuildinfo.cmake)
 conan_basic_setup()''')
 
@@ -33,7 +35,8 @@ def build(self):
         cmake.build()
 
         # Explicit way:
-        # self.run('cmake %s/hello %s' % (self.source_folder, cmake.command_line))
+        # self.run('cmake %s/hello %s'
+        #          % (self.source_folder, cmake.command_line))
         # self.run("cmake --build . %s" % cmake.build_config)
 
     def package(self):
@@ -46,10 +49,11 @@ def package(self):
 
     def package_info(self):
         self.cpp_info.libs = ["hello"]
+
 """
 
-conanfile_bare = """from conans import ConanFile
-from conans import tools
+conanfile_bare = """from conans import ConanFile, tools
+
 
 class {package_name}Conan(ConanFile):
     name = "{name}"
@@ -87,7 +91,8 @@ def build(self):
         cmake.build()
 
         # Explicit way:
-        # self.run('cmake %s/src %s' % (self.source_folder, cmake.command_line))
+        # self.run('cmake %s/hello %s'
+        #          % (self.source_folder, cmake.command_line))
         # self.run("cmake --build . %s" % cmake.build_config)
 
     def package(self):
@@ -102,8 +107,9 @@ def package_info(self):
         self.cpp_info.libs = ["hello"]
 """
 
-conanfile_header = """from conans import ConanFile, tools
-import os
+conanfile_header = """import os
+
+from conans import ConanFile, tools
 
 
 class {package_name}Conan(ConanFile):
@@ -116,20 +122,23 @@ class {package_name}Conan(ConanFile):
     # No settings/options are necessary, this is header only
 
     def source(self):
-        '''retrieval of the source code here. Remember you can also put the code in the folder and
-        use exports instead of retrieving it with this source() method
+        '''retrieval of the source code here. Remember you can also put the code
+        in the folder and use exports instead of retrieving it with this
+        source() method
         '''
-        #self.run("git clone ...") or
-        #tools.download("url", "file.zip")
-        #tools.unzip("file.zip" )
+        # self.run("git clone ...") or
+        # tools.download("url", "file.zip")
+        # tools.unzip("file.zip" )
 
     def package(self):
         self.copy("*.h", "include")
 """
 
 
-test_conanfile = """from conans import ConanFile, CMake, tools
-import os
+test_conanfile = """import os
+
+from conans import ConanFile, CMake, tools
+
 
 class {package_name}TestConan(ConanFile):
     settings = "os", "compiler", "build_type", "arch"
@@ -137,7 +146,8 @@ class {package_name}TestConan(ConanFile):
 
     def build(self):
         cmake = CMake(self)
-        # Current dir is "test_package/build/<build_id>" and CMakeLists.txt is in "test_package"
+        # Current dir is "test_package/build/<build_id>" and CMakeLists.txt is
+        # in "test_package"
         cmake.configure()
         cmake.build()
 
@@ -258,7 +268,7 @@ def cmd_new(ref, header=False, pure_c=False, test=False, exports_sources=False,
         files = {"conanfile.py": conanfile.format(name=name, version=version,
                                                   package_name=package_name)}
         if pure_c:
-            config = "\n    def configure(self):\n        del self.settings.compiler.libcxx"
+            config = "    def configure(self):\n        del self.settings.compiler.libcxx\n"
             files["conanfile.py"] = files["conanfile.py"] + config
 
     if test:
diff --git a/conans/model/conan_file.py b/conans/model/conan_file.py
--- a/conans/model/conan_file.py
+++ b/conans/model/conan_file.py
@@ -240,9 +240,15 @@ def configure(self):
         """
 
     def build(self):
+        """ build your project calling the desired build tools as done in the command line.
+        E.g. self.run("cmake --build .") Or use the provided build helpers. E.g. cmake.build()
+        """
         self.output.warn("This conanfile has no build step")
 
     def package(self):
+        """ package the needed files from source and build folders.
+        E.g. self.copy("*.h", src="src/includes", dst="includes")
+        """
         self.output.warn("This conanfile has no package step")
 
     def package_info(self):
@@ -267,6 +273,9 @@ def package_id(self):
         """
 
     def test(self):
+        """ test the generated executable.
+        E.g.  self.run("./example")
+        """
         raise ConanException("You need to create a method 'test' in your test/conanfile.py")
 
     def __repr__(self):


Test patch: