moat-src 0.8.5__py3-none-any.whl → 0.8.11__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.
Files changed (31) hide show
  1. moat/src/_cfg.yaml +2 -0
  2. moat/src/_main.py +295 -201
  3. moat/src/_templates/moat/_main.py +23 -0
  4. moat/src/test.py +1 -1
  5. {moat_src-0.8.5.dist-info → moat_src-0.8.11.dist-info}/METADATA +2 -2
  6. moat_src-0.8.11.dist-info/RECORD +14 -0
  7. {moat_src-0.8.5.dist-info → moat_src-0.8.11.dist-info}/WHEEL +1 -1
  8. moat_src-0.8.11.dist-info/top_level.txt +1 -0
  9. build/lib/moat/src/__init__.py +0 -0
  10. build/lib/moat/src/_main.py +0 -1113
  11. build/lib/moat/src/_templates/moat/__init__.py +0 -3
  12. build/lib/moat/src/inspect.py +0 -65
  13. build/lib/moat/src/test.py +0 -89
  14. debian/.gitignore +0 -8
  15. debian/changelog +0 -78
  16. debian/control +0 -20
  17. debian/moat-src/usr/lib/python3/dist-packages/moat/src/__init__.py +0 -0
  18. debian/moat-src/usr/lib/python3/dist-packages/moat/src/_main.py +0 -1113
  19. debian/moat-src/usr/lib/python3/dist-packages/moat/src/_templates/moat/__init__.py +0 -3
  20. debian/moat-src/usr/lib/python3/dist-packages/moat/src/_templates/pyproject.default.yaml +0 -146
  21. debian/moat-src/usr/lib/python3/dist-packages/moat/src/_templates/pyproject.forced.yaml +0 -77
  22. debian/moat-src/usr/lib/python3/dist-packages/moat/src/inspect.py +0 -65
  23. debian/moat-src/usr/lib/python3/dist-packages/moat/src/test.py +0 -89
  24. debian/rules +0 -10
  25. moat/src/_templates/pyproject.default.yaml +0 -146
  26. moat/src/_templates/pyproject.forced.yaml +0 -77
  27. moat_src-0.8.5.dist-info/RECORD +0 -30
  28. moat_src-0.8.5.dist-info/top_level.txt +0 -4
  29. {build/lib/moat/src/_templates → moat/src/_templates/packaging}/pyproject.default.yaml +0 -0
  30. {build/lib/moat/src/_templates → moat/src/_templates/packaging}/pyproject.forced.yaml +0 -0
  31. {moat_src-0.8.5.dist-info → moat_src-0.8.11.dist-info}/licenses/LICENSE.txt +0 -0
moat/src/_main.py CHANGED
@@ -13,33 +13,34 @@ from pathlib import Path
13
13
  import asyncclick as click
14
14
  import git
15
15
  import tomlkit
16
- from anyio import run_process
17
16
  from moat.util import P, add_repr, attrdict, make_proc, yload, yprint
18
17
  from packaging.requirements import Requirement
19
- from attrs import define,field
20
- from shutil import rmtree,copyfile,copytree
18
+ from attrs import define, field
19
+ from shutil import rmtree, copyfile, copytree
21
20
  from contextlib import suppress
22
21
 
23
22
  logger = logging.getLogger(__name__)
24
23
 
25
- PACK=Path("packaging")
26
- ARCH=subprocess.check_output(["dpkg","--print-architecture"]).decode("utf-8").strip()
24
+ PACK = Path("packaging")
25
+ ARCH = subprocess.check_output(["dpkg", "--print-architecture"]).decode("utf-8").strip()
27
26
 
28
- def dash(n:str) -> str:
27
+
28
+ def dash(n: str) -> str:
29
29
  """
30
30
  moat.foo.bar > foo-bar
31
31
  foo.bar > ext-foo-bar
32
32
  """
33
- if n in ("main","moat"):
33
+ if n in ("main", "moat"):
34
34
  return "main"
35
35
  if "." not in n: # also applies to single-name packages
36
36
  return n
37
37
 
38
38
  if not n.startswith("moat."):
39
- return "ext-"+n.replace("-",".")
40
- return n.replace(".","-")[5:]
39
+ return "ext-" + n.replace("-", ".")
40
+ return n.replace(".", "-")[5:]
41
+
41
42
 
