moat-src 0.6.1__py3-none-any.whl → 0.8.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- moat/src/_main.py +682 -381
- moat/src/inspect.py +3 -0
- moat/src/test.py +10 -7
- {moat_src-0.6.1.dist-info → moat_src-0.8.2.dist-info}/METADATA +6 -6
- moat_src-0.8.2.dist-info/RECORD +9 -0
- {moat_src-0.6.1.dist-info → moat_src-0.8.2.dist-info}/WHEEL +1 -1
- moat_src-0.8.2.dist-info/licenses/LICENSE.txt +14 -0
- moat/src/_hooks/pre-commit +0 -12
- moat/src/_templates/LICENSE.txt +0 -12
- moat/src/_templates/Makefile +0 -15
- moat/src/_templates/gitignore +0 -45
- moat/src/_templates/make/py +0 -108
- moat/src/_templates/moat/__init__.py +0 -5
- moat/src/_templates/pyproject.default.yaml +0 -146
- moat/src/_templates/pyproject.forced.yaml +0 -77
- moat/src/_templates/test_basic_py +0 -11
- moat_src-0.6.1.dist-info/RECORD +0 -17
- {moat_src-0.6.1.dist-info → moat_src-0.8.2.dist-info}/top_level.txt +0 -0
moat/src/_main.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# command line interface
|
2
2
|
# pylint: disable=missing-module-docstring
|
3
|
+
from __future__ import annotations
|
3
4
|
|
4
5
|
import io
|
5
6
|
import logging
|
6
7
|
import subprocess
|
7
8
|
import sys
|
8
|
-
from collections import defaultdict
|
9
|
+
from collections import defaultdict, deque
|
9
10
|
from configparser import RawConfigParser
|
10
11
|
from pathlib import Path
|
11
12
|
|
@@ -15,89 +16,322 @@ import tomlkit
|
|
15
16
|
from anyio import run_process
|
16
17
|
from moat.util import P, add_repr, attrdict, make_proc, yload, yprint
|
17
18
|
from packaging.requirements import Requirement
|
19
|
+
from attrs import define,field
|
20
|
+
from shutil import rmtree,copyfile,copytree
|
21
|
+
from contextlib import suppress
|
18
22
|
|
19
23
|
logger = logging.getLogger(__name__)
|
20
24
|
|
25
|
+
PACK=Path("packaging")
|
26
|
+
ARCH=subprocess.check_output(["dpkg","--print-architecture"]).decode("utf-8").strip()
|
21
27
|
|
22
|
-
|
23
|
-
"""
|
28
|
+
def dash(n:str) -> str:
|
29
|
+
"""
|
30
|
+
moat.foo.bar > foo-bar
|
31
|
+
foo.bar > ext-foo-bar
|
32
|
+
"""
|
33
|
+
if n in ("main","moat"):
|
34
|
+
return "main"
|
35
|
+
if "." not in n: # also applies to single-name packages
|
36
|
+
return n
|
37
|
+
|
38
|
+
if not n.startswith("moat."):
|
39
|
+
return "ext-"+n.replace("-",".")
|
40
|
+
return n.replace(".","-")[5:]
|
41
|
+
|
42
|
+
def undash(n:str) -> str:
|
43
|
+
"""
|
44
|
+
foo-bar > moat.foo.bar
|
45
|
+
ext-foo-bar > foo.bar
|
46
|
+
"""
|
47
|
+
if "." in n:
|
48
|
+
return n
|
49
|
+
|
50
|
+
if n in ("main","moat"):
|
51
|
+
return "moat"
|
52
|
+
if n.startswith("ext-"):
|
53
|
+
return n.replace("-",".")[4:]
|
54
|
+
return "moat."+n.replace("-",".")
|
55
|
+
|
56
|
+
class ChangedError(RuntimeError):
|
57
|
+
def __init__(subsys,tag,head):
|
58
|
+
self.subsys = subsys
|
59
|
+
self.tag = tag
|
60
|
+
self.head = head
|
61
|
+
def __str__(self):
|
62
|
+
s = self.subsys or "Something"
|
63
|
+
if head is None:
|
64
|
+
head="HEAD"
|
65
|
+
else:
|
66
|
+
head = head.hexsha[:9]
|
67
|
+
return f"{s} changed between {tag.name} and {head}"
|
68
|
+
|
69
|
+
class _Common:
|
70
|
+
_last_tag:str = None
|
71
|
+
|
72
|
+
def next_tag(self,major:bool=False,minor:bool=False):
|
73
|
+
tag = self.last_tag()[0]
|
74
|
+
try:
|
75
|
+
n = [ int(x) for x in tag.split('.') ]
|
76
|
+
if len(n) != 3:
|
77
|
+
raise ValueError(n)
|
78
|
+
except ValueError:
|
79
|
+
raise ValueError(f"Tag {tag} not in major#.minor#.patch# form.") from None
|
80
|
+
|
81
|
+
if major:
|
82
|
+
n = [n[0]+1,0,0]
|
83
|
+
elif minor:
|
84
|
+
n = [n[0],n[1]+1,0]
|
85
|
+
else:
|
86
|
+
n = [n[0],n[1],n[2]+1]
|
87
|
+
return ".".join(str(x) for x in n)
|
88
|
+
|
89
|
+
@define
|
90
|
+
class Package(_Common):
|
91
|
+
_repo:Repo = field(repr=False)
|
92
|
+
name:str = field()
|
93
|
+
under:str = field(init=False,repr=False)
|
94
|
+
path:Path = field(init=False,repr=False)
|
95
|
+
files:set(Path) = field(init=False,factory=set,repr=False)
|
96
|
+
subs:dict[str,Package] = field(factory=dict,init=False,repr=False)
|
97
|
+
hidden:bool = field(init=False,repr=False)
|
98
|
+
|
99
|
+
def __init__(self, repo, name):
|
100
|
+
self.__attrs_init__(repo,name)
|
101
|
+
self.under = name.replace(".","_")
|
102
|
+
self.path = Path(*name.split("."))
|
103
|
+
self.hidden = not (PACK/self.dash).exists()
|
104
|
+
|
105
|
+
@property
|
106
|
+
def dash(self):
|
107
|
+
return dash(self.name)
|
108
|
+
|
109
|
+
def __eq__(self, other):
|
110
|
+
return self.name==other.name
|
111
|
+
|
112
|
+
def __hash__(self):
|
113
|
+
return hash(self.name)
|
114
|
+
|
115
|
+
@property
|
116
|
+
def mdash(self):
|
117
|
+
d=dash(self.name)
|
118
|
+
if d.startswith("ext-"):
|
119
|
+
return d[4:]
|
120
|
+
else:
|
121
|
+
return "moat-"+d
|
122
|
+
|
123
|
+
def populate(self, path:Path, real=None):
|
124
|
+
"""
|
125
|
+
Collect this package's file names.
|
126
|
+
"""
|
127
|
+
self.path = path
|
128
|
+
for fn in path.iterdir():
|
129
|
+
if fn.name == "__pycache__":
|
130
|
+
continue
|
131
|
+
if (sb := self.subs.get(fn.name,None)) is not None:
|
132
|
+
sb.populate(fn, real=self if sb.hidden else None)
|
133
|
+
else:
|
134
|
+
(real or self).files.add(fn)
|
135
|
+
|
136
|
+
def copy(self) -> None:
|
137
|
+
"""
|
138
|
+
Copies the current version of this subsystem to its packaging area.
|
139
|
+
"""
|
140
|
+
if not self.files:
|
141
|
+
raise ValueError(f"No files in {self.name}?")
|
142
|
+
p = Path("packaging")/self.dash
|
143
|
+
with suppress(FileNotFoundError):
|
144
|
+
rmtree(p/"moat")
|
145
|
+
dest = p/self.path
|
146
|
+
dest.mkdir(parents=True)
|
147
|
+
for f in self.files:
|
148
|
+
pf=p/f
|
149
|
+
pf.parent.mkdir(parents=True,exist_ok=True)
|
150
|
+
if f.is_dir():
|
151
|
+
copytree(f, pf, symlinks=False)
|
152
|
+
else:
|
153
|
+
copyfile(f, pf, follow_symlinks=True)
|
154
|
+
licd = p/"LICENSE.txt"
|
155
|
+
if not licd.exists():
|
156
|
+
copyfile("LICENSE.txt", licd)
|
157
|
+
|
158
|
+
def last_tag(self, unchanged:bool=False) -> Tag|None:
|
159
|
+
"""
|
160
|
+
Return the most-recent tag for this subrepo
|
161
|
+
"""
|
162
|
+
try:
|
163
|
+
tag,commit = self._repo.versions[self.dash]
|
164
|
+
except KeyError:
|
165
|
+
raise KeyError(f"No version for {self.dash} found") from None
|
166
|
+
if unchanged and self.has_changes(commit):
|
167
|
+
raise ChangedError(subsys,t,ref)
|
168
|
+
return tag,commit
|
169
|
+
|
170
|
+
def has_changes(self, tag:Commit=None) -> bool:
|
171
|
+
"""
|
172
|
+
Test whether the given subsystem (or any subsystem)
|
173
|
+
changed between the head and the @tag commit
|
174
|
+
"""
|
175
|
+
if tag is None:
|
176
|
+
tag,commit = self.last_tag()
|
177
|
+
else:
|
178
|
+
commit = tag
|
179
|
+
head = self._repo.head.commit
|
180
|
+
for d in head.diff(commit):
|
181
|
+
if self._repo.repo_for(d.a_path) != self.name and self._repo.repo_for(d.b_path) != self.name:
|
182
|
+
continue
|
183
|
+
return True
|
184
|
+
return False
|
185
|
+
|
186
|
+
|
187
|
+
class Repo(git.Repo,_Common):
|
188
|
+
"""Amend git.Repo with tag caching and pseudo-submodule splitting"""
|
24
189
|
|
25
190
|
moat_tag = None
|
26
|
-
|
191
|
+
_last_tag=None
|
27
192
|
|
28
193
|
def __init__(self, *a, **k):
|
29
194
|
super().__init__(*a, **k)
|
30
|
-
self._subrepo_cache = {}
|
31
195
|
self._commit_tags = defaultdict(list)
|
32
196
|
self._commit_topo = {}
|
33
197
|
|
198
|
+
self._repos = {}
|
199
|
+
self._make_repos()
|
200
|
+
|
34
201
|
for t in self.tags:
|
35
202
|
self._commit_tags[t.commit].append(t)
|
36
203
|
|
37
204
|
p = Path(self.working_dir)
|
38
205
|
mi = p.parts.index("moat")
|
39
206
|
self.moat_name = "-".join(p.parts[mi:])
|
207
|
+
with open("versions.yaml") as f:
|
208
|
+
self.versions = yload(f)
|
40
209
|
|
41
|
-
def
|
42
|
-
|
210
|
+
def write_tags(self):
|
211
|
+
with open("versions.yaml","w") as f:
|
212
|
+
yprint(self.versions,f)
|
213
|
+
self.index.add("versions.yaml")
|
43
214
|
|
44
|
-
if same and recurse and not depth:
|
45
|
-
yield self
|
46
215
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
continue
|
58
|
-
res.submod = r
|
59
|
-
if recurse:
|
60
|
-
yield from res.subrepos(depth=depth)
|
61
|
-
else:
|
62
|
-
yield res
|
216
|
+
def last_tag(self, unchanged:bool=False) -> Tag|None:
|
217
|
+
"""
|
218
|
+
Return the most-recent tag for this repo
|
219
|
+
"""
|
220
|
+
if self._last_tag is not None:
|
221
|
+
return self._last_tag,self._last_tag
|
222
|
+
for c in self._repo.commits(self.head.commit):
|
223
|
+
t = self.tagged(c)
|
224
|
+
if t is None:
|
225
|
+
continue
|
63
226
|
|
64
|
-
|
65
|
-
|
227
|
+
self._last_tag = t
|
228
|
+
if unchanged and self.has_changes(c):
|
229
|
+
raise ChangedError(subsys,t)
|
230
|
+
return t,t
|
231
|
+
|
232
|
+
raise ValueError(f"No tags found")
|
233
|
+
|
234
|
+
def part(self, name):
|
235
|
+
return self._repos[dash(name)]
|
236
|
+
|
237
|
+
@property
|
238
|
+
def _repo(self):
|
239
|
+
return self
|
240
|
+
|
241
|
+
@property
|
242
|
+
def parts(self):
|
243
|
+
return self._repos.values()
|
244
|
+
|
245
|
+
def tags_of(self, c:Commit) -> Sequence[Tag]:
|
246
|
+
return self._commit_tags[c]
|
247
|
+
|
248
|
+
def _add_repo(self, name):
|
249
|
+
dn = dash(name)
|
250
|
+
pn = undash(name)
|
251
|
+
if dn in self._repos:
|
252
|
+
return self._repos[dn]
|
253
|
+
|
254
|
+
p = Package(self,pn)
|
255
|
+
self._repos[dn] = p
|
256
|
+
if "." in pn:
|
257
|
+
par,nam = pn.rsplit(".",1)
|
258
|
+
pp = self._add_repo(par)
|
259
|
+
pp.subs[nam] = p
|
260
|
+
return p
|
261
|
+
|
262
|
+
def _make_repos(self) -> dict:
|
263
|
+
"""Collect subrepos"""
|
264
|
+
for fn in Path("packaging").iterdir():
|
265
|
+
if fn.name == "main":
|
266
|
+
continue
|
267
|
+
if not fn.is_dir() or "." in fn.name:
|
268
|
+
continue
|
269
|
+
self._add_repo(str(fn.name))
|
270
|
+
|
271
|
+
self._repos["main"].populate(Path("moat"))
|
272
|
+
|
273
|
+
def repo_for(self, path:Path|str) -> str:
|
274
|
+
"""
|
275
|
+
Given a file path, returns the subrepo in question
|
276
|
+
"""
|
277
|
+
name = "moat"
|
278
|
+
sc = self._repos["main"]
|
279
|
+
path=Path(path)
|
280
|
+
try:
|
281
|
+
if path.parts[0] == "packaging":
|
282
|
+
return path.parts[1].replace("-",".")
|
283
|
+
except KeyError:
|
284
|
+
return name
|
285
|
+
|
286
|
+
if path.parts[0] != "moat":
|
287
|
+
return None
|
288
|
+
|
289
|
+
for p in path.parts[1:]:
|
290
|
+
if p in sc.subs:
|
291
|
+
name += "."+p
|
292
|
+
sc = sc.subs[p]
|
293
|
+
else:
|
294
|
+
break
|
295
|
+
return name
|
66
296
|
|
67
297
|
def commits(self, ref=None):
|
68
|
-
"""Iterate over topo sort of commits following @ref, or HEAD
|
298
|
+
"""Iterate over topo sort of commits following @ref, or HEAD.
|
299
|
+
|
300
|
+
WARNING: this code does not do a true topological breadth-first
|
301
|
+
search. Doesn't matter much for simple merges that are based on
|
302
|
+
a mostly-current checkout, but don't expect correctness when branches
|
303
|
+
span tags.
|
304
|
+
"""
|
69
305
|
if ref is None:
|
70
306
|
ref = self.head.commit
|
71
|
-
try:
|
72
|
-
res = self._commit_topo[ref]
|
73
|
-
except KeyError:
|
74
|
-
visited = set()
|
75
|
-
res = []
|
76
|
-
|
77
|
-
def _it(c):
|
78
|
-
return iter(sorted(c.parents, key=lambda x: x.committed_date))
|
79
|
-
|
80
|
-
work = [(ref, _it(ref))]
|
81
|
-
|
82
|
-
while work:
|
83
|
-
c, gen = work.pop()
|
84
|
-
visited.add(c)
|
85
|
-
for n in gen:
|
86
|
-
if n not in visited:
|
87
|
-
work.append((c, gen))
|
88
|
-
work.append((n, _it(n)))
|
89
|
-
break
|
90
|
-
else:
|
91
|
-
res.append(c)
|
92
|
-
self._commit_topo[ref] = res
|
93
307
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
308
|
+
visited = set()
|
309
|
+
work = deque([ref])
|
310
|
+
while work:
|
311
|
+
ref = work.popleft()
|
312
|
+
if ref in visited:
|
313
|
+
continue
|
314
|
+
visited.add(ref)
|
315
|
+
yield ref
|
316
|
+
work.extend(ref.parents)
|
317
|
+
|
318
|
+
def has_changes(self, tag:Tag=None) -> bool:
|
319
|
+
"""
|
320
|
+
Test whether any subsystem changed since the "tagged" commit
|
321
|
+
|
322
|
+
"""
|
323
|
+
if tag is None:
|
324
|
+
tag,commit = self.last_tag()
|
325
|
+
head = self._repo.head.commit
|
326
|
+
for d in head.diff(tag):
|
327
|
+
if self.repo_for(d.a_path) == "moat" and self.repo_for(d.b_path) == "moat":
|
328
|
+
continue
|
329
|
+
return True
|
330
|
+
return False
|
98
331
|
|
99
|
-
|
100
|
-
|
332
|
+
|
333
|
+
def tagged(self, c:Commit=None) -> Tag|None:
|
334
|
+
"""Return a commit's tag name.
|
101
335
|
Defaults to the head commit.
|
102
336
|
Returns None if no tag, raises ValueError if more than one is found.
|
103
337
|
"""
|
@@ -106,9 +340,17 @@ class Repo(git.Repo):
|
|
106
340
|
if c not in self._commit_tags:
|
107
341
|
return None
|
108
342
|
tt = self._commit_tags[c]
|
343
|
+
|
344
|
+
tt = [t for t in tt if "/" not in t.name]
|
345
|
+
|
346
|
+
if not tt:
|
347
|
+
return None
|
109
348
|
if len(tt) > 1:
|
110
|
-
|
111
|
-
|
349
|
+
if subsys is not None:
|
350
|
+
raise ValueError(f"Multiple tags for {subsys}: {tt}")
|
351
|
+
raise ValueError(f"Multiple tags: {tt}")
|
352
|
+
return tt[0].name
|
353
|
+
|
112
354
|
|
113
355
|
|
114
356
|
@click.group(short_help="Manage MoaT itself")
|
@@ -116,7 +358,7 @@ async def cli():
|
|
116
358
|
"""
|
117
359
|
This collection of commands is useful for managing and building MoaT itself.
|
118
360
|
"""
|
119
|
-
pass
|
361
|
+
pass
|
120
362
|
|
121
363
|
|
122
364
|
def fix_deps(deps: list[str], tags: dict[str, str]) -> bool:
|
@@ -132,17 +374,22 @@ def fix_deps(deps: list[str], tags: dict[str, str]) -> bool:
|
|
132
374
|
return work
|
133
375
|
|
134
376
|
|
135
|
-
def run_tests(
|
136
|
-
"""Run
|
377
|
+
def run_tests(pkg: str|None, *opts) -> bool:
|
378
|
+
"""Run subtests for subpackage @pkg."""
|
379
|
+
|
380
|
+
if pkg is None:
|
381
|
+
tests = Path("tests")
|
382
|
+
else:
|
383
|
+
tests = dash(pkg).replace("-","_")
|
384
|
+
tests = Path("tests")/tests
|
137
385
|
|
138
|
-
|
139
|
-
|
140
|
-
# No Makefile. Assume it's OK.
|
386
|
+
if not Path(tests):
|
387
|
+
# No tests. Assume it's OK.
|
141
388
|
return True
|
142
389
|
try:
|
143
|
-
print("\n*** Testing:",
|
390
|
+
print("\n*** Testing:", pkg)
|
144
391
|
# subprocess.run(["python3", "-mtox"], cwd=repo.working_dir, check=True)
|
145
|
-
subprocess.run(["
|
392
|
+
subprocess.run(["python3","-mpytest", *opts, tests], stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr, check=True)
|
146
393
|
except subprocess.CalledProcessError:
|
147
394
|
return False
|
148
395
|
else:
|
@@ -269,8 +516,9 @@ def encomma(proj, path):
|
|
269
516
|
"""list > comma-delimited string"""
|
270
517
|
_mangle(proj, path, lambda x: ",".join(x)) # pylint: disable=unnecessary-lambda
|
271
518
|
|
519
|
+
|
272
520
|
def apply_hooks(repo, force=False):
|
273
|
-
h = Path(repo.git_dir)/"hooks"
|
521
|
+
h = Path(repo.git_dir) / "hooks"
|
274
522
|
drop = set()
|
275
523
|
seen = set()
|
276
524
|
for f in h.iterdir():
|
@@ -281,12 +529,12 @@ def apply_hooks(repo, force=False):
|
|
281
529
|
for f in drop:
|
282
530
|
f.unlink()
|
283
531
|
|
284
|
-
pt =
|
532
|
+
pt = Path(__file__).parent / "_hooks"
|
285
533
|
for f in pt.iterdir():
|
286
534
|
if not force:
|
287
535
|
if f.name in seen:
|
288
536
|
continue
|
289
|
-
t = h/f.name
|
537
|
+
t = h / f.name
|
290
538
|
d = f.read_text()
|
291
539
|
t.write_text(d)
|
292
540
|
t.chmod(0o755)
|
@@ -382,8 +630,9 @@ def apply_templates(repo):
|
|
382
630
|
txi = "\n" + txi.replace("\n\t", "\n ")
|
383
631
|
proj["tool"]["tox"] = dict(
|
384
632
|
legacy_tox_ini=tomlkit.items.String.from_raw(
|
385
|
-
txi,
|
386
|
-
|
633
|
+
txi,
|
634
|
+
type_=tomlkit.items.StringType.MLB,
|
635
|
+
),
|
387
636
|
)
|
388
637
|
|
389
638
|
projp = Path(repo.working_dir) / "pyproject.toml"
|
@@ -445,386 +694,438 @@ def path_():
|
|
445
694
|
print(Path(__file__).parent / "_templates")
|
446
695
|
|
447
696
|
|
448
|
-
@cli.command(
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
)
|
453
|
-
@click.option("-A", "--amend", is_flag=True, help="Fix previous commit (DANGER)")
|
454
|
-
@click.option("-N", "--no-amend", is_flag=True, help="Don't fix prev commit even if same text")
|
455
|
-
@click.option("-D", "--no-dirty", is_flag=True, help="don't check for dirtiness (DANGER)")
|
456
|
-
@click.option("-C", "--no-commit", is_flag=True, help="don't commit")
|
457
|
-
@click.option("-s", "--skip", type=str, multiple=True, help="skip this repo")
|
458
|
-
@click.option("--hooks", is_flag=True, help="only update hooks")
|
459
|
-
@click.option("--HOOKS", "fhooks", is_flag=True, help="force-update hooks")
|
460
|
-
@click.option(
|
461
|
-
"-m",
|
462
|
-
"--message",
|
463
|
-
type=str,
|
464
|
-
help="commit message if changed",
|
465
|
-
default="Update from MoaT template",
|
466
|
-
)
|
467
|
-
@click.option("-o", "--only", type=str, multiple=True, help="affect only this repo")
|
468
|
-
async def setup(no_dirty, no_commit, skip, only, message, amend, no_amend, hooks, fhooks):
|
469
|
-
"""
|
470
|
-
Set up projects using templates.
|
471
|
-
|
472
|
-
Default: amend if the text is identical and the prev head isn't tagged.
|
473
|
-
"""
|
474
|
-
repo = Repo()
|
475
|
-
skip = set(skip)
|
476
|
-
if only:
|
477
|
-
repos = (Repo(x) for x in only)
|
478
|
-
else:
|
479
|
-
repos = (x for x in repo.subrepos(depth=True) if x.moat_name[5:] not in skip)
|
697
|
+
@cli.command()
|
698
|
+
@click.option("-s", "--show", is_flag=True, help="Show all tags")
|
699
|
+
@click.option("-r", "--run", is_flag=True, help="Update all stale tags")
|
700
|
+
def tags(show,run):
|
701
|
+
repo = Repo(None)
|
480
702
|
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
print(r.working_dir)
|
485
|
-
continue
|
703
|
+
if show:
|
704
|
+
if run:
|
705
|
+
raise click.UsageError("Can't display and change the tag at the same time!")
|
486
706
|
|
487
|
-
|
488
|
-
|
707
|
+
for r in repo.parts:
|
708
|
+
try:
|
709
|
+
tag,commit = r.last_tag()
|
710
|
+
except ValueError:
|
711
|
+
print(f"{r.dash} -")
|
489
712
|
continue
|
713
|
+
if r.has_changes(commit):
|
714
|
+
print(f"{r.dash} {tag} STALE")
|
715
|
+
else:
|
716
|
+
print(f"{r.dash} {tag}")
|
717
|
+
return
|
490
718
|
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
719
|
+
if repo.is_dirty(index=True, working_tree=True, untracked_files=True, submodules=False):
|
720
|
+
print("Repo is dirty. Not tagging globally.", file=sys.stderr)
|
721
|
+
return
|
722
|
+
|
723
|
+
changed=False
|
724
|
+
for r in repo.parts:
|
725
|
+
tag,commit = r.last_tag()
|
726
|
+
if not r.has_changes(commit):
|
727
|
+
print(repo.dash,tag,"UNCHANGED")
|
496
728
|
continue
|
497
729
|
|
498
|
-
|
730
|
+
tag = r.next_tag()
|
731
|
+
print(repo.dash,tag)
|
732
|
+
if not run:
|
499
733
|
continue
|
500
734
|
|
501
|
-
|
502
|
-
|
503
|
-
a = False
|
504
|
-
elif amend:
|
505
|
-
a = True
|
506
|
-
else:
|
507
|
-
a = r.head.commit.message == message
|
508
|
-
|
509
|
-
for rr in r.subrepos(recurse=False):
|
510
|
-
# This part updates the supermodule's SHA entries so they
|
511
|
-
# point to the submodule#s current HEAD, assuming it is
|
512
|
-
# clean
|
513
|
-
if not rr.is_dirty(index=True, working_tree=True, submodules=True):
|
514
|
-
rrp = rr.submod.path
|
515
|
-
rri = rr.head.commit.hexsha
|
516
|
-
ri = r.index.entries[(rrp, 0)].hexsha
|
517
|
-
|
518
|
-
if rri == ri:
|
519
|
-
continue
|
520
|
-
sn = git.objects.submodule.base.Submodule(
|
521
|
-
r,
|
522
|
-
rr.head.commit.binsha,
|
523
|
-
name=rr.submod.name,
|
524
|
-
path=rr.submod.path,
|
525
|
-
mode=rr.submod.mode,
|
526
|
-
)
|
527
|
-
print("Submodule update:", rrp)
|
528
|
-
r.index.add([sn]) # doesn't work, SIGH
|
529
|
-
if a:
|
530
|
-
p = r.head.commit.parents
|
531
|
-
else:
|
532
|
-
p = (r.head.commit,)
|
735
|
+
repo.versions[r.dash] = (tag,repo.head.commit.hexsha[:9])
|
736
|
+
changed=True
|
533
737
|
|
534
|
-
|
738
|
+
if changed:
|
739
|
+
repo.write_tags()
|
535
740
|
|
536
741
|
|
537
742
|
@cli.command()
|
538
|
-
@click.option("-
|
539
|
-
@click.option("-
|
540
|
-
@click.option("-
|
541
|
-
@click.option("-
|
542
|
-
@click.option("-
|
543
|
-
|
743
|
+
@click.option("-r", "--run", is_flag=True, help="actually do the tagging")
|
744
|
+
@click.option("-m", "--minor", is_flag=True, help="create a new minor version")
|
745
|
+
@click.option("-M", "--major", is_flag=True, help="create a new major version")
|
746
|
+
@click.option("-s", "--subtree", type=str, help="Tag this partial module")
|
747
|
+
@click.option("-v", "--tag", "force", type=str, help="Use this explicit tag value")
|
748
|
+
@click.option("-q", "--query","--show","show", is_flag=True, help="Show the latest tag")
|
749
|
+
@click.option("-f", "--force","FORCE", is_flag=True, help="replace an existing tag")
|
750
|
+
def tag(run,minor,major,subtree,force,FORCE,show):
|
544
751
|
"""
|
545
|
-
|
752
|
+
Tag the repository (or a subtree).
|
546
753
|
"""
|
547
|
-
|
548
|
-
|
549
|
-
if
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
for r in repos:
|
556
|
-
p = Path(r.working_dir) / "pyproject.toml"
|
557
|
-
if not p.is_file():
|
558
|
-
continue
|
559
|
-
print(r.working_dir)
|
560
|
-
args = ["-d", deb] if deb else []
|
561
|
-
subprocess.run(["merge-to-deb"] + args, cwd=r.working_dir, check=True)
|
754
|
+
if minor and major:
|
755
|
+
raise click.UsageError("Can't change both minor and major!")
|
756
|
+
if force and (minor or major):
|
757
|
+
raise click.UsageError("Can't use an explicit tag with changing minor or major!")
|
758
|
+
if FORCE and (minor or major):
|
759
|
+
raise click.UsageError("Can't use an explicit tag with changing minor or major!")
|
760
|
+
if show and (run or force or minor or major):
|
761
|
+
raise click.UsageError("Can't display and change the tag at the same time!")
|
562
762
|
|
563
|
-
|
564
|
-
for r in repos:
|
565
|
-
p = Path(r.working_dir) / "pyproject.toml"
|
566
|
-
if not p.is_file():
|
567
|
-
continue
|
568
|
-
print(r.working_dir)
|
569
|
-
subprocess.run(["make", "pypi"], cwd=r.working_dir, check=True)
|
763
|
+
repo = Repo(None)
|
570
764
|
|
765
|
+
if subtree:
|
766
|
+
r = repo.part(subtree)
|
767
|
+
else:
|
768
|
+
r = repo
|
571
769
|
|
572
|
-
|
573
|
-
|
574
|
-
|
770
|
+
if show:
|
771
|
+
tag,commit = r.last_tag()
|
772
|
+
if r.has_changes(commit):
|
773
|
+
print(f"{tag} STALE")
|
774
|
+
else:
|
775
|
+
print(tag)
|
776
|
+
return
|
575
777
|
|
576
|
-
|
577
|
-
|
578
|
-
|
778
|
+
if force:
|
779
|
+
tag = force
|
780
|
+
elif FORCE:
|
781
|
+
tag,_ = r.last_tag()
|
782
|
+
else:
|
783
|
+
tag = r.next_tag(major,minor)
|
579
784
|
|
580
|
-
|
581
|
-
if
|
582
|
-
|
583
|
-
|
584
|
-
return
|
585
|
-
if "moat" in r.refs:
|
586
|
-
m = r.refs["moat"]
|
785
|
+
if run or subtree:
|
786
|
+
if subtree:
|
787
|
+
repo.versions[r.dash] = (tag,repo.head.commit.hexsha[:9])
|
788
|
+
repo.write_tags()
|
587
789
|
else:
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
input=None,
|
593
|
-
stderr=sys.stderr,
|
594
|
-
)
|
595
|
-
ref = ch.stdout.decode().strip()
|
596
|
-
if ref != m.commit.hexsha:
|
597
|
-
print(f"{r.working_dir}: need merge", file=sys.stderr)
|
598
|
-
return
|
599
|
-
m.set_reference(ref=r.head.commit, logmsg="fix_main")
|
600
|
-
r.head.set_reference(ref=m, logmsg="fix_main 2")
|
601
|
-
|
602
|
-
for r in repo.subrepos():
|
603
|
-
await _fix(r)
|
790
|
+
git.TagReference.create(repo,tag, force=FORCE)
|
791
|
+
print(f"{tag}")
|
792
|
+
else:
|
793
|
+
print(f"{tag} DRY_RUN")
|
604
794
|
|
605
795
|
|
606
796
|
@cli.command()
|
607
|
-
|
797
|
+
@click.option("-P", "--no-pypi", is_flag=True, help="don't push to PyPi")
|
798
|
+
@click.option("-D", "--no-deb", is_flag=True, help="don't debianize")
|
799
|
+
@click.option("-d", "--deb", type=str, help="Debian archive to push to (from dput.cfg)")
|
800
|
+
@click.option("-o", "--only", type=str, multiple=True, help="affect only this package")
|
801
|
+
@click.option("-s", "--skip", type=str, multiple=True, help="skip this package")
|
802
|
+
async def publish(no_pypi, no_deb, skip, only, deb):
|
608
803
|
"""
|
609
|
-
|
610
|
-
|
611
|
-
Submodules frequently have detached HEADs. This command resets "main"
|
612
|
-
to them, but only if that is a fast-forward operation.
|
804
|
+
Publish modules to PyPi and/or Debian.
|
613
805
|
|
614
|
-
|
615
|
-
the
|
806
|
+
MoaT modules can be given as shorthand, i.e. with dashes and excluding
|
807
|
+
the "moat-" prefix.
|
616
808
|
"""
|
617
809
|
repo = Repo(None)
|
618
|
-
|
810
|
+
if only and skip:
|
811
|
+
raise click.UsageError("You can't both include and exclude packages.")
|
619
812
|
|
813
|
+
if only:
|
814
|
+
repos = (repo.subrepo(x) for x in only)
|
815
|
+
else:
|
816
|
+
s = set()
|
817
|
+
for sk in skip:
|
818
|
+
s += set(sk.split(","))
|
819
|
+
repos = (x for x in repo.parts if dash(x.name) not in sk)
|
620
820
|
|
621
|
-
|
622
|
-
@click.option("-r", "--remote", type=str, help="Remote. Default: all.", default="--all")
|
623
|
-
@click.pass_obj
|
624
|
-
async def push(obj, remote):
|
625
|
-
"""Push the current state"""
|
821
|
+
deb_args = "-b -us -uc".split()
|
626
822
|
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
cmd.append("-q")
|
633
|
-
elif obj.debug > 1:
|
634
|
-
cmd.append("-v")
|
635
|
-
cmd.append(remote)
|
636
|
-
await run_process(cmd, input=None, stdout=sys.stdout, stderr=sys.stderr)
|
637
|
-
|
638
|
-
except subprocess.CalledProcessError as exc:
|
639
|
-
print(" Error in", r.working_dir, file=sys.stderr)
|
640
|
-
sys.exit(exc.returncode)
|
823
|
+
for r in repos:
|
824
|
+
t,c = r.last_tag
|
825
|
+
if r.has_changes(c):
|
826
|
+
print(f"Error: changes in {r.name} since tag {t.name}")
|
827
|
+
continue
|
641
828
|
|
829
|
+
print(f"Processing {r.name}, tag: {t.name}")
|
830
|
+
r.copy()
|
831
|
+
rd=PACK/r.dash
|
642
832
|
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
"""Fetch updates"""
|
833
|
+
if not no_deb:
|
834
|
+
p = rd / "debian"
|
835
|
+
if not p.is_dir():
|
836
|
+
continue
|
837
|
+
subprocess.run(["debuild"] + deb_args, cwd=rd, check=True)
|
649
838
|
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
cmd.append("-v")
|
658
|
-
if remote is not None:
|
659
|
-
cmd.append(remote)
|
660
|
-
await run_process(cmd, input=None, stdout=sys.stdout, stderr=sys.stderr)
|
661
|
-
|
662
|
-
cmd = ["git", "-C", r.working_dir, "merge", "--ff"]
|
663
|
-
if not obj.debug:
|
664
|
-
cmd.append("-q")
|
665
|
-
elif obj.debug > 1:
|
666
|
-
cmd.append("-v")
|
667
|
-
if remote is not None:
|
668
|
-
if branch is None:
|
669
|
-
branch = "moat" if "moat" in r.refs else "main"
|
670
|
-
cmd.append(f"{remote}/{branch}")
|
671
|
-
await run_process(cmd, input=None, stdout=sys.stdout, stderr=sys.stderr)
|
672
|
-
|
673
|
-
except subprocess.CalledProcessError as exc:
|
674
|
-
print(" Error in", r.working_dir, file=sys.stderr)
|
675
|
-
sys.exit(exc.returncode)
|
839
|
+
if not no_pypi:
|
840
|
+
for r in repos:
|
841
|
+
p = Path(r.working_dir) / "pyproject.toml"
|
842
|
+
if not p.is_file():
|
843
|
+
continue
|
844
|
+
print(r.working_dir)
|
845
|
+
subprocess.run(["make", "pypi"], cwd=r.working_dir, check=True)
|
676
846
|
|
677
847
|
|
678
|
-
@cli.command(
|
679
|
-
|
848
|
+
@cli.command(epilog="""
|
849
|
+
The default for building Debian packages is '--no-sign --build=binary'.
|
850
|
+
'--no-sign' is dropped when you use '--deb'.
|
851
|
+
The binary-only build is currently unconditional.
|
852
|
+
|
853
|
+
The default for uploading to Debian via 'dput' is '--unchecked ext';
|
854
|
+
it is dropped when you use '--dput'.
|
855
|
+
""")
|
856
|
+
@click.option("-f", "--no-dirty", is_flag=True, help="don't check for dirtiness (DANGER)")
|
857
|
+
@click.option("-F", "--no-tag", is_flag=True, help="don't check for tag uptodate-ness (DANGER)")
|
858
|
+
@click.option("-D", "--no-deb", is_flag=True, help="don't build Debian packages")
|
859
|
+
@click.option("-C", "--no-commit", is_flag=True, help="don't commit the result")
|
860
|
+
@click.option("-V", "--no-version", is_flag=True, help="don't update dependency versions in pyproject files")
|
861
|
+
@click.option("-P", "--no-pypi", is_flag=True, help="don't push to PyPI")
|
862
|
+
@click.option("-T", "--no-test", is_flag=True, help="don't run tests")
|
863
|
+
@click.option("-o", "--pytest", "pytest_opts", type=str,multiple=True, help="Options for pytest")
|
864
|
+
@click.option("-d", "--deb", "deb_opts", type=str,multiple=True, help="Options for debuild")
|
865
|
+
@click.option("-p", "--dput", "dput_opts", type=str,multiple=True, help="Options for dput")
|
866
|
+
@click.option("-r", "--run", is_flag=True, help="actually do the tagging")
|
867
|
+
@click.option("-s", "--skip", "skip_", type=str,multiple=True, help="skip these repos")
|
868
|
+
@click.option("-m", "--minor", is_flag=True, help="create a new minor version")
|
869
|
+
@click.option("-M", "--major", is_flag=True, help="create a new major version")
|
870
|
+
@click.option("-t", "--tag", "forcetag", type=str, help="Use this explicit tag value")
|
680
871
|
@click.option(
|
681
872
|
"-v",
|
682
873
|
"--version",
|
683
874
|
type=(str, str),
|
684
875
|
multiple=True,
|
685
|
-
help="Update external
|
876
|
+
help="Update external dependency",
|
686
877
|
)
|
687
|
-
@click.
|
688
|
-
|
689
|
-
@click.option("-c", "--cache", is_flag=True, help="don't re-test if unchanged")
|
690
|
-
async def build(version, no_test, no_commit, no_dirty, cache):
|
878
|
+
@click.argument("parts", nargs=-1)
|
879
|
+
async def build(no_commit, no_dirty, no_test, no_tag, no_pypi, parts, dput_opts, pytest_opts, deb_opts, run, version, no_version, no_deb, skip_, major,minor,forcetag):
|
691
880
|
"""
|
692
881
|
Rebuild all modified packages.
|
693
882
|
"""
|
694
|
-
bad = False
|
695
883
|
repo = Repo(None)
|
884
|
+
|
696
885
|
tags = dict(version)
|
697
886
|
skip = set()
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
if
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
if not
|
719
|
-
|
720
|
-
|
721
|
-
break
|
887
|
+
for s in skip_:
|
888
|
+
for sn in s.split(","):
|
889
|
+
skip.add(dash(sn))
|
890
|
+
parts = set(dash(s) for s in parts)
|
891
|
+
debversion={}
|
892
|
+
|
893
|
+
if no_tag and not no_version:
|
894
|
+
print("Warning: not updating moat versions in pyproject files", file=sys.stderr)
|
895
|
+
if minor and major:
|
896
|
+
raise click.UsageError("Can't change both minor and major!")
|
897
|
+
if forcetag and (minor or major):
|
898
|
+
raise click.UsageError("Can't use an explicit tag with changing minor or major!")
|
899
|
+
|
900
|
+
if forcetag is None:
|
901
|
+
forcetag = repo.next_tag(major,minor)
|
902
|
+
|
903
|
+
full = False
|
904
|
+
if parts:
|
905
|
+
repos = [ repo.part(x) for x in parts ]
|
906
|
+
else:
|
907
|
+
if not skip:
|
908
|
+
full = True
|
909
|
+
repos = [ x for x in repo.parts if not x.hidden and x.dash not in skip and not (PACK/x.dash/"SKIP").exists() ]
|
722
910
|
|
723
|
-
|
724
|
-
|
725
|
-
if r.moat_name != "src":
|
726
|
-
bad = True
|
911
|
+
for name in PACK.iterdir():
|
912
|
+
if name.suffix != ".changes":
|
727
913
|
continue
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
if r.is_dirty(index=False, working_tree=False, untracked_files=False, submodules=True):
|
733
|
-
t = None
|
734
|
-
|
735
|
-
if t is None:
|
736
|
-
for c in r.commits():
|
737
|
-
t = r.tagged(c)
|
738
|
-
if t is not None:
|
739
|
-
break
|
740
|
-
else:
|
741
|
-
print("NOTAG", t, r.moat_name)
|
742
|
-
bad = True
|
743
|
-
continue
|
744
|
-
print("UNTAGGED", t, r.moat_name)
|
745
|
-
xt, t = t.name.rsplit(".", 1)
|
746
|
-
t = f"{xt}.{int(t)+1}"
|
747
|
-
# t = r.create_tag(t)
|
748
|
-
# do not create the tag yet
|
914
|
+
name=name.stem
|
915
|
+
name,vers,_ = name.split("_")
|
916
|
+
if name.startswith("moat-"):
|
917
|
+
name = name[5:]
|
749
918
|
else:
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
919
|
+
name = "ext-"+name
|
920
|
+
debversion[name]=vers.rsplit("-",1)[0]
|
921
|
+
|
922
|
+
|
923
|
+
# Step 0: basic check
|
924
|
+
if not no_dirty:
|
925
|
+
if repo.is_dirty(index=False, working_tree=True, untracked_files=True, submodules=False):
|
926
|
+
if not run:
|
927
|
+
print("*** Repository is not clean.", file=sys.stderr)
|
928
|
+
else:
|
929
|
+
print("Please commit changes and try again.", file=sys.stderr)
|
930
|
+
return
|
762
931
|
|
763
|
-
check
|
932
|
+
# Step 1: check for changed files since last tagging
|
933
|
+
if not no_tag:
|
934
|
+
err = set()
|
935
|
+
for r in repos:
|
936
|
+
try:
|
937
|
+
tag,commit = r.last_tag()
|
938
|
+
except KeyError:
|
939
|
+
rd = PACK/r.dash
|
940
|
+
p = rd / "pyproject.toml"
|
941
|
+
if not p.is_file():
|
942
|
+
continue
|
943
|
+
raise
|
944
|
+
tags[r.mdash] = tag
|
945
|
+
if r.has_changes(commit):
|
946
|
+
err.add(r.dash)
|
947
|
+
if err:
|
948
|
+
if not run:
|
949
|
+
print("*** Untagged changes:", file=sys.stderr)
|
950
|
+
print("***", *err, file=sys.stderr)
|
951
|
+
else:
|
952
|
+
print("Untagged changes:", file=sys.stderr)
|
953
|
+
print(*err, file=sys.stderr)
|
954
|
+
print("Please tag (moat src tag -s PACKAGE) and try again.", file=sys.stderr)
|
955
|
+
return
|
764
956
|
|
765
|
-
|
766
|
-
|
957
|
+
# Step 2: run tests
|
958
|
+
if not no_test:
|
959
|
+
fails = set()
|
960
|
+
for p in parts:
|
961
|
+
if not run_tests(p, *pytest_opts):
|
962
|
+
fails.add(p.name)
|
963
|
+
if fails:
|
964
|
+
if not run:
|
965
|
+
print(f"*** Tests failed:", *fails, file=sys.stderr)
|
966
|
+
else:
|
967
|
+
print(f"Failed tests:", *fails, file=sys.stderr)
|
968
|
+
print(f"Fix and try again.", file=sys.stderr)
|
969
|
+
return
|
767
970
|
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
971
|
+
# Step 3: set version and fix versioned dependencies
|
972
|
+
for r in repos:
|
973
|
+
rd = PACK/r.dash
|
974
|
+
p = rd / "pyproject.toml"
|
975
|
+
if not p.is_file():
|
976
|
+
# bad=True
|
977
|
+
print("Skip:", r.name, file=sys.stderr)
|
978
|
+
continue
|
979
|
+
with p.open("r") as f:
|
980
|
+
pr = tomlkit.load(f)
|
981
|
+
pr["project"]["version"] = r.last_tag()[0]
|
779
982
|
|
780
|
-
|
983
|
+
if not no_version:
|
781
984
|
try:
|
782
985
|
deps = pr["project"]["dependencies"]
|
783
986
|
except KeyError:
|
784
987
|
pass
|
785
988
|
else:
|
786
|
-
|
989
|
+
fix_deps(deps, tags)
|
787
990
|
try:
|
788
991
|
deps = pr["project"]["optional_dependencies"]
|
789
992
|
except KeyError:
|
790
993
|
pass
|
791
994
|
else:
|
792
995
|
for v in deps.values():
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
r.index.add(p)
|
798
|
-
if work or r.is_dirty(index=False, working_tree=False, submodules=True):
|
799
|
-
dirty.add(r)
|
800
|
-
t = tags[r.moat_name]
|
801
|
-
if not isinstance(t, str):
|
802
|
-
xt, t = t.name.rsplit(".", 1)
|
803
|
-
t = f"{xt}.{int(t)+1}"
|
804
|
-
tags[r.moat_name] = t
|
805
|
-
check = True
|
806
|
-
|
807
|
-
if bad:
|
808
|
-
print("Partial work done. Fix and try again.")
|
809
|
-
return
|
996
|
+
fix_deps(v, tags)
|
997
|
+
|
998
|
+
p.write_text(pr.as_string())
|
999
|
+
repo.index.add(p)
|
810
1000
|
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
1001
|
+
# Step 3: copy to packaging dir
|
1002
|
+
for r in repos:
|
1003
|
+
r.copy()
|
1004
|
+
|
1005
|
+
# Step 4: build Debian package
|
1006
|
+
if not no_deb:
|
1007
|
+
if not deb_opts:
|
1008
|
+
deb_opts = ["--no-sign"]
|
1009
|
+
|
1010
|
+
for r in repos:
|
1011
|
+
rd=PACK/r.dash
|
1012
|
+
p = rd / "debian"
|
1013
|
+
if not p.is_dir():
|
816
1014
|
continue
|
1015
|
+
try:
|
1016
|
+
res = subprocess.run(["dpkg-parsechangelog","-l","debian/changelog","-S","version"], cwd=rd, check=True, stdout=subprocess.PIPE)
|
1017
|
+
tag = res.stdout.strip().decode("utf-8").rsplit("-",1)[0]
|
1018
|
+
ltag = r.last_tag()[0]
|
1019
|
+
if tag != ltag:
|
1020
|
+
subprocess.run(["debchange", "--distribution","unstable", "--newversion",ltag+"-1",f"New release for {forcetag}"] , cwd=rd, check=True)
|
1021
|
+
repo.index.add(p/"changelog")
|
1022
|
+
|
1023
|
+
if debversion.get(r.dash,"") != ltag:
|
1024
|
+
subprocess.run(["debuild", "--build=binary"] + deb_opts, cwd=rd, check=True)
|
1025
|
+
except subprocess.CalledProcessError:
|
1026
|
+
if not run:
|
1027
|
+
print("*** Failure packaging",r.name,file=sys.stderr)
|
1028
|
+
else:
|
1029
|
+
print("Failure packaging",r.name,file=sys.stderr)
|
1030
|
+
return
|
817
1031
|
|
818
|
-
|
819
|
-
|
1032
|
+
# Step 5: build PyPI package
|
1033
|
+
if not no_pypi:
|
1034
|
+
err=set()
|
1035
|
+
up=set()
|
1036
|
+
for r in repos:
|
1037
|
+
rd=PACK/r.dash
|
1038
|
+
p = rd / "pyproject.toml"
|
1039
|
+
if not p.is_file():
|
1040
|
+
continue
|
1041
|
+
tag = r.last_tag()[0]
|
1042
|
+
name = r.dash
|
1043
|
+
if name.startswith("ext-"):
|
1044
|
+
name=name[4:]
|
1045
|
+
else:
|
1046
|
+
name="moat-"+r.dash
|
1047
|
+
|
1048
|
+
targz = rd/"dist"/f"{r.under}-{tag}.tar.gz"
|
1049
|
+
done = rd/"dist"/f"{r.under}-{tag}.done"
|
1050
|
+
if targz.is_file():
|
1051
|
+
if not done.exists():
|
1052
|
+
up.add(r)
|
1053
|
+
else:
|
1054
|
+
try:
|
1055
|
+
subprocess.run(["python3", "-mbuild", "-snw"], cwd=rd, check=True)
|
1056
|
+
except subprocess.CalledProcessError:
|
1057
|
+
err.add(r.name)
|
1058
|
+
else:
|
1059
|
+
up.add(r)
|
1060
|
+
if err:
|
1061
|
+
if not run:
|
1062
|
+
print("*** Build errors:", file=sys.stderr)
|
1063
|
+
print("***", *err, file=sys.stderr)
|
1064
|
+
else:
|
1065
|
+
print("Build errors:", file=sys.stderr)
|
1066
|
+
print(*err, file=sys.stderr)
|
1067
|
+
print("Please fix and try again.", file=sys.stderr)
|
1068
|
+
return
|
1069
|
+
|
1070
|
+
# Step 6: upload PyPI package
|
1071
|
+
if run:
|
1072
|
+
err=set()
|
1073
|
+
for r in up:
|
1074
|
+
rd=PACK/r.dash
|
1075
|
+
p = rd / "pyproject.toml"
|
1076
|
+
if not p.is_file():
|
1077
|
+
continue
|
1078
|
+
tag = r.last_tag()[0]
|
1079
|
+
name = r.dash
|
1080
|
+
if name.startswith("ext-"):
|
1081
|
+
name=name[4:]
|
1082
|
+
else:
|
1083
|
+
name="moat-"+r.dash
|
1084
|
+
targz = Path("dist")/f"{r.under}-{tag}.tar.gz"
|
1085
|
+
whl = Path("dist")/f"{r.under}-{tag}-py3-none-any.whl"
|
1086
|
+
try:
|
1087
|
+
res = subprocess.run(["twine", "upload", str(targz), str(whl)], cwd=rd, check=True)
|
1088
|
+
except subprocess.CalledProcessError:
|
1089
|
+
err.add(r.name)
|
1090
|
+
else:
|
1091
|
+
done = rd/"dist"/f"{r.under}-{tag}.done"
|
1092
|
+
done.touch()
|
1093
|
+
if err:
|
1094
|
+
print("Upload errors:", file=sys.stderr)
|
1095
|
+
print(*err, file=sys.stderr)
|
1096
|
+
print("Please fix(?) and try again.", file=sys.stderr)
|
1097
|
+
return
|
820
1098
|
|
821
|
-
|
822
|
-
|
823
|
-
|
1099
|
+
# Step 7: upload Debian package
|
1100
|
+
if run and not no_deb:
|
1101
|
+
err = set()
|
1102
|
+
if not dput_opts:
|
1103
|
+
dput_opts = ["-u","ext"]
|
1104
|
+
for r in repos:
|
1105
|
+
ltag = r.last_tag()[0]
|
1106
|
+
if not (PACK/r.dash/"debian").is_dir():
|
1107
|
+
continue
|
1108
|
+
changes = PACK/f"{r.mdash}_{ltag}-1_{ARCH}.changes"
|
1109
|
+
done = PACK/f"{r.mdash}_{ltag}-1_{ARCH}.done"
|
1110
|
+
if done.exists():
|
1111
|
+
continue
|
1112
|
+
try:
|
1113
|
+
subprocess.run(["dput", *dput_opts, str(changes)], check=True)
|
1114
|
+
except subprocess.CalledProcessError:
|
1115
|
+
err.add(r.name)
|
1116
|
+
else:
|
1117
|
+
done.touch()
|
1118
|
+
if err:
|
1119
|
+
print("Upload errors:", file=sys.stderr)
|
1120
|
+
print(*err, file=sys.stderr)
|
1121
|
+
print("Please fix(?) and try again.", file=sys.stderr)
|
1122
|
+
return
|
824
1123
|
|
825
|
-
|
826
|
-
|
827
|
-
|
1124
|
+
# Step 8: commit the result
|
1125
|
+
if run and not no_commit:
|
1126
|
+
repo.write_tags()
|
1127
|
+
repo.index.commit(f"Build version {forcetag}")
|
1128
|
+
git.TagReference.create(repo, forcetag)
|
828
1129
|
|
829
1130
|
|
830
1131
|
add_repr(tomlkit.items.String)
|