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