42
- def undash(n:str) -> str:
43
+ def undash(n: str) -> str:
43
44
  """
44
45
  foo-bar > moat.foo.bar
45
46
  ext-foo-bar > foo.bar
@@ -47,85 +48,97 @@ def undash(n:str) -> str:
47
48
  if "." in n:
48
49
  return n
49
50
 
50
- if n in ("main","moat"):
51
+ if n in ("main", "moat"):
51
52
  return "moat"
52
53
  if n.startswith("ext-"):
53
- return n.replace("-",".")[4:]
54
- return "moat."+n.replace("-",".")
54
+ return n.replace("-", ".")[4:]
55
+ return "moat." + n.replace("-", ".")
56
+
55
57
 
56
58
  class ChangedError(RuntimeError):
57
- def __init__(subsys,tag,head):
59
+ def __init__(subsys, tag, head):
58
60
  self.subsys = subsys
59
61
  self.tag = tag
60
62
  self.head = head
63
+
61
64
  def __str__(self):
62
65
  s = self.subsys or "Something"
63
66
  if head is None:
64
- head="HEAD"
67
+ head = "HEAD"
65
68
  else:
66
69
  head = head.hexsha[:9]
67
70
  return f"{s} changed between {tag.name} and {head}"
68
71
 
69
- class _Common:
70
72
 
71
- def next_tag(self,major:bool=False,minor:bool=False):
73
+ class _Common:
74
+ def next_tag(self, major: bool = False, minor: bool = False):
72
75
  tag = self.last_tag
73
76
  try:
74
- n = [ int(x) for x in tag.split('.') ]
77
+ n = [int(x) for x in tag.split(".")]
75
78
  if len(n) != 3:
76
79
  raise ValueError(n)
77
80
  except ValueError:
78
81
  raise ValueError(f"Tag {tag} not in major#.minor#.patch# form.") from None
79
82
 
80
83
  if major:
81
- n = [n[0]+1,0,0]
84
+ n = [n[0] + 1, 0, 0]
82
85
  elif minor:
83
- n = [n[0],n[1]+1,0]
86
+ n = [n[0], n[1] + 1, 0]
84
87
  else:
85
- n = [n[0],n[1],n[2]+1]
88
+ n = [n[0], n[1], n[2] + 1]
86
89
  return ".".join(str(x) for x in n)
87
90
 
91
+
88
92
  @define
89
93
  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)
94
+ _repo: Repo = field(repr=False)
95
+ name: str = field()
96
+ under: str = field(init=False, repr=False)
97
+ path: Path = field(init=False, repr=False)
98
+ files: set(Path) = field(init=False, factory=set, repr=False)
99
+ subs: dict[str, Package] = field(factory=dict, init=False, repr=False)
100
+ hidden: bool = field(init=False, repr=False)
97
101
 
98
102
  def __init__(self, repo, name):
99
- self.__attrs_init__(repo,name)
100
- self.under = name.replace(".","_")
103
+ self.__attrs_init__(repo, name)
104
+ self.under = name.replace(".", "_")
101
105
  self.path = Path(*name.split("."))
102
- self.hidden = not (PACK/self.dash).exists()
106
+ self.hidden = not (PACK / self.dash).exists()
103
107
 
104
108
  @property
105
109
  def dash(self):
106
110
  return dash(self.name)
107
111
 
108
112
  def __eq__(self, other):
109
- return self.name==other.name
113
+ return self.name == other.name
110
114
 
111
115
  def __hash__(self):
112
116
  return hash(self.name)
113
117
 
118
+ @property
119
+ def verstr(self):
120
+ v=self.vers
121
+ return f"{v.tag}-{v.pkg}"
122
+
114
123
  @property
115
124
  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
+ try:
126
+ v = self._repo.versions[self.dash]
127
+ except KeyError:
128
+ self._repo.versions[self.dash] = v = attrdict()
129
+ else:
130
+ if not isinstance(v, dict):
131
+ tag, commit = v
132
+ v = attrdict(
133
+ tag=tag,
134
+ pkg=1,
135
+ rev=commit,
136
+ )
137
+ self._repo.versions[self.dash] = v
125
138
  return v
126
139
 
127
140
  @vers.setter
128
- def vers(self,d):
141
+ def vers(self, d):
129
142
  v = self.vers
130
143
  v.update(d)
131
144
  return v
@@ -140,13 +153,13 @@ class Package(_Common):
140
153
 
141
154
  @property
142
155
  def mdash(self):
143
- d=dash(self.name)
156
+ d = dash(self.name)
144
157
  if d.startswith("ext-"):
145
158
  return d[4:]
146
159
  else:
147
- return "moat-"+d
160
+ return "moat-" + d
148
161
 
149
- def populate(self, path:Path, real=None):
162
+ def populate(self, path: Path, real=None):
150
163
  """
151
164
  Collect this package's file names.
152
165
  """
@@ -154,7 +167,7 @@ class Package(_Common):
154
167
  for fn in path.iterdir():
155
168
  if fn.name == "__pycache__":
156
169
  continue
157
- if (sb := self.subs.get(fn.name,None)) is not None:
170
+ if (sb := self.subs.get(fn.name, None)) is not None:
158
171
  sb.populate(fn, real=self if sb.hidden else None)
159
172
  else:
160
173
  (real or self).files.add(fn)
@@ -165,41 +178,49 @@ class Package(_Common):
165
178
  """
