Repository: Qiskit/qiskit

Issue:
Adapt file headers to Lint specifications
### What is the expected enhancement?

When running the test suite, specifically 'lint' tests for code quality, the same message is displayed for every file in the project:

`************* Module qiskit.tools.visualization
qiskit\tools\visualization.py:1:0: C5001: File header should match regex "(?:(?:#[^\n]*)?\n)*# This code is part of Qiskit.\n#\n# \(C\) Copyright IBM [0-9, -]*.\n#\n# This code is licensed under the Apache License, Version 2.0. You may\n# obtain a copy of this license in the LICENSE.txt file in the root directory\n# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.\n#\n# Any modifications or derivative works of this code must retain this\n# copyright notice, and modified files need to carry a notice indicating\n# that they have been altered from the originals.\n" (invalid-file-header)`

This message, with some variations, appears hundreds of times in the log. That makes it a bit difficult to keep track of the other code smells that should be fixed. Setting a new format for the headers, or making Lint ignore this issue, would remove hundreds of log entries in one go.



Discussion:
I have never seen this issue using `make lint` in the source directory, nor when directly using `pylint` on a source file.
It appears when I use `tox`, that runs both the unit tests and lint.
@rafamartinc What platform are you testing on?
On Windows 10. It was just easier to use `tox` there. But when I switched to Ubuntu, `make lint` did not yield this error, so I'm not sure about what causes this... Any further details I can provide, that may be of help?
@rafamartinc this is a pretty common issue with pylint (one of many reasons I opened #1179). Pylint is very sensitive to the environment it's run in. The only way to get pylint to behave consistently is to pin pylint and anything packages or tools that it uses to an exact version. We've been doing this in the constraints file, but we're likely missing several packages there. But even with pinning pylint and astroid it still behaves differently for me between my desktop, laptop, and CI (although I've never experienced an issue with the header rule check).

Assuming you only have one version of python3 installed on your local system can you run `pip freeze` and `.tox/lint/bin/pip freeze` (from the repo root) and paste the output from both here. That will let us see all the packages in your tox venv vs what's installed in your system site-packages and we can add additional pins to constraints.txt based on that if needed.
Oh, and the other key difference I forgot is python version. We run in CI with python 3.5 (although I'm going to push a PR to update that to 3.7 shortly) and pylint will flag errors based on different versions (mostly because of stdlib differences, but sometimes for more subtle reasons). For example, the most recent example is on python 3.7 (which is my local default) lint will fail with:

```
************* Module qiskit.pulse.commands.command
qiskit/pulse/commands/command.py:32:4: W0221: Parameters differ from overridden '__new__' method (arguments-differ)
```

because 3.7 changed the signature for `__new__` in the `abc.ABCMeta`class after python 3.5 (not sure if it was in 3.6 or 3.7)

Resolving patch:
diff --git a/qiskit/util.py b/qiskit/util.py
--- a/qiskit/util.py
+++ b/qiskit/util.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+
 # This code is part of Qiskit.
 #
 # (C) Copyright IBM 2017.
diff --git a/qiskit/visualization/transition_visualization.py b/qiskit/visualization/transition_visualization.py
--- a/qiskit/visualization/transition_visualization.py
+++ b/qiskit/visualization/transition_visualization.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
 # This code is part of Qiskit.
 #
 # (C) Copyright IBM 2017, 2018.
diff --git a/tools/verify_headers.py b/tools/verify_headers.py
new file mode 100755
--- /dev/null
+++ b/tools/verify_headers.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2020
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+import argparse
+import multiprocessing
+import os
+import sys
+
+
+def discover_files(code_paths):
+    out_paths = []
+    for path in code_paths:
+        if os.path.isfile(path):
+            out_paths.append(path)
+        else:
+            for directory in os.walk(path):
+                dir_path = directory[0]
+                for subfile in directory[2]:
+                    if subfile.endswith('.py'):
+                        out_paths.append(os.path.join(dir_path, subfile))
+    return out_paths
+
+
+def validate_header(file_path):
+    header = """# -*- coding: utf-8 -*-
+
+# This code is part of Qiskit.
+#
+"""
+    apache_text = """#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+"""
+    count = 0
+    with open(file_path, 'r', encoding='utf8') as fd:
+        lines = fd.readlines()
+    start = 0
+    for index, line in enumerate(lines):
+        count += 1
+        if count > 5:
+            return (file_path, False, "Header not found in first 5 lines")
+        if line == "# -*- coding: utf-8 -*-\n":
+            start = index
+            break
+    if ''.join(lines[start:start + 4]) != header:
+        return (file_path, False,
+                "Header up to copyright line does not match: %s" % header)
+    if not lines[start + 4].startswith("# (C) Copyright IBM"):
+        return (file_path, False,
+                "Header copyright line not found")
+    if ''.join(lines[start + 5:start + 13]) != apache_text:
+        return (file_path, False,
+                "Header apache text string doesn't match:\n %s" % apache_text)
+    return (file_path, True, None)
+
+
+def main():
+    default_path = os.path.join(
+        os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
+        'qiskit')
+    parser = argparse.ArgumentParser(description="Check file headers.")
+    parser.add_argument("paths", type=str, nargs='*',
+                        default=[default_path],
+                        help='Paths to scan by default uses ../qiskit from the'
+                             ' script')
+    args = parser.parse_args()
+    files = discover_files(args.paths)
+    pool = multiprocessing.Pool()
+    res = pool.map(validate_header, files)
+    failed_files = [x for x in res if x[1] is False]
+    if len(failed_files) > 0:
+        for i in failed_files:
+            sys.stderr.write("%s failed header check because:\n\n" % i[0])
+            sys.stderr.write("%s\n\n" % i[2])
+        sys.exit(1)
+    sys.exit(0)
+
+
+if __name__ == '__main__':
+    main()


Test patch: