Browse code

do not import functions from distutils

According to the comment [1], we are adviced to use functions wrapped by
setuptools instead of distutils' raw ones.

Related problem is that setuptools-49.2.0 started to raise a warning, if
it sees that distutils is imported already. This *maybe* mitigates this
warning (if nothing imports distutils before

from ._version import __version__

is called.

[1] https://github.com/pypa/setuptools/issues/2261#issuecomment-658315975

Slava Ostroukh authored on 17/07/2020 16:15:21 • Joseph Weston committed on 15/08/2020 23:36:36
Showing 1 changed files
... ...
@@ -5,7 +5,7 @@ from collections import namedtuple
5 5
 import os
6 6
 import subprocess
7 7
 
8
-from distutils.command.build_py import build_py as build_py_orig
8
+from setuptools.command.build_py import build_py as build_py_orig
9 9
 from setuptools.command.sdist import sdist as sdist_orig
10 10
 
11 11
 Version = namedtuple("Version", ("release", "dev", "labels"))
Browse code

allow distributions that place packages in a "src" directory

Closes #15
Closes #18

Joseph Weston authored on 17/10/2019 21:13:32
Showing 1 changed files
... ...
@@ -16,6 +16,13 @@ __all__ = []
16 16
 package_root = os.path.dirname(os.path.realpath(__file__))
17 17
 package_name = os.path.basename(package_root)
18 18
 distr_root = os.path.dirname(package_root)
19
+# If the package is inside a "src" directory the
20
+# distribution root is 1 level up.
21
+if os.path.split(distr_root)[1] == "src":
22
+    _package_root_inside_src = True
23
+    distr_root = os.path.dirname(distr_root)
24
+else:
25
+    _package_root_inside_src = False
19 26
 
20 27
 STATIC_VERSION_FILE = "_static_version.py"
21 28
 
... ...
@@ -190,7 +197,11 @@ class _build_py(build_py_orig):
190 197
 class _sdist(sdist_orig):
191 198
     def make_release_tree(self, base_dir, files):
192 199
         super().make_release_tree(base_dir, files)
193
-        _write_version(os.path.join(base_dir, package_name, STATIC_VERSION_FILE))
200
+        if _package_root_inside_src:
201
+            p = os.path.join("src", package_name)
202
+        else:
203
+            p = package_name
204
+        _write_version(os.path.join(base_dir, p, STATIC_VERSION_FILE))
194 205
 
195 206
 
196 207
 cmdclass = dict(sdist=_sdist, build_py=_build_py)
Browse code

run black code formatter

Joseph Weston authored on 08/05/2019 12:24:04
Showing 1 changed files
... ...
@@ -8,7 +8,7 @@ import subprocess
8 8
 from distutils.command.build_py import build_py as build_py_orig
9 9
 from setuptools.command.sdist import sdist as sdist_orig
10 10
 
11
-Version = namedtuple('Version', ('release', 'dev', 'labels'))
11
+Version = namedtuple("Version", ("release", "dev", "labels"))
12 12
 
13 13
 # No public API
14 14
 __all__ = []
... ...
@@ -17,12 +17,12 @@ package_root = os.path.dirname(os.path.realpath(__file__))
17 17
 package_name = os.path.basename(package_root)
18 18
 distr_root = os.path.dirname(package_root)
19 19
 
20
-STATIC_VERSION_FILE = '_static_version.py'
20
+STATIC_VERSION_FILE = "_static_version.py"
21 21
 
22 22
 
23 23
 def get_version(version_file=STATIC_VERSION_FILE):
24 24
     version_info = get_static_version_info(version_file)
25
-    version = version_info['version']
25
+    version = version_info["version"]
26 26
     if version == "__use_git__":
27 27
         version = get_version_from_git()
28 28
         if not version:
... ...
@@ -36,13 +36,13 @@ def get_version(version_file=STATIC_VERSION_FILE):
36 36
 
37 37
 def get_static_version_info(version_file=STATIC_VERSION_FILE):
38 38
     version_info = {}
39
-    with open(os.path.join(package_root, version_file), 'rb') as f:
39
+    with open(os.path.join(package_root, version_file), "rb") as f:
40 40
         exec(f.read(), {}, version_info)
41 41
     return version_info
42 42
 
43 43
 
44 44
 def version_is_from_git(version_file=STATIC_VERSION_FILE):
45
-    return get_static_version_info(version_file)['version'] == '__use_git__'
45
+    return get_static_version_info(version_file)["version"] == "__use_git__"
46 46
 
47 47
 
48 48
 def pep440_format(version_info):
... ...
@@ -50,13 +50,13 @@ def pep440_format(version_info):
50 50
 
51 51
     version_parts = [release]
52 52
     if dev:
53
-        if release.endswith('-dev') or release.endswith('.dev'):
53
+        if release.endswith("-dev") or release.endswith(".dev"):
54 54
             version_parts.append(dev)
55 55
         else:  # prefer PEP440 over strict adhesion to semver
56
-            version_parts.append('.dev{}'.format(dev))
56
+            version_parts.append(".dev{}".format(dev))
57 57
 
58 58
     if labels:
59
-        version_parts.append('+')
59
+        version_parts.append("+")
60 60
         version_parts.append(".".join(labels))
61 61
 
62 62
     return "".join(version_parts)
... ...
@@ -64,15 +64,17 @@ def pep440_format(version_info):
64 64
 
65 65
 def get_version_from_git():
66 66
     try:
67
-        p = subprocess.Popen(['git', 'rev-parse', '--show-toplevel'],
68
-                             cwd=distr_root,
69
-                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
67
+        p = subprocess.Popen(
68
+            ["git", "rev-parse", "--show-toplevel"],
69
+            cwd=distr_root,
70
+            stdout=subprocess.PIPE,
71
+            stderr=subprocess.PIPE,
72
+        )
70 73
     except OSError:
71 74
         return
72 75
     if p.wait() != 0:
73 76
         return
74
-    if not os.path.samefile(p.communicate()[0].decode().rstrip('\n'),
75
-                            distr_root):
77
+    if not os.path.samefile(p.communicate()[0].decode().rstrip("\n"), distr_root):
76 78
         # The top-level directory of the current Git repository is not the same
77 79
         # as the root directory of the distribution: do not extract the
78 80
         # version from Git.
... ...
@@ -81,12 +83,14 @@ def get_version_from_git():
81 83
     # git describe --first-parent does not take into account tags from branches
82 84
     # that were merged-in. The '--long' flag gets us the 'dev' version and
83 85
     # git hash, '--always' returns the git hash even if there are no tags.
84
-    for opts in [['--first-parent'], []]:
86
+    for opts in [["--first-parent"], []]:
85 87
         try:
86 88
             p = subprocess.Popen(
87
-                ['git', 'describe', '--long', '--always'] + opts,
89
+                ["git", "describe", "--long", "--always"] + opts,
88 90
                 cwd=distr_root,
89
-                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
91
+                stdout=subprocess.PIPE,
92
+                stderr=subprocess.PIPE,
93
+            )
90 94
         except OSError:
91 95
             return
92 96
         if p.wait() == 0:
... ...
@@ -97,17 +101,17 @@ def get_version_from_git():
97 101
     description = (
98 102
         p.communicate()[0]
99 103
         .decode()
100
-        .strip('v')  # Tags can have a leading 'v', but the version should not
101
-        .rstrip('\n')
102
-        .rsplit('-', 2)  # Split the latest tag, commits since tag, and hash
104
+        .strip("v")  # Tags can have a leading 'v', but the version should not
105
+        .rstrip("\n")
106
+        .rsplit("-", 2)  # Split the latest tag, commits since tag, and hash
103 107
     )
104 108
 
105 109
     try:
106 110
         release, dev, git = description
107 111
     except ValueError:  # No tags, only the git hash
108 112
         # prepend 'g' to match with format returned by 'git describe'
109
-        git = 'g{}'.format(*description)
110
-        release = 'unknown'
113
+        git = "g{}".format(*description)
114
+        release = "unknown"
111 115
         dev = None
112 116
 
113 117
     labels = []
... ...
@@ -117,12 +121,12 @@ def get_version_from_git():
117 121
         labels.append(git)
118 122
 
119 123
     try:
120
-        p = subprocess.Popen(['git', 'diff', '--quiet'], cwd=distr_root)
124
+        p = subprocess.Popen(["git", "diff", "--quiet"], cwd=distr_root)
121 125
     except OSError:
122
-        labels.append('confused')  # This should never happen.
126
+        labels.append("confused")  # This should never happen.
123 127
     else:
124 128
         if p.wait() == 1:
125
-            labels.append('dirty')
129
+            labels.append("dirty")
126 130
 
127 131
     return Version(release, dev, labels)
128 132
 
... ...
@@ -134,25 +138,25 @@ def get_version_from_git():
134 138
 #       if it is not tagged.
135 139
 def get_version_from_git_archive(version_info):
136 140
     try:
137
-        refnames = version_info['refnames']
138
-        git_hash = version_info['git_hash']
141
+        refnames = version_info["refnames"]
142
+        git_hash = version_info["git_hash"]
139 143
     except KeyError:
140 144
         # These fields are not present if we are running from an sdist.
141 145
         # Execution should never reach here, though
142 146
         return None
143 147
 
144
-    if git_hash.startswith('$Format') or refnames.startswith('$Format'):
148
+    if git_hash.startswith("$Format") or refnames.startswith("$Format"):
145 149
         # variables not expanded during 'git archive'
146 150
         return None
147 151
 
148
-    VTAG = 'tag: v'
152
+    VTAG = "tag: v"
149 153
     refs = set(r.strip() for r in refnames.split(","))
150
-    version_tags = set(r[len(VTAG):] for r in refs if r.startswith(VTAG))
154
+    version_tags = set(r[len(VTAG) :] for r in refs if r.startswith(VTAG))
151 155
     if version_tags:
152 156
         release, *_ = sorted(version_tags)  # prefer e.g. "2.0" over "2.0rc1"
153 157
         return Version(release, dev=None, labels=None)
154 158
     else:
155
-        return Version('unknown', dev=None, labels=['g{}'.format(git_hash)])
159
+        return Version("unknown", dev=None, labels=["g{}".format(git_hash)])
156 160
 
157 161
 
158 162
 __version__ = get_version()
... ...
@@ -162,6 +166,7 @@ __version__ = get_version()
162 166
 # which can be used from setup.py. The 'package_name' and
163 167
 # '__version__' module globals are used (but not modified).
164 168
 
169
+
165 170
 def _write_version(fname):
166 171
     # This could be a hard link, so try to delete it first.  Is there any way
167 172
     # to do this atomically together with opening?
... ...
@@ -169,23 +174,23 @@ def _write_version(fname):
169 174
         os.remove(fname)
170 175
     except OSError:
171 176
         pass
172
-    with open(fname, 'w') as f:
173
-        f.write("# This file has been created by setup.py.\n"
174
-                "version = '{}'\n".format(__version__))
177
+    with open(fname, "w") as f:
178
+        f.write(
179
+            "# This file has been created by setup.py.\n"
180
+            "version = '{}'\n".format(__version__)
181
+        )
175 182
 
176 183
 
177 184
 class _build_py(build_py_orig):
178 185
     def run(self):
179 186
         super().run()
180
-        _write_version(os.path.join(self.build_lib, package_name,
181
-                                    STATIC_VERSION_FILE))
187
+        _write_version(os.path.join(self.build_lib, package_name, STATIC_VERSION_FILE))
182 188
 
183 189
 
184 190
 class _sdist(sdist_orig):
185 191
     def make_release_tree(self, base_dir, files):
186 192
         super().make_release_tree(base_dir, files)
187
-        _write_version(os.path.join(base_dir, package_name,
188
-                                    STATIC_VERSION_FILE))
193
+        _write_version(os.path.join(base_dir, package_name, STATIC_VERSION_FILE))
189 194
 
190 195
 
191 196
 cmdclass = dict(sdist=_sdist, build_py=_build_py)
Browse code

fix typo

Bas Nijholt authored on 16/10/2018 11:40:55 • GitHub committed on 16/10/2018 11:40:55
Showing 1 changed files
... ...
@@ -52,7 +52,7 @@ def pep440_format(version_info):
52 52
     if dev:
53 53
         if release.endswith('-dev') or release.endswith('.dev'):
54 54
             version_parts.append(dev)
55
-        else:  # prefer PEP440 over stric adhesion to semver
55
+        else:  # prefer PEP440 over strict adhesion to semver
56 56
             version_parts.append('.dev{}'.format(dev))
57 57
 
58 58
     if labels:
Browse code

fix flake8 issues

Bas Nijholt authored on 15/10/2018 13:56:39
Showing 1 changed files
... ...
@@ -94,11 +94,13 @@ def get_version_from_git():
94 94
     else:
95 95
         return
96 96
 
97
-    description = (p.communicate()[0]
97
+    description = (
98
+        p.communicate()[0]
98 99
         .decode()
99 100
         .strip('v')  # Tags can have a leading 'v', but the version should not
100 101
         .rstrip('\n')
101
-        .rsplit('-', 2))  # Split the latest tag, commits since tag, and hash
102
+        .rsplit('-', 2)  # Split the latest tag, commits since tag, and hash
103
+    )
102 104
 
103 105
     try:
104 106
         release, dev, git = description
Browse code

add function to tell us if version info comes from git

Joseph Weston authored on 15/10/2018 10:35:17
Showing 1 changed files
... ...
@@ -41,6 +41,10 @@ def get_static_version_info(version_file=STATIC_VERSION_FILE):
41 41
     return version_info
42 42
 
43 43
 
44
+def version_is_from_git(version_file=STATIC_VERSION_FILE):
45
+    return get_static_version_info(version_file)['version'] == '__use_git__'
46
+
47
+
44 48
 def pep440_format(version_info):
45 49
     release, dev, labels = version_info
46 50
 
Browse code

factor out getting static version info

Joseph Weston authored on 15/10/2018 10:33:15
Showing 1 changed files
... ...
@@ -21,9 +21,7 @@ STATIC_VERSION_FILE = '_static_version.py'
21 21
 
22 22
 
23 23
 def get_version(version_file=STATIC_VERSION_FILE):
24
-    version_info = {}
25
-    with open(os.path.join(package_root, version_file), 'rb') as f:
26
-        exec(f.read(), {}, version_info)
24
+    version_info = get_static_version_info(version_file)
27 25
     version = version_info['version']
28 26
     if version == "__use_git__":
29 27
         version = get_version_from_git()
... ...
@@ -36,6 +34,13 @@ def get_version(version_file=STATIC_VERSION_FILE):
36 34
         return version
37 35
 
38 36
 
37
+def get_static_version_info(version_file=STATIC_VERSION_FILE):
38
+    version_info = {}
39
+    with open(os.path.join(package_root, version_file), 'rb') as f:
40
+        exec(f.read(), {}, version_info)
41
+    return version_info
42
+
43
+
39 44
 def pep440_format(version_info):
40 45
     release, dev, labels = version_info
41 46
 
Browse code

prepend 'g' to git hash reported when no tags are present

When tags are present 'git describe' reports the git hash with
a leading 'g' character, but when no tags are present it does
not. We want the format to be consistent, so we prepend it ourselves.

Joseph Weston authored on 13/08/2018 18:29:17
Showing 1 changed files
... ...
@@ -94,7 +94,8 @@ def get_version_from_git():
94 94
     try:
95 95
         release, dev, git = description
96 96
     except ValueError:  # No tags, only the git hash
97
-        git, = description
97
+        # prepend 'g' to match with format returned by 'git describe'
98
+        git = 'g{}'.format(*description)
98 99
         release = 'unknown'
99 100
         dev = None
100 101
 
Browse code

report version 'unknown' with git hash as a label when there are no tags

This gives more useful information with no real disadvantage.
In the future we may consider reporting 'v0.0.0' if there are no tags.

Closes #9.

Joseph Weston authored on 06/08/2018 20:15:36
Showing 1 changed files
... ...
@@ -70,11 +70,12 @@ def get_version_from_git():
70 70
         return
71 71
 
72 72
     # git describe --first-parent does not take into account tags from branches
73
-    # that were merged-in.
73
+    # that were merged-in. The '--long' flag gets us the 'dev' version and
74
+    # git hash, '--always' returns the git hash even if there are no tags.
74 75
     for opts in [['--first-parent'], []]:
75 76
         try:
76 77
             p = subprocess.Popen(
77
-                ['git', 'describe', '--long'] + opts,
78
+                ['git', 'describe', '--long', '--always'] + opts,
78 79
                 cwd=distr_root,
79 80
                 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
80 81
         except OSError:
... ...
@@ -83,9 +84,20 @@ def get_version_from_git():
83 84
             break
84 85
     else:
85 86
         return
86
-    description = p.communicate()[0].decode().strip('v').rstrip('\n')
87 87
 
88
-    release, dev, git = description.rsplit('-', 2)
88
+    description = (p.communicate()[0]
89
+        .decode()
90
+        .strip('v')  # Tags can have a leading 'v', but the version should not
91
+        .rstrip('\n')
92
+        .rsplit('-', 2))  # Split the latest tag, commits since tag, and hash
93
+
94
+    try:
95
+        release, dev, git = description
96
+    except ValueError:  # No tags, only the git hash
97
+        git, = description
98
+        release = 'unknown'
99
+        dev = None
100
+
89 101
     labels = []
90 102
     if dev == "0":
91 103
         dev = None
Joseph Weston authored on 06/08/2018 09:40:24
Showing 1 changed files
... ...
@@ -73,9 +73,10 @@ def get_version_from_git():
73 73
     # that were merged-in.
74 74
     for opts in [['--first-parent'], []]:
75 75
         try:
76
-            p = subprocess.Popen(['git', 'describe', '--long'] + opts,
77
-                                 cwd=distr_root,
78
-                                 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
76
+            p = subprocess.Popen(
77
+                ['git', 'describe', '--long'] + opts,
78
+                cwd=distr_root,
79
+                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
79 80
         except OSError:
80 81
             return
81 82
         if p.wait() == 0:
... ...
@@ -94,7 +95,7 @@ def get_version_from_git():
94 95
     try:
95 96
         p = subprocess.Popen(['git', 'diff', '--quiet'], cwd=distr_root)
96 97
     except OSError:
97
-        labels.append('confused') # This should never happen.
98
+        labels.append('confused')  # This should never happen.
98 99
     else:
99 100
         if p.wait() == 1:
100 101
             labels.append('dirty')
... ...
@@ -132,6 +133,7 @@ def get_version_from_git_archive(version_info):
132 133
 
133 134
 __version__ = get_version()
134 135
 
136
+
135 137
 # The following section defines a module global 'cmdclass',
136 138
 # which can be used from setup.py. The 'package_name' and
137 139
 # '__version__' module globals are used (but not modified).
Browse code

remove unused import 'sys'

Bas Nijholt authored on 25/06/2018 00:04:01 • Joseph Weston committed on 01/08/2018 11:21:05
Showing 1 changed files
... ...
@@ -4,7 +4,6 @@
4 4
 from collections import namedtuple
5 5
 import os
6 6
 import subprocess
7
-import sys
8 7
 
9 8
 from distutils.command.build_py import build_py as build_py_orig
10 9
 from setuptools.command.sdist import sdist as sdist_orig
Browse code

hard code version string on invocation of 'build_py', not 'build'

Closes #5.

Joseph Weston authored on 13/06/2018 18:42:23
Showing 1 changed files
... ...
@@ -6,7 +6,7 @@ import os
6 6
 import subprocess
7 7
 import sys
8 8
 
9
-from distutils.command.build import build as build_orig
9
+from distutils.command.build_py import build_py as build_py_orig
10 10
 from setuptools.command.sdist import sdist as sdist_orig
11 11
 
12 12
 Version = namedtuple('Version', ('release', 'dev', 'labels'))
... ...
@@ -149,7 +149,7 @@ def _write_version(fname):
149 149
                 "version = '{}'\n".format(__version__))
150 150
 
151 151
 
152
-class _build(build_orig):
152
+class _build_py(build_py_orig):
153 153
     def run(self):
154 154
         super().run()
155 155
         _write_version(os.path.join(self.build_lib, package_name,
... ...
@@ -163,4 +163,4 @@ class _sdist(sdist_orig):
163 163
                                     STATIC_VERSION_FILE))
164 164
 
165 165
 
166
-cmdclass = dict(sdist=_sdist, build=_build)
166
+cmdclass = dict(sdist=_sdist, build_py=_build_py)
Browse code

prefer PEP440 formatting to strict semver

Miniver is, after all, for Python projects.

Christian Marquardt authored on 07/03/2018 14:25:15 • Joseph Weston committed on 07/03/2018 14:29:37
Showing 1 changed files
... ...
@@ -32,22 +32,20 @@ def get_version(version_file=STATIC_VERSION_FILE):
32 32
             version = get_version_from_git_archive(version_info)
33 33
         if not version:
34 34
             version = Version("unknown", None, None)
35
-        return semver_format(version)
35
+        return pep440_format(version)
36 36
     else:
37 37
         return version
38 38
 
39 39
 
40
-def semver_format(version_info):
40
+def pep440_format(version_info):
41 41
     release, dev, labels = version_info
42 42
 
43 43
     version_parts = [release]
44 44
     if dev:
45
-        if release.endswith('-dev'):
45
+        if release.endswith('-dev') or release.endswith('.dev'):
46 46
             version_parts.append(dev)
47
-        elif release.contains('-'):
47
+        else:  # prefer PEP440 over stric adhesion to semver
48 48
             version_parts.append('.dev{}'.format(dev))
49
-        else:
50
-            version_parts.append('-dev{}'.format(dev))
51 49
 
52 50
     if labels:
53 51
         version_parts.append('+')
Browse code

remove use of f-strings where not necessary

No point in excluding Python < 3.6 for no reason.

Christian Marquardt authored on 07/03/2018 14:19:21 • Joseph Weston committed on 07/03/2018 14:29:21
Showing 1 changed files
... ...
@@ -130,7 +130,7 @@ def get_version_from_git_archive(version_info):
130 130
         release, *_ = sorted(version_tags)  # prefer e.g. "2.0" over "2.0rc1"
131 131
         return Version(release, dev=None, labels=None)
132 132
     else:
133
-        return Version('unknown', dev=None, labels=[f'g{git_hash}'])
133
+        return Version('unknown', dev=None, labels=['g{}'.format(git_hash)])
134 134
 
135 135
 
136 136
 __version__ = get_version()
Browse code

minor cleanup

Joseph Weston authored on 27/02/2018 00:26:16
Showing 1 changed files
... ...
@@ -26,8 +26,7 @@ def get_version(version_file=STATIC_VERSION_FILE):
26 26
     with open(os.path.join(package_root, version_file), 'rb') as f:
27 27
         exec(f.read(), {}, version_info)
28 28
     version = version_info['version']
29
-    version_is_from_git = (version == "__use_git__")
30
-    if version_is_from_git:
29
+    if version == "__use_git__":
31 30
         version = get_version_from_git()
32 31
         if not version:
33 32
             version = get_version_from_git_archive(version_info)
Browse code

remove reference to Kwant

Joseph Weston authored on 27/02/2018 00:21:52
Showing 1 changed files
... ...
@@ -69,7 +69,7 @@ def get_version_from_git():
69 69
     if not os.path.samefile(p.communicate()[0].decode().rstrip('\n'),
70 70
                             distr_root):
71 71
         # The top-level directory of the current Git repository is not the same
72
-        # as the root directory of the Kwant distribution: do not extract the
72
+        # as the root directory of the distribution: do not extract the
73 73
         # version from Git.
74 74
         return
75 75
 
Browse code

change default names for version modules and variables

Change 'version.py' to '_version.py' and make the 'version'
module attribute '__version__' instead.

Joseph Weston authored on 24/02/2018 15:37:54
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,169 @@
1
+# -*- coding: utf-8 -*-
2
+# This file is part of 'miniver': https://github.com/jbweston/miniver
3
+#
4
+from collections import namedtuple
5
+import os
6
+import subprocess
7
+import sys
8
+
9
+from distutils.command.build import build as build_orig
10
+from setuptools.command.sdist import sdist as sdist_orig
11
+
12
+Version = namedtuple('Version', ('release', 'dev', 'labels'))
13
+
14
+# No public API
15
+__all__ = []
16
+
17
+package_root = os.path.dirname(os.path.realpath(__file__))
18
+package_name = os.path.basename(package_root)
19
+distr_root = os.path.dirname(package_root)
20
+
21
+STATIC_VERSION_FILE = '_static_version.py'
22
+
23
+
24
+def get_version(version_file=STATIC_VERSION_FILE):
25
+    version_info = {}
26
+    with open(os.path.join(package_root, version_file), 'rb') as f:
27
+        exec(f.read(), {}, version_info)
28
+    version = version_info['version']
29
+    version_is_from_git = (version == "__use_git__")
30
+    if version_is_from_git:
31
+        version = get_version_from_git()
32
+        if not version:
33
+            version = get_version_from_git_archive(version_info)
34
+        if not version:
35
+            version = Version("unknown", None, None)
36
+        return semver_format(version)
37
+    else:
38
+        return version
39
+
40
+
41
+def semver_format(version_info):
42
+    release, dev, labels = version_info
43
+
44
+    version_parts = [release]
45
+    if dev:
46
+        if release.endswith('-dev'):
47
+            version_parts.append(dev)
48
+        elif release.contains('-'):
49
+            version_parts.append('.dev{}'.format(dev))
50
+        else:
51
+            version_parts.append('-dev{}'.format(dev))
52
+
53
+    if labels:
54
+        version_parts.append('+')
55
+        version_parts.append(".".join(labels))
56
+
57
+    return "".join(version_parts)
58
+
59
+
60
+def get_version_from_git():
61
+    try:
62
+        p = subprocess.Popen(['git', 'rev-parse', '--show-toplevel'],
63
+                             cwd=distr_root,
64
+                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
65
+    except OSError:
66
+        return
67
+    if p.wait() != 0:
68
+        return
69
+    if not os.path.samefile(p.communicate()[0].decode().rstrip('\n'),
70
+                            distr_root):
71
+        # The top-level directory of the current Git repository is not the same
72
+        # as the root directory of the Kwant distribution: do not extract the
73
+        # version from Git.
74
+        return
75
+
76
+    # git describe --first-parent does not take into account tags from branches
77
+    # that were merged-in.
78
+    for opts in [['--first-parent'], []]:
79
+        try:
80
+            p = subprocess.Popen(['git', 'describe', '--long'] + opts,
81
+                                 cwd=distr_root,
82
+                                 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
83
+        except OSError:
84
+            return
85
+        if p.wait() == 0:
86
+            break
87
+    else:
88
+        return
89
+    description = p.communicate()[0].decode().strip('v').rstrip('\n')
90
+
91
+    release, dev, git = description.rsplit('-', 2)
92
+    labels = []
93
+    if dev == "0":
94
+        dev = None
95
+    else:
96
+        labels.append(git)
97
+
98
+    try:
99
+        p = subprocess.Popen(['git', 'diff', '--quiet'], cwd=distr_root)
100
+    except OSError:
101
+        labels.append('confused') # This should never happen.
102
+    else:
103
+        if p.wait() == 1:
104
+            labels.append('dirty')
105
+
106
+    return Version(release, dev, labels)
107
+
108
+
109
+# TODO: change this logic when there is a git pretty-format
110
+#       that gives the same output as 'git describe'.
111
+#       Currently we can only tell the tag the current commit is
112
+#       pointing to, or its hash (with no version info)
113
+#       if it is not tagged.
114
+def get_version_from_git_archive(version_info):
115
+    try:
116
+        refnames = version_info['refnames']
117
+        git_hash = version_info['git_hash']
118
+    except KeyError:
119
+        # These fields are not present if we are running from an sdist.
120
+        # Execution should never reach here, though
121
+        return None
122
+
123
+    if git_hash.startswith('$Format') or refnames.startswith('$Format'):
124
+        # variables not expanded during 'git archive'
125
+        return None
126
+
127
+    VTAG = 'tag: v'
128
+    refs = set(r.strip() for r in refnames.split(","))
129
+    version_tags = set(r[len(VTAG):] for r in refs if r.startswith(VTAG))
130
+    if version_tags:
131
+        release, *_ = sorted(version_tags)  # prefer e.g. "2.0" over "2.0rc1"
132
+        return Version(release, dev=None, labels=None)
133
+    else:
134
+        return Version('unknown', dev=None, labels=[f'g{git_hash}'])
135
+
136
+
137
+__version__ = get_version()
138
+
139
+# The following section defines a module global 'cmdclass',
140
+# which can be used from setup.py. The 'package_name' and
141
+# '__version__' module globals are used (but not modified).
142
+
143
+def _write_version(fname):
144
+    # This could be a hard link, so try to delete it first.  Is there any way
145
+    # to do this atomically together with opening?
146
+    try:
147
+        os.remove(fname)
148
+    except OSError:
149
+        pass
150
+    with open(fname, 'w') as f:
151
+        f.write("# This file has been created by setup.py.\n"
152
+                "version = '{}'\n".format(__version__))
153
+
154
+
155
+class _build(build_orig):
156
+    def run(self):
157
+        super().run()
158
+        _write_version(os.path.join(self.build_lib, package_name,
159
+                                    STATIC_VERSION_FILE))
160
+
161
+
162
+class _sdist(sdist_orig):
163
+    def make_release_tree(self, base_dir, files):
164
+        super().make_release_tree(base_dir, files)
165
+        _write_version(os.path.join(base_dir, package_name,
166
+                                    STATIC_VERSION_FILE))
167
+
168
+
169
+cmdclass = dict(sdist=_sdist, build=_build)