166
179
  if not self.files:
167
180
  raise ValueError(f"No files in {self.name}?")
168
- p = Path("packaging")/self.dash
181
+ p = Path("packaging") / self.dash / "src"
169
182
  with suppress(FileNotFoundError):
170
- rmtree(p/"moat")
171
- dest = p/self.path
183
+ rmtree(p / "moat")
184
+ dest = p / self.path
172
185
  dest.mkdir(parents=True)
173
186
  for f in self.files:
174
- pf=p/f
175
- pf.parent.mkdir(parents=True,exist_ok=True)
187
+ pf = p / f
188
+ pf.parent.mkdir(parents=True, exist_ok=True)
176
189
  if f.is_dir():
177
190
  copytree(f, pf, symlinks=False)
178
191
  else:
179
192
  copyfile(f, pf, follow_symlinks=True)
180
- licd = p/"LICENSE.txt"
193
+
194
+ p = Path("packaging") / self.dash
195
+ licd = p / "LICENSE.txt"
181
196
  if not licd.exists():
182
197
  copyfile("LICENSE.txt", licd)
183
198
 
184
- def has_changes(self, main:bool|None=None) -> bool:
199
+ def has_changes(self, main: bool | None = None) -> bool:
185
200
  """
186
201
  Test whether the given subsystem changed
187
202
  between the head and the @tag commit
188
203
  """
189
- commit = self.last_commit
190
204
  head = self._repo.head.commit
205
+ try:
206
+ lc = self.last_commit
207
+ except AttributeError:
208
+ return True
191
209
  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:
210
+ if (
211
+ self._repo.repo_for(d.a_path, main) != self.name
212
+ and self._repo.repo_for(d.b_path, main) != self.name
213
+ ):
193
214
  continue
194
215
  return True
195
216
  return False
196
217
 
197
218
 
198
- class Repo(git.Repo,_Common):
219
+ class Repo(git.Repo, _Common):
199
220
  """Amend git.Repo with tag caching and pseudo-submodule splitting"""
200
221
 
201
222
  moat_tag = None
202
- _last_tag=None
223
+ _last_tag = None
203
224
 
204
225
  def __init__(self, *a, **k):
205
226
  super().__init__(*a, **k)
@@ -219,13 +240,12 @@ class Repo(git.Repo,_Common):
219
240
  self.versions = yload(f, attr=True)
220
241
 
221
242
  def write_tags(self):
222
- with open("versions.yaml","w") as f:
223
- yprint(self.versions,f)
243
+ with open("versions.yaml", "w") as f:
244
+ yprint(self.versions, f)
224
245
  self.index.add("versions.yaml")
225
246
 
226
-
227
247
  @property
228
- def last_tag(self) -> Tag|None:
248
+ def last_tag(self) -> Tag | None:
229
249
  """
230
250
  Return the most-recent tag for this repo
231
251
  """
@@ -240,7 +260,7 @@ class Repo(git.Repo,_Common):
240
260
  self._last_tag = t
241
261
  return t
242
262
 
243
- raise ValueError(f"No tags found")
263
+ raise ValueError("No tags found")
244
264
 
245
265
  @property
246
266
  def last_commit(self) -> str:
@@ -259,7 +279,7 @@ class Repo(git.Repo,_Common):
259
279
  def parts(self):
260
280
  return self._repos.values()
261
281
 
262
- def tags_of(self, c:Commit) -> Sequence[Tag]:
282
+ def tags_of(self, c: Commit) -> Sequence[Tag]:
263
283
  return self._commit_tags[c]
264
284
 
265
285
  def _add_repo(self, name):
@@ -268,10 +288,10 @@ class Repo(git.Repo,_Common):
268
288
  if dn in self._repos:
269
289
  return self._repos[dn]
270
290
 
271
- p = Package(self,pn)
291
+ p = Package(self, pn)
272
292
  self._repos[dn] = p
273
293
  if "." in pn:
274
- par,nam = pn.rsplit(".",1)
294
+ par, nam = pn.rsplit(".", 1)
275
295
  pp = self._add_repo(par)
276
296
  pp.subs[nam] = p
277
297
  return p
@@ -287,18 +307,18 @@ class Repo(git.Repo,_Common):
287
307
 
288
308
  self._repos["main"].populate(Path("moat"))
289
309
 
290
- def repo_for(self, path:Path|str, main:bool|None) -> str:
310
+ def repo_for(self, path: Path | str, main: bool | None) -> str:
291
311
  """
292
312
  Given a file path, returns the subrepo in question
293
313
  """
294
314
  sc = self._repos["main"]
295
- path=Path(path)
315
+ path = Path(path)
296
316
 
297
317
  if main is not False and path.parts[0] == "moat":
298
318
  name = "moat"
299
319
  for p in path.parts[1:]:
300
320
  if p in sc.subs:
