moat-src 0.6.0__py3-none-any.whl → 0.8.1__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 +671 -381
- moat/src/inspect.py +3 -0
- moat/src/test.py +10 -7
- {moat_src-0.6.0.dist-info → moat_src-0.8.1.dist-info}/METADATA +7 -7
- moat_src-0.8.1.dist-info/RECORD +9 -0
- {moat_src-0.6.0.dist-info → moat_src-0.8.1.dist-info}/WHEEL +1 -1
- moat_src-0.8.1.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 -145
- moat/src/_templates/pyproject.forced.yaml +0 -77
- moat/src/_templates/test_basic_py +0 -11
- moat_src-0.6.0.dist-info/RECORD +0 -17
- {moat_src-0.6.0.dist-info → moat_src-0.8.1.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,319 @@ 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)
|
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
|
+
tag,commit = self._repo.versions[self.dash]
|
163
|
+
if unchanged and self.has_changes(commit):
|
164
|
+
raise ChangedError(subsys,t,ref)
|
165
|
+
return tag,commit
|
166
|
+
|
167
|
+
def has_changes(self, tag:Commit=None) -> bool:
|
168
|
+
"""
|
169
|
+
Test whether the given subsystem (or any subsystem)
|
170
|
+
changed between the head and the @tag commit
|
171
|
+
"""
|
172
|
+
if tag is None:
|
173
|
+
tag,commit = self.last_tag()
|
174
|
+
else:
|
175
|
+
commit = tag
|
176
|
+
head = self._repo.head.commit
|
177
|
+
for d in head.diff(commit):
|
178
|
+
if self._repo.repo_for(d.a_path) != self.name and self._repo.repo_for(d.b_path) != self.name:
|
179
|
+
continue
|
180
|
+
return True
|
181
|
+
return False
|
182
|
+
|
183
|
+
|
184
|
+
class Repo(git.Repo,_Common):
|
185
|
+
"""Amend git.Repo with tag caching and pseudo-submodule splitting"""
|
24
186
|
|
25
187
|
moat_tag = None
|
26
|
-
|
188
|
+
_last_tag=None
|
27
189
|
|
28
190
|
def __init__(self, *a, **k):
|
29
191
|
super().__init__(*a, **k)
|
30
|
-
self._subrepo_cache = {}
|
31
192
|
self._commit_tags = defaultdict(list)
|
32
193
|
self._commit_topo = {}
|
33
194
|
|
195
|
+
self._repos = {}
|
196
|
+
self._make_repos()
|
197
|
+
|
34
198
|
for t in self.tags:
|
35
199
|
self._commit_tags[t.commit].append(t)
|
36
200
|
|
37
201
|
p = Path(self.working_dir)
|
38
202
|
mi = p.parts.index("moat")
|
39
203
|
self.moat_name = "-".join(p.parts[mi:])
|
204
|
+
with open("versions.yaml") as f:
|
205
|
+
self.versions = yload(f)
|
40
206
|
|
41
|
-
def
|
42
|
-
|
207
|
+
def write_tags(self):
|
208
|
+
with open("versions.yaml","w") as f:
|
209
|
+
yprint(self.versions,f)
|
210
|
+
self.index.add("versions.yaml")
|
43
211
|
|
44
|
-
if same and recurse and not depth:
|
45
|
-
yield self
|
46
212
|
|
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
|
213
|
+
def last_tag(self, unchanged:bool=False) -> Tag|None:
|
214
|
+
"""
|
215
|
+
Return the most-recent tag for this repo
|
216
|
+
"""
|
217
|
+
if self._last_tag is not None:
|
218
|
+
return self._last_tag,self._last_tag
|
219
|
+
for c in self._repo.commits(self.head.commit):
|
220
|
+
t = self.tagged(c)
|
221
|
+
if t is None:
|
222
|
+
continue
|
63
223
|
|
64
|
-
|
65
|
-
|
224
|
+
self._last_tag = t
|
225
|
+
if unchanged and self.has_changes(c):
|
226
|
+
raise ChangedError(subsys,t)
|
227
|
+
return t,t
|
228
|
+
|
229
|
+
raise ValueError(f"No tags found")
|
230
|
+
|
231
|
+
def part(self, name):
|
232
|
+
return self._repos[dash(name)]
|
233
|
+
|
234
|
+
@property
|
235
|
+
def _repo(self):
|
236
|
+
return self
|
237
|
+
|
238
|
+
@property
|
239
|
+
def parts(self):
|
240
|
+
return self._repos.values()
|
241
|
+
|
242
|
+
def tags_of(self, c:Commit) -> Sequence[Tag]:
|
243
|
+
return self._commit_tags[c]
|
244
|
+
|
245
|
+
def _add_repo(self, name):
|
246
|
+
dn = dash(name)
|
247
|
+
pn = undash(name)
|
248
|
+
if dn in self._repos:
|
249
|
+
return self._repos[dn]
|
250
|
+
|
251
|
+
p = Package(self,pn)
|
252
|
+
self._repos[dn] = p
|
253
|
+
if "." in pn:
|
254
|
+
par,nam = pn.rsplit(".",1)
|
255
|
+
pp = self._add_repo(par)
|
256
|
+
pp.subs[nam] = p
|
257
|
+
return p
|
258
|
+
|
259
|
+
def _make_repos(self) -> dict:
|
260
|
+
"""Collect subrepos"""
|
261
|
+
for fn in Path("packaging").iterdir():
|
262
|
+
if fn.name == "main":
|
263
|
+
continue
|
264
|
+
if not fn.is_dir() or "." in fn.name:
|
265
|
+
continue
|
266
|
+
self._add_repo(str(fn.name))
|
267
|
+
|
268
|
+
self._repos["main"].populate(Path("moat"))
|
269
|
+
|
270
|
+
def repo_for(self, path:Path|str) -> str:
|
271
|
+
"""
|
272
|
+
Given a file path, returns the subrepo in question
|
273
|
+
"""
|
274
|
+
name = "moat"
|
275
|
+
sc = self._repos["main"]
|
276
|
+
path=Path(path)
|
277
|
+
try:
|
278
|
+
if path.parts[0] == "packaging":
|
279
|
+
return path.parts[1].replace("-",".")
|
280
|
+
except KeyError:
|
281
|
+
return name
|
282
|
+
|
283
|
+
if path.parts[0] != "moat":
|
284
|
+
return name
|
285
|
+
|
286
|
+
for p in path.parts[1:]:
|
287
|
+
if p in sc.subs:
|
288
|
+
name += "."+p
|
289
|
+
sc = sc.subs[p]
|
290
|
+
else:
|
291
|
+
break
|
292
|
+
return name
|
66
293
|
|
67
294
|
def commits(self, ref=None):
|
68
|
-
"""Iterate over topo sort of commits following @ref, or HEAD
|
295
|
+
"""Iterate over topo sort of commits following @ref, or HEAD.
|
296
|
+
|
297
|
+
WARNING: this code does not do a true topological breadth-first
|
298
|
+
search. Doesn't matter much for simple merges that are based on
|
299
|
+
a mostly-current checkout, but don't expect correctness when branches
|
300
|
+
span tags.
|
301
|
+
"""
|
69
302
|
if ref is None:
|
70
303
|
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
304
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
305
|
+
visited = set()
|
306
|
+
work = deque([ref])
|
307
|
+
while work:
|
308
|
+
ref = work.popleft()
|
309
|
+
if ref in visited:
|
310
|
+
continue
|
311
|
+
visited.add(ref)
|
312
|
+
yield ref
|
313
|
+
work.extend(ref.parents)
|
98
314
|
|
99
|
-
def
|
100
|
-
"""
|
315
|
+
def has_changes(self, tag:Tag=None) -> bool:
|
316
|
+
"""
|
317
|
+
Test whether any subsystem changed since the "tagged" commit
|
318
|
+
|
319
|
+
"""
|
320
|
+
if tag is None:
|
321
|
+
tag,commit = self.last_tag()
|
322
|
+
head = self._repo.head.commit
|
323
|
+
for d in head.diff(tag):
|
324
|
+
if self.repo_for(d.a_path) == "moat" and self.repo_for(d.b_path) == "moat":
|
325
|
+
continue
|
326
|
+
return True
|
327
|
+
return False
|
328
|
+
|
329
|
+
|
330
|
+
def tagged(self, c:Commit=None) -> Tag|None:
|
331
|
+
"""Return a commit's tag name.
|
101
332
|
Defaults to the head commit.
|
102
333
|
Returns None if no tag, raises ValueError if more than one is found.
|
103
334
|
"""
|
@@ -106,9 +337,17 @@ class Repo(git.Repo):
|
|
106
337
|
if c not in self._commit_tags:
|
107
338
|
return None
|
108
339
|
tt = self._commit_tags[c]
|
340
|
+
|
341
|
+
tt = [t for t in tt if "/" not in t.name]
|
342
|
+
|
343
|
+
if not tt:
|
344
|
+
return None
|
109
345
|
if len(tt) > 1:
|
110
|
-
|
111
|
-
|
346
|
+
if subsys is not None:
|
347
|
+
raise ValueError(f"Multiple tags for {subsys}: {tt}")
|
348
|
+
raise ValueError(f"Multiple tags: {tt}")
|
349
|
+
return tt[0].name
|
350
|
+
|
112
351
|
|
113
352
|
|
114
353
|
@click.group(short_help="Manage MoaT itself")
|
@@ -116,7 +355,7 @@ async def cli():
|
|
116
355
|
"""
|
117
356
|
This collection of commands is useful for managing and building MoaT itself.
|
118
357
|
"""
|
119
|
-
pass
|
358
|
+
pass
|
120
359
|
|
121
360
|
|
122
361
|
def fix_deps(deps: list[str], tags: dict[str, str]) -> bool:
|
@@ -132,17 +371,22 @@ def fix_deps(deps: list[str], tags: dict[str, str]) -> bool:
|
|
132
371
|
return work
|
133
372
|
|
134
373
|
|
135
|
-
def run_tests(
|
136
|
-
"""Run
|
374
|
+
def run_tests(pkg: str|None, *opts) -> bool:
|
375
|
+
"""Run subtests for subpackage @pkg."""
|
376
|
+
|
377
|
+
if pkg is None:
|
378
|
+
tests = Path("tests")
|
379
|
+
else:
|
380
|
+
tests = dash(pkg).replace("-","_")
|
381
|
+
tests = Path("tests")/tests
|
137
382
|
|
138
|
-
|
139
|
-
|
140
|
-
# No Makefile. Assume it's OK.
|
383
|
+
if not Path(tests):
|
384
|
+
# No tests. Assume it's OK.
|
141
385
|
return True
|
142
386
|
try:
|
143
|
-
print("\n*** Testing:",
|
387
|
+
print("\n*** Testing:", pkg)
|
144
388
|
# subprocess.run(["python3", "-mtox"], cwd=repo.working_dir, check=True)
|
145
|
-
subprocess.run(["
|
389
|
+
subprocess.run(["python3","-mpytest", *opts, tests], stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr, check=True)
|
146
390
|
except subprocess.CalledProcessError:
|
147
391
|
return False
|
148
392
|
else:
|
@@ -269,8 +513,9 @@ def encomma(proj, path):
|
|
269
513
|
"""list > comma-delimited string"""
|
270
514
|
_mangle(proj, path, lambda x: ",".join(x)) # pylint: disable=unnecessary-lambda
|
271
515
|
|
516
|
+
|
272
517
|
def apply_hooks(repo, force=False):
|
273
|
-
h = Path(repo.git_dir)/"hooks"
|
518
|
+
h = Path(repo.git_dir) / "hooks"
|
274
519
|
drop = set()
|
275
520
|
seen = set()
|
276
521
|
for f in h.iterdir():
|
@@ -281,12 +526,12 @@ def apply_hooks(repo, force=False):
|
|
281
526
|
for f in drop:
|
282
527
|
f.unlink()
|
283
528
|
|
284
|
-
pt =
|
529
|
+
pt = Path(__file__).parent / "_hooks"
|
285
530
|
for f in pt.iterdir():
|
286
531
|
if not force:
|
287
532
|
if f.name in seen:
|
288
533
|
continue
|
289
|
-
t = h/f.name
|
534
|
+
t = h / f.name
|
290
535
|
d = f.read_text()
|
291
536
|
t.write_text(d)
|
292
537
|
t.chmod(0o755)
|
@@ -382,8 +627,9 @@ def apply_templates(repo):
|
|
382
627
|
txi = "\n" + txi.replace("\n\t", "\n ")
|
383
628
|
proj["tool"]["tox"] = dict(
|
384
629
|
legacy_tox_ini=tomlkit.items.String.from_raw(
|
385
|
-
txi,
|
386
|
-
|
630
|
+
txi,
|
631
|
+
type_=tomlkit.items.StringType.MLB,
|
632
|
+
),
|
387
633
|
)
|
388
634
|
|
389
635
|
projp = Path(repo.working_dir) / "pyproject.toml"
|
@@ -445,386 +691,430 @@ def path_():
|
|
445
691
|
print(Path(__file__).parent / "_templates")
|
446
692
|
|
447
693
|
|
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)
|
694
|
+
@cli.command()
|
695
|
+
@click.option("-s", "--show", is_flag=True, help="Show all tags")
|
696
|
+
@click.option("-r", "--run", is_flag=True, help="Update all stale tags")
|
697
|
+
def tags(show,run):
|
698
|
+
repo = Repo(None)
|
480
699
|
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
print(r.working_dir)
|
485
|
-
continue
|
700
|
+
if show:
|
701
|
+
if run:
|
702
|
+
raise click.UsageError("Can't display and change the tag at the same time!")
|
486
703
|
|
487
|
-
|
488
|
-
|
704
|
+
for r in repo.parts:
|
705
|
+
try:
|
706
|
+
tag,commit = r.last_tag()
|
707
|
+
except ValueError:
|
708
|
+
print(f"{r.dash} -")
|
489
709
|
continue
|
710
|
+
if r.has_changes(commit):
|
711
|
+
print(f"{r.dash} {tag} STALE")
|
712
|
+
else:
|
713
|
+
print(f"{r.dash} {tag}")
|
714
|
+
return
|
490
715
|
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
716
|
+
if repo.is_dirty(index=True, working_tree=True, untracked_files=True, submodules=False):
|
717
|
+
print("Repo is dirty. Not tagging globally.", file=sys.stderr)
|
718
|
+
return
|
719
|
+
|
720
|
+
changed=False
|
721
|
+
for r in repo.parts:
|
722
|
+
tag,commit = r.last_tag()
|
723
|
+
if not r.has_changes(commit):
|
724
|
+
print(repo.dash,tag,"UNCHANGED")
|
496
725
|
continue
|
497
726
|
|
498
|
-
|
727
|
+
tag = r.next_tag()
|
728
|
+
print(repo.dash,tag)
|
729
|
+
if not run:
|
499
730
|
continue
|
500
731
|
|
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,)
|
732
|
+
repo.versions[r.dash] = (tag,repo.head.commit.hexsha[:9])
|
733
|
+
changed=True
|
533
734
|
|
534
|
-
|
735
|
+
if changed:
|
736
|
+
repo.write_tags()
|
535
737
|
|
536
738
|
|
537
739
|
@cli.command()
|
538
|
-
@click.option("-
|
539
|
-
@click.option("-
|
540
|
-
@click.option("-
|
541
|
-
@click.option("-
|
542
|
-
@click.option("-
|
543
|
-
|
740
|
+
@click.option("-r", "--run", is_flag=True, help="actually do the tagging")
|
741
|
+
@click.option("-m", "--minor", is_flag=True, help="create a new minor version")
|
742
|
+
@click.option("-M", "--major", is_flag=True, help="create a new major version")
|
743
|
+
@click.option("-s", "--subtree", type=str, help="Tag this partial module")
|
744
|
+
@click.option("-v", "--tag", "force", type=str, help="Use this explicit tag value")
|
745
|
+
@click.option("-q", "--query","--show","show", is_flag=True, help="Show the latest tag")
|
746
|
+
@click.option("-f", "--force","FORCE", is_flag=True, help="replace an existing tag")
|
747
|
+
def tag(run,minor,major,subtree,force,FORCE,show):
|
544
748
|
"""
|
545
|
-
|
749
|
+
Tag the repository (or a subtree).
|
546
750
|
"""
|
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)
|
751
|
+
if minor and major:
|
752
|
+
raise click.UsageError("Can't change both minor and major!")
|
753
|
+
if force and (minor or major):
|
754
|
+
raise click.UsageError("Can't use an explicit tag with changing minor or major!")
|
755
|
+
if FORCE and (minor or major):
|
756
|
+
raise click.UsageError("Can't use an explicit tag with changing minor or major!")
|
757
|
+
if show and (run or force or minor or major):
|
758
|
+
raise click.UsageError("Can't display and change the tag at the same time!")
|
562
759
|
|
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)
|
760
|
+
repo = Repo(None)
|
570
761
|
|
762
|
+
if subtree:
|
763
|
+
r = repo.part(subtree)
|
764
|
+
else:
|
765
|
+
r = repo
|
571
766
|
|
572
|
-
|
573
|
-
|
574
|
-
|
767
|
+
if show:
|
768
|
+
tag,commit = r.last_tag()
|
769
|
+
if r.has_changes(commit):
|
770
|
+
print(f"{tag} STALE")
|
771
|
+
else:
|
772
|
+
print(tag)
|
773
|
+
return
|
575
774
|
|
576
|
-
|
577
|
-
|
578
|
-
|
775
|
+
if force:
|
776
|
+
tag = force
|
777
|
+
elif FORCE:
|
778
|
+
tag,_ = r.last_tag()
|
779
|
+
else:
|
780
|
+
tag = r.next_tag(major,minor)
|
579
781
|
|
580
|
-
|
581
|
-
if
|
582
|
-
|
583
|
-
|
584
|
-
return
|
585
|
-
if "moat" in r.refs:
|
586
|
-
m = r.refs["moat"]
|
782
|
+
if run or subtree:
|
783
|
+
if subtree:
|
784
|
+
repo.versions[r.dash] = (tag,repo.head.commit.hexsha[:9])
|
785
|
+
repo.write_tags()
|
587
786
|
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)
|
787
|
+
git.TagReference.create(repo,tag, force=FORCE)
|
788
|
+
print(f"{tag}")
|
789
|
+
else:
|
790
|
+
print(f"{tag} DRY_RUN")
|
604
791
|
|
605
792
|
|
606
793
|
@cli.command()
|
607
|
-
|
794
|
+
@click.option("-P", "--no-pypi", is_flag=True, help="don't push to PyPi")
|
795
|
+
@click.option("-D", "--no-deb", is_flag=True, help="don't debianize")
|
796
|
+
@click.option("-d", "--deb", type=str, help="Debian archive to push to (from dput.cfg)")
|
797
|
+
@click.option("-o", "--only", type=str, multiple=True, help="affect only this package")
|
798
|
+
@click.option("-s", "--skip", type=str, multiple=True, help="skip this package")
|
799
|
+
async def publish(no_pypi, no_deb, skip, only, deb):
|
608
800
|
"""
|
609
|
-
|
610
|
-
|
611
|
-
Submodules frequently have detached HEADs. This command resets "main"
|
612
|
-
to them, but only if that is a fast-forward operation.
|
801
|
+
Publish modules to PyPi and/or Debian.
|
613
802
|
|
614
|
-
|
615
|
-
the
|
803
|
+
MoaT modules can be given as shorthand, i.e. with dashes and excluding
|
804
|
+
the "moat-" prefix.
|
616
805
|
"""
|
617
806
|
repo = Repo(None)
|
618
|
-
|
807
|
+
if only and skip:
|
808
|
+
raise click.UsageError("You can't both include and exclude packages.")
|
619
809
|
|
810
|
+
if only:
|
811
|
+
repos = (repo.subrepo(x) for x in only)
|
812
|
+
else:
|
813
|
+
s = set()
|
814
|
+
for sk in skip:
|
815
|
+
s += set(sk.split(","))
|
816
|
+
repos = (x for x in repo.parts if dash(x.name) not in sk)
|
620
817
|
|
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"""
|
818
|
+
deb_args = "-b -us -uc".split()
|
626
819
|
|
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)
|
820
|
+
for r in repos:
|
821
|
+
t,c = r.last_tag
|
822
|
+
if r.has_changes(c):
|
823
|
+
print(f"Error: changes in {r.name} since tag {t.name}")
|
824
|
+
continue
|
641
825
|
|
826
|
+
print(f"Processing {r.name}, tag: {t.name}")
|
827
|
+
r.copy()
|
828
|
+
rd=PACK/r.dash
|
642
829
|
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
"""Fetch updates"""
|
830
|
+
if not no_deb:
|
831
|
+
p = rd / "debian"
|
832
|
+
if not p.is_dir():
|
833
|
+
continue
|
834
|
+
subprocess.run(["debuild"] + deb_args, cwd=rd, check=True)
|
649
835
|
|
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)
|
836
|
+
if not no_pypi:
|
837
|
+
for r in repos:
|
838
|
+
p = Path(r.working_dir) / "pyproject.toml"
|
839
|
+
if not p.is_file():
|
840
|
+
continue
|
841
|
+
print(r.working_dir)
|
842
|
+
subprocess.run(["make", "pypi"], cwd=r.working_dir, check=True)
|
676
843
|
|
677
844
|
|
678
|
-
@cli.command(
|
679
|
-
|
845
|
+
@cli.command(epilog="""
|
846
|
+
The default for building Debian packages is '--no-sign --build=binary'.
|
847
|
+
'--no-sign' is dropped when you use '--deb'.
|
848
|
+
The binary-only build is currently unconditional.
|
849
|
+
|
850
|
+
The default for uploading to Debian via 'dput' is '--unchecked ext';
|
851
|
+
it is dropped when you use '--dput'.
|
852
|
+
""")
|
853
|
+
@click.option("-f", "--no-dirty", is_flag=True, help="don't check for dirtiness (DANGER)")
|
854
|
+
@click.option("-F", "--no-tag", is_flag=True, help="don't check for tag uptodate-ness (DANGER)")
|
855
|
+
@click.option("-D", "--no-deb", is_flag=True, help="don't build Debian packages")
|
856
|
+
@click.option("-C", "--no-commit", is_flag=True, help="don't commit the result")
|
857
|
+
@click.option("-V", "--no-version", is_flag=True, help="don't update dependency versions in pyproject files")
|
858
|
+
@click.option("-P", "--no-pypi", is_flag=True, help="don't push to PyPI")
|
859
|
+
@click.option("-T", "--no-test", is_flag=True, help="don't run tests")
|
860
|
+
@click.option("-o", "--pytest", "pytest_opts", type=str,multiple=True, help="Options for pytest")
|
861
|
+
@click.option("-d", "--deb", "deb_opts", type=str,multiple=True, help="Options for debuild")
|
862
|
+
@click.option("-p", "--dput", "dput_opts", type=str,multiple=True, help="Options for dput")
|
863
|
+
@click.option("-r", "--run", is_flag=True, help="actually do the tagging")
|
864
|
+
@click.option("-s", "--skip", "skip_", type=str,multiple=True, help="skip these repos")
|
865
|
+
@click.option("-m", "--minor", is_flag=True, help="create a new minor version")
|
866
|
+
@click.option("-M", "--major", is_flag=True, help="create a new major version")
|
867
|
+
@click.option("-t", "--tag", "forcetag", type=str, help="Use this explicit tag value")
|
680
868
|
@click.option(
|
681
869
|
"-v",
|
682
870
|
"--version",
|
683
871
|
type=(str, str),
|
684
872
|
multiple=True,
|
685
|
-
help="Update external
|
873
|
+
help="Update external dependency",
|
686
874
|
)
|
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):
|
875
|
+
@click.argument("parts", nargs=-1)
|
876
|
+
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
877
|
"""
|
692
878
|
Rebuild all modified packages.
|
693
879
|
"""
|
694
|
-
bad = False
|
695
880
|
repo = Repo(None)
|
881
|
+
|
696
882
|
tags = dict(version)
|
697
883
|
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
|
884
|
+
for s in skip_:
|
885
|
+
for sn in s.split(","):
|
886
|
+
skip.add(dash(sn))
|
887
|
+
parts = set(dash(s) for s in parts)
|
888
|
+
debversion={}
|
889
|
+
|
890
|
+
if no_tag and not no_version:
|
891
|
+
print("Warning: not updating moat versions in pyproject files", file=sys.stderr)
|
892
|
+
if minor and major:
|
893
|
+
raise click.UsageError("Can't change both minor and major!")
|
894
|
+
if forcetag and (minor or major):
|
895
|
+
raise click.UsageError("Can't use an explicit tag with changing minor or major!")
|
896
|
+
|
897
|
+
if forcetag is None:
|
898
|
+
forcetag = repo.next_tag(major,minor)
|
899
|
+
|
900
|
+
full = False
|
901
|
+
if parts:
|
902
|
+
repos = [ repo.part(x) for x in parts ]
|
903
|
+
else:
|
904
|
+
if not skip:
|
905
|
+
full = True
|
906
|
+
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
907
|
|
723
|
-
|
724
|
-
|
725
|
-
if r.moat_name != "src":
|
726
|
-
bad = True
|
908
|
+
for name in PACK.iterdir():
|
909
|
+
if name.suffix != ".changes":
|
727
910
|
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
|
911
|
+
name=name.stem
|
912
|
+
name,vers,_ = name.split("_")
|
913
|
+
if name.startswith("moat-"):
|
914
|
+
name = name[5:]
|
749
915
|
else:
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
916
|
+
name = "ext-"+name
|
917
|
+
debversion[name]=vers.rsplit("-",1)[0]
|
918
|
+
|
919
|
+
|
920
|
+
# Step 0: basic check
|
921
|
+
if not no_dirty:
|
922
|
+
if repo.is_dirty(index=False, working_tree=True, untracked_files=True, submodules=False):
|
923
|
+
if not run:
|
924
|
+
print("*** Repository is not clean.", file=sys.stderr)
|
925
|
+
else:
|
926
|
+
print("Please commit changes and try again.", file=sys.stderr)
|
927
|
+
return
|
762
928
|
|
763
|
-
check
|
929
|
+
# Step 1: check for changed files since last tagging
|
930
|
+
if not no_tag:
|
931
|
+
err = set()
|
932
|
+
for r in repos:
|
933
|
+
try:
|
934
|
+
tag,commit = r.last_tag()
|
935
|
+
except KeyError:
|
936
|
+
rd = PACK/r.dash
|
937
|
+
p = rd / "pyproject.toml"
|
938
|
+
if not p.is_file():
|
939
|
+
continue
|
940
|
+
raise
|
941
|
+
tags[r.mdash] = tag
|
942
|
+
if r.has_changes(commit):
|
943
|
+
err.add(r.dash)
|
944
|
+
if err:
|
945
|
+
if not run:
|
946
|
+
print("*** Untagged changes:", file=sys.stderr)
|
947
|
+
print("***", *err, file=sys.stderr)
|
948
|
+
else:
|
949
|
+
print("Untagged changes:", file=sys.stderr)
|
950
|
+
print(*err, file=sys.stderr)
|
951
|
+
print("Please tag (moat src tag -s PACKAGE) and try again.", file=sys.stderr)
|
952
|
+
return
|
764
953
|
|
765
|
-
|
766
|
-
|
954
|
+
# Step 2: run tests
|
955
|
+
if not no_test:
|
956
|
+
fails = set()
|
957
|
+
for p in parts:
|
958
|
+
if not run_tests(p, *pytest_opts):
|
959
|
+
fails.add(p.name)
|
960
|
+
if fails:
|
961
|
+
if not run:
|
962
|
+
print(f"*** Tests failed:", *fails, file=sys.stderr)
|
963
|
+
else:
|
964
|
+
print(f"Failed tests:", *fails, file=sys.stderr)
|
965
|
+
print(f"Fix and try again.", file=sys.stderr)
|
966
|
+
return
|
767
967
|
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
968
|
+
# Step 3: set version and fix versioned dependencies
|
969
|
+
for r in repos:
|
970
|
+
rd = PACK/r.dash
|
971
|
+
p = rd / "pyproject.toml"
|
972
|
+
if not p.is_file():
|
973
|
+
# bad=True
|
974
|
+
print("Skip:", r.name, file=sys.stderr)
|
975
|
+
continue
|
976
|
+
with p.open("r") as f:
|
977
|
+
pr = tomlkit.load(f)
|
978
|
+
pr["project"]["version"] = r.last_tag()[0]
|
779
979
|
|
780
|
-
|
980
|
+
if not no_version:
|
781
981
|
try:
|
782
982
|
deps = pr["project"]["dependencies"]
|
783
983
|
except KeyError:
|
784
984
|
pass
|
785
985
|
else:
|
786
|
-
|
986
|
+
fix_deps(deps, tags)
|
787
987
|
try:
|
788
988
|
deps = pr["project"]["optional_dependencies"]
|
789
989
|
except KeyError:
|
790
990
|
pass
|
791
991
|
else:
|
792
992
|
for v in deps.values():
|
793
|
-
|
794
|
-
|
795
|
-
if work:
|
796
|
-
p.write_text(pr.as_string())
|
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
|
993
|
+
fix_deps(v, tags)
|
810
994
|
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
995
|
+
p.write_text(pr.as_string())
|
996
|
+
repo.index.add(p)
|
997
|
+
|
998
|
+
# Step 3: copy to packaging dir
|
999
|
+
for r in repos:
|
1000
|
+
r.copy()
|
1001
|
+
|
1002
|
+
# Step 4: build Debian package
|
1003
|
+
if not no_deb:
|
1004
|
+
if not deb_opts:
|
1005
|
+
deb_opts = ["--no-sign"]
|
1006
|
+
|
1007
|
+
for r in repos:
|
1008
|
+
rd=PACK/r.dash
|
1009
|
+
p = rd / "debian"
|
1010
|
+
if not p.is_dir():
|
816
1011
|
continue
|
1012
|
+
try:
|
1013
|
+
res = subprocess.run(["dpkg-parsechangelog","-l","debian/changelog","-S","version"], cwd=rd, check=True, stdout=subprocess.PIPE)
|
1014
|
+
tag = res.stdout.strip().decode("utf-8").rsplit("-",1)[0]
|
1015
|
+
ltag = r.last_tag()[0]
|
1016
|
+
if tag != ltag:
|
1017
|
+
subprocess.run(["debchange", "--distribution","unstable", "--newversion",ltag+"-1",f"New release for {forcetag}"] , cwd=rd, check=True)
|
1018
|
+
repo.index.add(p/"changelog")
|
1019
|
+
|
1020
|
+
if debversion.get(r.dash,"") != ltag:
|
1021
|
+
subprocess.run(["debuild", "--build=binary"] + deb_opts, cwd=rd, check=True)
|
1022
|
+
except subprocess.CalledProcessError:
|
1023
|
+
if not run:
|
1024
|
+
print("*** Failure packaging",r.name,file=sys.stderr)
|
1025
|
+
else:
|
1026
|
+
print("Failure packaging",r.name,file=sys.stderr)
|
1027
|
+
return
|
817
1028
|
|
818
|
-
|
819
|
-
|
1029
|
+
# Step 5: build PyPI package
|
1030
|
+
if not no_pypi:
|
1031
|
+
err=set()
|
1032
|
+
up=set()
|
1033
|
+
for r in repos:
|
1034
|
+
rd=PACK/r.dash
|
1035
|
+
p = rd / "pyproject.toml"
|
1036
|
+
if not p.is_file():
|
1037
|
+
continue
|
1038
|
+
tag = r.last_tag()[0]
|
1039
|
+
name = r.dash
|
1040
|
+
if name.startswith("ext-"):
|
1041
|
+
name=name[4:]
|
1042
|
+
else:
|
1043
|
+
name="moat-"+r.dash
|
1044
|
+
|
1045
|
+
targz = rd/"dist"/f"{r.under}-{tag}.tar.gz"
|
1046
|
+
done = rd/"dist"/f"{r.under}-{tag}.done"
|
1047
|
+
if targz.is_file():
|
1048
|
+
print(f"{name}: Source package exists.")
|
1049
|
+
if not done.exists():
|
1050
|
+
up.add(r)
|
1051
|
+
else:
|
1052
|
+
try:
|
1053
|
+
subprocess.run(["python3", "-mbuild", "-snw"], cwd=rd, check=True)
|
1054
|
+
except subprocess.CalledProcessError:
|
1055
|
+
err.add(r.name)
|
1056
|
+
if err:
|
1057
|
+
if not run:
|
1058
|
+
print("*** Build errors:", file=sys.stderr)
|
1059
|
+
print("***", *err, file=sys.stderr)
|
1060
|
+
else:
|
1061
|
+
print("Build errors:", file=sys.stderr)
|
1062
|
+
print(*err, file=sys.stderr)
|
1063
|
+
print("Please fix and try again.", file=sys.stderr)
|
1064
|
+
return
|
1065
|
+
|
1066
|
+
# Step 6: upload PyPI package
|
1067
|
+
if run:
|
1068
|
+
err=set()
|
1069
|
+
for p in up:
|
1070
|
+
rd=PACK/r.dash
|
1071
|
+
p = rd / "pyproject.toml"
|
1072
|
+
if not p.is_file():
|
1073
|
+
continue
|
1074
|
+
tag = r.last_tag()[0]
|
1075
|
+
name = r.dash
|
1076
|
+
if name.startswith("ext-"):
|
1077
|
+
name=name[4:]
|
1078
|
+
else:
|
1079
|
+
name="moat-"+r.dash
|
1080
|
+
targz = Path("dist")/f"{name}-{tag}.tar.gz"
|
1081
|
+
whl = Path("dist")/f"{name}-{tag}-py3-none-any.whl"
|
1082
|
+
try:
|
1083
|
+
subprocess.run(["twine", "upload", str(targz), str(whl)], cwd=rd, check=True)
|
1084
|
+
except subprocess.CalledProcessError:
|
1085
|
+
err.add(r.name)
|
1086
|
+
else:
|
1087
|
+
done = rd/"dist"/f"{r.under}-{tag}.done"
|
1088
|
+
done.touch()
|
1089
|
+
if err:
|
1090
|
+
print("Upload errors:", file=sys.stderr)
|
1091
|
+
print(*err, file=sys.stderr)
|
1092
|
+
print("Please fix(?) and try again.", file=sys.stderr)
|
1093
|
+
return
|
820
1094
|
|
821
|
-
|
822
|
-
|
823
|
-
|
1095
|
+
# Step 7: upload Debian package
|
1096
|
+
if run and not no_deb:
|
1097
|
+
err = set()
|
1098
|
+
if not dput_opts:
|
1099
|
+
dput_opts = ["-u","ext"]
|
1100
|
+
for r in repos:
|
1101
|
+
ltag = r.last_tag()[0]
|
1102
|
+
changes = PACK/f"{r.mdash}_{ltag}-1_{ARCH}.changes"
|
1103
|
+
try:
|
1104
|
+
subprocess.run(["dput", *dput_opts, str(changes)], cwd=PACK, check=True)
|
1105
|
+
except subprocess.CalledProcessError:
|
1106
|
+
err.add(r.name)
|
1107
|
+
if err:
|
1108
|
+
print("Upload errors:", file=sys.stderr)
|
1109
|
+
print(*err, file=sys.stderr)
|
1110
|
+
print("Please fix(?) and try again.", file=sys.stderr)
|
1111
|
+
return
|
824
1112
|
|
825
|
-
|
826
|
-
|
827
|
-
|
1113
|
+
# Step 8: commit the result
|
1114
|
+
if run and not no_commit:
|
1115
|
+
repo.write_tags()
|
1116
|
+
repo.index.commit(f"Build version {forcetag}")
|
1117
|
+
git.TagReference.create(repo, forcetag)
|
828
1118
|
|
829
1119
|
|
830
1120
|
add_repr(tomlkit.items.String)
|