301
- name += "."+p
321
+ name += "." + p
302
322
  sc = sc.subs[p]
303
323
  else:
304
324
  break
@@ -312,7 +332,6 @@ class Repo(git.Repo,_Common):
312
332
 
313
333
  return None
314
334
 
315
-
316
335
  def commits(self, ref=None):
317
336
  """Iterate over topo sort of commits following @ref, or HEAD.
318
337
 
@@ -334,7 +353,7 @@ class Repo(git.Repo,_Common):
334
353
  yield ref
335
354
  work.extend(ref.parents)
336
355
 
337
- def has_changes(self, main:bool|None=None) -> bool:
356
+ def has_changes(self, main: bool | None = None) -> bool:
338
357
  """
339
358
  Test whether any subsystem changed since the "tagged" commit
340
359
 
@@ -348,8 +367,7 @@ class Repo(git.Repo,_Common):
348
367
  return True
349
368
  return False
350
369
 
351
-
352
- def tagged(self, c:Commit=None) -> Tag|None:
370
+ def tagged(self, c: Commit = None) -> Tag | None:
353
371
  """Return a commit's tag name.
354
372
  Defaults to the head commit.
355
373
  Returns None if no tag, raises ValueError if more than one is found.
@@ -371,7 +389,6 @@ class Repo(git.Repo,_Common):
371
389
  return tt[0].name
372
390
 
373
391
 
374
-
375
392
  @click.group(short_help="Manage MoaT itself")
376
393
  async def cli():
377
394
  """
@@ -393,22 +410,27 @@ def fix_deps(deps: list[str], tags: dict[str, str]) -> bool:
393
410
  return work
394
411
 
395
412
 
396
- def run_tests(pkg: str|None, *opts) -> bool:
413
+ def run_tests(pkg: str | None, *opts) -> bool:
397
414
  """Run subtests for subpackage @pkg."""
398
415
 
399
416
  if pkg is None:
400
417
  tests = Path("tests")
401
418
  else:
402
- tests = dash(pkg).replace("-","_")
403
- tests = Path("tests")/tests
419
+ tests = dash(pkg).replace("-", "_")
420
+ tests = Path("tests") / tests
404
421
 
405
422
  if not Path(tests):
406
423
  # No tests. Assume it's OK.
407
424
  return True
408
425
  try:
409
426
  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)
427
+ subprocess.run(
428
+ ["python3", "-mpytest", *opts, tests],
429
+ stdin=sys.stdin,
430
+ stdout=sys.stdout,
431
+ stderr=sys.stderr,
432
+ check=True,
433
+ )
412
434
  except subprocess.CalledProcessError:
413
435
  return False
414
436
  else:
@@ -550,18 +572,32 @@ def apply_hooks(repo, force=False):
550
572
 
551
573
  pt = Path(__file__).parent / "_hooks"
552
574
  for f in pt.iterdir():
553
- if not force:
554
- if f.name in seen:
555
- continue
575
+ if not force and f.name in seen:
576
+ continue
556
577
  t = h / f.name
557
578
  d = f.read_text()
558
579
  t.write_text(d)
559
580
  t.chmod(0o755)
560
581
 
561
582
 
562
- def apply_templates(repo):
583
+ @cli.command
584
+ @click.argument("part", type=str)
585
+ def setup(part):
563
586
  """
564
- Apply templates to this repo.
587
+ Create a new MoaT subcommand.
588
+ """
589
+ repo = Repo(None)
590
+ if "-" in part:
591
+ part = undash(part)
592
+
593
+ (Path("packaging") / dash(part)).mkdir()
594
+ (Path("packaging") / dash(part)).mkdir()
595
+ apply_templates(repo, part)
596
+
597
+
598
+ def apply_templates(repo: Repo, part):
599
+ """
600
+ Apply template files to this component.
565
601
  """
566
602
  commas = (
567
603
  P("tool.tox.tox.envlist"),
@@ -569,14 +605,11 @@ def apply_templates(repo):
569
605
  P("tool.pylint.messages_control.disable"),
570
606
  )
571
607
 
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)
608
+ pt = part.split(".")
609
+ rname = dash(part)
610
+ rdot = part
611
+ rpath = "/".join(pt)
612
+ runder = "_".join(pt)
580
613
  repl = Replace(
581
614
  SUBNAME=rname,
582
615
  SUBDOT=rdot,
@@ -584,7 +617,7 @@ def apply_templates(repo):
584
617
  SUBUNDER=runder,
585
618
  )
586
619
  pt = (Path(__file__).parent / "_templates").joinpath
587
- pr = Path(repo.working_dir).joinpath
620
+ pr = Path.cwd().joinpath
588
621
  with pt("pyproject.forced.yaml").open("r") as f:
589
622
  t1 = yload(f)
590
623
  with pt("pyproject.default.yaml").open("r") as f:
@@ -592,27 +625,13 @@ def apply_templates(repo):
592
625
  try:
593
626
  with pr("pyproject.toml").open("r") as f:
594
627
  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
628
 
611
629
  for p in commas:
612
630
  decomma(proj, p)
613
631
 
614
632
  except FileNotFoundError:
615
633
  proj = tomlkit.TOMLDocument()
634
+
616
635
  mod = default_dict(t1, proj, t2, repl=repl, cls=tomlkit.items.Table)
617
636
  try:
618
637
  proc = proj["tool"]["moat"]["fixup"]
@@ -630,31 +649,7 @@ def apply_templates(repo):
630
649
  for p in commas:
631
650
  encomma(proj, p)
632
651
 
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"
652
+ projp = Path("packaging") / rname / "pyproject.toml"
658
653
  projp.write_text(proj.as_string())
659
654
  repo.index.add(projp)
660
655
 
@@ -678,6 +673,11 @@ def apply_templates(repo):
678
673
  pr("moat", "__init__.py").write_text(init)
679
674
  repo.index.add(pr("moat", "__init__.py"))
680
675
 
676
+ if not pr("moat", "_main.py").exists():
677
+ main = repl(pt("moat", "_main.py").read_text())
678
+ pr("moat", "_main.py").write_text(main)
679
+ repo.index.add(pr("moat", "_main.py"))
680
+
681
681
  tst = pr("tests")
682
682
  if not tst.is_dir():
683
683
  tst.mkdir()
@@ -732,16 +732,17 @@ def tags():
732
732
  else:
733
733
  print(f"{r.dash} {tag}")
734
734
 
735
+
735
736
  @cli.command()
736
737
  @click.option("-r", "--run", is_flag=True, help="actually do the tagging")
737
738
  @click.option("-m", "--minor", is_flag=True, help="create a new minor version")
738
739
  @click.option("-M", "--major", is_flag=True, help="create a new major version")
739
740
  @click.option("-s", "--subtree", type=str, help="Tag this partial module")
740
741
  @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")
742
+ @click.option("-q", "--query", "--show", "show", is_flag=True, help="Show the latest tag")
743
+ @click.option("-f", "--force", "FORCE", is_flag=True, help="replace an existing tag")
743
744
  @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
+ def tag(run, minor, major, subtree, force, FORCE, show, build):
745
746
  """
746
747
  Tag the repository (or a subtree).
747
748
 
@@ -782,48 +783,56 @@ def tag(run,minor,major,subtree,force,FORCE,show,build):
782
783
  elif FORCE or build:
783
784
  tag = r.last_tag
784
785
  else:
785
- tag = r.next_tag(major,minor)
786
+ tag = r.next_tag(major, minor)
786
787
 
787
788
  if run or subtree:
788
789
  if subtree:
789
790
  sb = repo.part(r.dash)
790
791
  if build:
791
792
  sb.vers.pkg += 1
792
- sb.vers.rev=repo.head.commit.hexsha
793
+ sb.vers.rev = repo.head.commit.hexsha
793
794
  else:
794
795
  sb.vers = attrdict(
795
796
  tag=tag,
796
797
  pkg=1,
797
798
  rev=repo.head.commit.hexsha,
798
- )
799
+ )
799
800
  repo.write_tags()
800
801
  else:
801
- git.TagReference.create(repo,tag, force=FORCE)
802
+ git.TagReference.create(repo, tag, force=FORCE)
802
803
  print(f"{tag}")
803
804
  else:
804
805
  print(f"{tag} DRY_RUN")
805
806
 
806
807
 
807
- @cli.command(epilog="""
808
+ @cli.command(
809
+ epilog="""
808
810
  The default for building Debian packages is '--no-sign --build=binary'.
809
811
  '--no-sign' is dropped when you use '--deb'.
810
812
  The binary-only build is currently unconditional.
811
813
 
812
814
  The default for uploading to Debian via 'dput' is '--unchecked ext';
813
815
  it is dropped when you use '--dput'.
814
- """)
816
+ """,
817
+ )
815
818
  @click.option("-f", "--no-dirty", is_flag=True, help="don't check for dirtiness (DANGER)")
816
819
  @click.option("-F", "--no-tag", is_flag=True, help="don't check for tag uptodate-ness (DANGER)")
817
820
  @click.option("-D", "--no-deb", is_flag=True, help="don't build Debian packages")
818
821
  @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")
822
+ @click.option(
823
+ "-V",
824
+ "--no-version",
825
+ is_flag=True,
826
+ help="don't update dependency versions in pyproject files",
827
+ )
820
828
  @click.option("-P", "--no-pypi", is_flag=True, help="don't push to PyPI")
821
829
  @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")
830
+ @click.option("-G", "--test-chg", is_flag=True, help="rebuild if changes file doesn't exist")
831
+ @click.option("-o", "--pytest", "pytest_opts", type=str, multiple=True, help="Options for pytest")
832
+ @click.option("-d", "--deb", "deb_opts", type=str, multiple=True, help="Options for debuild")
833
+ @click.option("-p", "--dput", "dput_opts", type=str, multiple=True, help="Options for dput")
825
834
  @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")
835
+ @click.option("-s", "--skip", "skip_", type=str, multiple=True, help="skip these repos")
827
836
  @click.option("-m", "--minor", is_flag=True, help="create a new minor version")
828
837
  @click.option("-M", "--major", is_flag=True, help="create a new major version")
829
838
  @click.option("-t", "--tag", "forcetag", type=str, help="Use this explicit tag value")
@@ -836,10 +845,38 @@ it is dropped when you use '--dput'.
836
845
  help="Update external dependency",
837
846
  )
838
847
  @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):
848
+ @click.pass_obj
849
+ async def build(
850
+ obj,
851
+ no_commit,
852
+ no_dirty,
853
+ no_test,
854
+ no_tag,
855
+ no_pypi,
856
+ parts,
857
+ dput_opts,
858
+ pytest_opts,
859
+ deb_opts,
860
+ run,
861
+ test_chg,
862
+ version,
863
+ no_version,
864
+ no_deb,
865
+ skip_,
866
+ major,
867
+ minor,
868
+ forcetag,
869
+ autotag,
870
+ ):
840
871
  """
841
872
  Rebuild all modified packages.
842
873
  """
874
+ cfg = obj.cfg
875
+ g_done = cfg.get("src", {}).get("done")
876
+ if g_done is not None:
877
+ g_done = Path(g_done)
878
+ else:
879
+ g_done = Path("/tmp/nonexistent")
843
880
  repo = Repo(None)
844
881
 
845
882
  tags = dict(version)
@@ -848,7 +885,7 @@ async def build(no_commit, no_dirty, no_test, no_tag, no_pypi, parts, dput_opts,
848
885
  for sn in s.split(","):
849
886
  skip.add(dash(sn))
850
887
  parts = set(dash(s) for s in parts)
851
- debversion={}
888
+ debversion = {}
852
889
 
853
890
  if no_tag and not no_version:
854
891
  print("Warning: not updating moat versions in pyproject files", file=sys.stderr)
@@ -860,28 +897,30 @@ async def build(no_commit, no_dirty, no_test, no_tag, no_pypi, parts, dput_opts,
860
897
  raise click.UsageError("Can't use an explicit tag with changing minor or major!")
861
898
 
862
899
  if forcetag is None:
863
- forcetag = repo.next_tag(major,minor)
900
+ forcetag = repo.next_tag(major, minor)
864
901
 
865
- full = False
866
902
  if parts:
867
- repos = [ repo.part(x) for x in parts ]
903
+ repos = [repo.part(x) for x in parts]
868
904
  else:
869
905
  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() ]
906
+ pass
907
+ repos = [
908
+ x
909
+ for x in repo.parts
910
+ if not x.hidden and x.dash not in skip and not (PACK / x.dash / "SKIP").exists()
911
+ ]
872
912
 
873
913
  for name in PACK.iterdir():
874
914
  if name.suffix != ".changes":
875
915
  continue
876
- name=name.stem
877
- name,vers,_ = name.split("_")
916
+ name = name.stem
917
+ name, vers, _ = name.split("_")
878
918
  if name.startswith("moat-"):
879
919
  name = name[5:]
880
920
  else:
881
- name = "ext-"+name
882
- debversion[name]=vers.rsplit("-",1)[0]
921
+ name = "ext-" + name
922
+ debversion[name] = vers.rsplit("-", 1)[0]
883
923
 
884
-
885
924
  # Step 0: basic check
886
925
  if not no_dirty:
887
926
  if repo.is_dirty(index=False, working_tree=True, untracked_files=True, submodules=False):
@@ -895,14 +934,22 @@ async def build(no_commit, no_dirty, no_test, no_tag, no_pypi, parts, dput_opts,
895
934
  if autotag:
896
935
  for r in repos:
897
936
  if r.has_changes(True):
937
+ try:
938
+ nt = r.next_tag()
939
+ except AttributeError:
940
+ nt = "1.0.0" if major else "0.1.0" if minor else "0.0.1"
898
941
  r.vers = attrdict(
899
- tag=r.next_tag(),
942
+ tag=nt,
900
943
  pkg=1,
901
944
  rev=repo.head.commit.hexsha,
902
- )
945
+ )
946
+ logger.debug("Changes: %s %s",r.name,r.verstr)
903
947
  elif r.has_changes(False):
904
948
  r.vers.pkg += 1
905
- r.vers.rev=repo.head.commit.hexsha
949
+ r.vers.rev = repo.head.commit.hexsha
950
+ logger.debug("Build Changes: %s %s",r.name,r.verstr)
951
+ else:
952
+ logger.debug("No Changes: %s %s",r.name,r.verstr)
906
953
 
907
954
  elif not no_tag:
908
955
  err = set()
@@ -910,7 +957,7 @@ async def build(no_commit, no_dirty, no_test, no_tag, no_pypi, parts, dput_opts,
910
957
  try:
911
958
  tag = r.last_tag
912
959
  except KeyError:
913
- rd = PACK/r.dash
960
+ rd = PACK / r.dash
914
961
  p = rd / "pyproject.toml"
915
962
  if not p.is_file():
916
963
  continue
@@ -936,15 +983,15 @@ async def build(no_commit, no_dirty, no_test, no_tag, no_pypi, parts, dput_opts,
936
983
  fails.add(p.name)
937
984
  if fails:
938
985
  if not run:
939
- print(f"*** Tests failed:", *fails, file=sys.stderr)
986
+ print("*** Tests failed:", *fails, file=sys.stderr)
940
987
  else:
941
- print(f"Failed tests:", *fails, file=sys.stderr)
942
- print(f"Fix and try again.", file=sys.stderr)
988
+ print("Failed tests:", *fails, file=sys.stderr)
989
+ print("Fix and try again.", file=sys.stderr)
943
990
  return
944
991
 
945
992
  # Step 3: set version and fix versioned dependencies
946
993
  for r in repos:
947
- rd = PACK/r.dash
994
+ rd = PACK / r.dash
948
995
  p = rd / "pyproject.toml"
949
996
  if not p.is_file():
950
997
  # bad=True
@@ -975,52 +1022,89 @@ async def build(no_commit, no_dirty, no_test, no_tag, no_pypi, parts, dput_opts,
975
1022
  # Step 3: copy to packaging dir
976
1023
  for r in repos:
977
1024
  r.copy()
978
-
1025
+
979
1026
  # Step 4: build Debian package
980
1027
  if not no_deb:
981
1028
  if not deb_opts:
982
1029
  deb_opts = ["--no-sign"]
983
1030
 
984
1031
  for r in repos:
985
- rd=PACK/r.dash
1032
+ rd = PACK / r.dash
986
1033
  p = rd / "debian"
987
1034
  if not p.is_dir():
988
1035
  continue
1036
+ if not (rd/"debian"/"changelog").exists():
1037
+ subprocess.run(
1038
+ [
1039
+ "debchange",
1040
+ "--create",
1041
+ "--newversion", f"{r.last_tag}-{r.vers.pkg}",
1042
+ "--package", r.mdash,
1043
+ f"Initial release for {forcetag}",
1044
+ ],
1045
+ cwd=rd,
1046
+ check=True,
1047
+ stdout=sys.stdout,
1048
+ stderr=sys.stderr,
1049
+ )
1050
+
989
1051
  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]
1052
+ res = subprocess.run(
1053
+ ["dpkg-parsechangelog", "-l", "debian/changelog", "-S", "version"],
1054
+ cwd=rd,
1055
+ check=True,
1056
+ stdout=subprocess.PIPE,
1057
+ )
1058
+ tag,ptag = res.stdout.strip().decode("utf-8").rsplit("-", 1)
1059
+ ptag = int(ptag)
992
1060
  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")
1061
+ if tag != ltag or r.vers.pkg != ptag:
1062
+ subprocess.run(
1063
+ [
1064
+ "debchange",
1065
+ "--distribution",
1066
+ "unstable",
1067
+ "--newversion",
1068
+ f"{ltag}-{r.vers.pkg}",
1069
+ f"New release for {forcetag}",
1070
+ ],
1071
+ cwd=rd,
1072
+ check=True,
1073
+ )
1074
+ repo.index.add(p / "changelog")
1075
+
1076
+ changes = PACK / f"{r.mdash}_{ltag}-{r.vers.pkg}_{ARCH}.changes"
1077
+ if debversion.get(r.dash, "") != ltag or r.vers.pkg != ptag or test_chg and not changes.exists():
996
1078
 
997
- if debversion.get(r.dash,"") != ltag:
998
1079
  subprocess.run(["debuild", "--build=binary"] + deb_opts, cwd=rd, check=True)
999
1080
  except subprocess.CalledProcessError:
1000
1081
  if not run:
1001
- print("*** Failure packaging",r.name,file=sys.stderr)
1082
+ print("*** Failure packaging", r.name, file=sys.stderr)
1002
1083
  else:
1003
- print("Failure packaging",r.name,file=sys.stderr)
1004
- return
1084
+ print("Failure packaging", r.name, file=sys.stderr)
1085
+ no_commit=True
1086
+ no_deb=True
1087
+ no_pypi=True
1005
1088
 
1006
1089
  # Step 5: build PyPI package
1007
1090
  if not no_pypi:
1008
- err=set()
1009
- up=set()
1091
+ err = set()
1092
+ up = set()
1010
1093
  for r in repos:
1011
- rd=PACK/r.dash
1094
+ rd = PACK / r.dash
1012
1095
  p = rd / "pyproject.toml"
1013
1096
  if not p.is_file():
1014
1097
  continue
1015
1098
  tag = r.last_tag
1016
1099
  name = r.dash
1017
1100
  if name.startswith("ext-"):
1018
- name=name[4:]
1101
+ name = name[4:]
1019
1102
  else:
1020
- name="moat-"+r.dash
1103
+ name = "moat-" + r.dash
1104
+
1105
+ targz = rd / "dist" / f"{r.under}-{tag}.tar.gz"
1106
+ done = rd / "dist" / f"{r.under}-{tag}.done"
1021
1107
 
1022
- targz = rd/"dist"/f"{r.under}-{tag}.tar.gz"
1023
- done = rd/"dist"/f"{r.under}-{tag}.done"
1024
1108
  if targz.is_file():
1025
1109
  if not done.exists():
1026
1110
  up.add(r)
@@ -1039,50 +1123,60 @@ async def build(no_commit, no_dirty, no_test, no_tag, no_pypi, parts, dput_opts,
1039
1123
  print("Build errors:", file=sys.stderr)
1040
1124
  print(*err, file=sys.stderr)
1041
1125
  print("Please fix and try again.", file=sys.stderr)
1042
- return
1043
-
1126
+ no_commit=True
1127
+ no_deb=True
1128
+
1044
1129
  # Step 6: upload PyPI package
1045
- if run:
1046
- err=set()
1130
+ elif run:
1131
+ err = set()
1047
1132
  for r in up:
1048
- rd=PACK/r.dash
1133
+ rd = PACK / r.dash
1049
1134
  p = rd / "pyproject.toml"
1050
1135
  if not p.is_file():
1051
1136
  continue
1052
1137
  tag = r.last_tag
1053
1138
  name = r.dash
1054
1139
  if name.startswith("ext-"):
1055
- name=name[4:]
1140
+ name = name[4:]
1056
1141
  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"
1142
+ name = "moat-" + r.dash
1143
+ targz = Path("dist") / f"{r.under}-{tag}.tar.gz"
1144
+ whl = Path("dist") / f"{r.under}-{tag}-py3-none-any.whl"
1060
1145
  try:
1061
- res = subprocess.run(["twine", "upload", str(targz), str(whl)], cwd=rd, check=True)
1146
+ res = subprocess.run(
1147
+ ["twine", "upload", str(targz), str(whl)],
1148
+ cwd=rd,
1149
+ check=True,
1150
+ )
1062
1151
  except subprocess.CalledProcessError:
1063
1152
  err.add(r.name)
1064
1153
  else:
1065
- done = rd/"dist"/f"{r.under}-{tag}.done"
1154
+ done = rd / "dist" / f"{r.under}-{tag}.done"
1066
1155
  done.touch()
1067
1156
  if err:
1068
1157
  print("Upload errors:", file=sys.stderr)
1069
1158
  print(*err, file=sys.stderr)
1070
1159
  print("Please fix(?) and try again.", file=sys.stderr)
1071
- return
1160
+ no_commit=True
1161
+ no_deb=True
1072
1162
 
1073
1163
  # Step 7: upload Debian package
1074
1164
  if run and not no_deb:
1075
1165
  err = set()
1076
1166
  if not dput_opts:
1077
- dput_opts = ["-u","ext"]
1167
+ dput_opts = ["-u", "ext"]
1078
1168
  for r in repos:
1079
1169
  ltag = r.last_tag
1080
- if not (PACK/r.dash/"debian").is_dir():
1170
+ if not (PACK / r.dash / "debian").is_dir():
1081
1171
  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"
1172
+ changes = PACK / f"{r.mdash}_{ltag}-{r.vers.pkg}_{ARCH}.changes"
1173
+ done = PACK / f"{r.mdash}_{ltag}-{r.vers.pkg}_{ARCH}.done"
1084
1174
  if done.exists():
1085
1175
  continue
1176
+ if g_done is not None:
1177
+ gdone = g_done / f"{r.mdash}_{ltag}-{r.vers.pkg}_{ARCH}.done"
1178
+ if gdone.exists():
1179
+ continue
1086
1180
  try:
1087
1181
  subprocess.run(["dput", *dput_opts, str(changes)], check=True)
1088
1182
  except subprocess.CalledProcessError:
@@ -1093,7 +1187,7 @@ async def build(no_commit, no_dirty, no_test, no_tag, no_pypi, parts, dput_opts,
1093
1187
  print("Upload errors:", file=sys.stderr)
1094
1188
  print(*err, file=sys.stderr)
1095
1189
  print("Please fix(?) and try again.", file=sys.stderr)
1096
- return
1190
+ no_commit=True
1097
1191
 
1098
1192
  # Step 8: commit the result
1099
1193
  if run: