proj-flow 0.11.2__py3-none-any.whl → 0.12.0__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.
proj_flow/__init__.py CHANGED
@@ -6,4 +6,4 @@ The **proj_flow** contains only ``__version__`` to be updated, nothing more.
6
6
  This is in an attempt to make this module easy to load initially.
7
7
  """
8
8
 
9
- __version__ = "0.11.2"
9
+ __version__ = "0.12.0"
proj_flow/api/release.py CHANGED
@@ -56,6 +56,14 @@ class Project:
56
56
  def tag_name(self):
57
57
  return f"v{self.version}"
58
58
 
59
+ @property
60
+ def package_prefix(self):
61
+ return f"{self.archive_name}-"
62
+
63
+ @property
64
+ def package_suffix(self):
65
+ return ""
66
+
59
67
 
60
68
  class ProjectSuite(ABC):
61
69
  @abstractmethod
@@ -97,3 +105,13 @@ class ProjectSuite(ABC):
97
105
 
98
106
 
99
107
  project_suites = registry.Registry[ProjectSuite]("ProjectSuite")
108
+
109
+
110
+ def get_project(rt: env.Runtime):
111
+ def wrap(suite: ProjectSuite):
112
+ return suite.get_project(rt)
113
+
114
+ _, project = project_suites.find(wrap)
115
+ if project is None:
116
+ rt.fatal(f"Cannot get project information from {rt.root}")
117
+ return project
proj_flow/base/cmd.py CHANGED
@@ -5,8 +5,10 @@
5
5
  The **proj_flow.base.cmd** defines environment for `proj_flow.api.env.Runtime`.
6
6
  """
7
7
 
8
+ import os
8
9
  import shutil
9
10
  import subprocess
11
+ from contextlib import contextmanager
10
12
  from typing import Optional
11
13
 
12
14
 
@@ -48,3 +50,13 @@ def run(app: str, *args: str, capture_output=False):
48
50
  return subprocess.run(
49
51
  [cmd, *args], shell=False, encoding="UTF-8", capture_output=capture_output
50
52
  )
53
+
54
+
55
+ @contextmanager
56
+ def cd(path: str):
57
+ prev = os.getcwd()
58
+ os.chdir(path)
59
+ try:
60
+ yield
61
+ finally:
62
+ os.chdir(prev)
@@ -7,6 +7,6 @@ The **proj_flow.ext.cplusplus.cmake** provides ``"CMake"``, ``"Build"``,
7
7
  context.
8
8
  """
9
9
 
10
- from . import parser, steps, version
10
+ from . import parser, project, steps
11
11
 
12
- __all__ = ["parser", "steps", "version"]
12
+ __all__ = ["parser", "project", "steps"]
@@ -5,12 +5,8 @@
5
5
  The **proj_flow.ext.cplusplus.cmake.version** provides project suite plugin.
6
6
  """
7
7
 
8
- import os
9
- import re
10
8
  from typing import NamedTuple, Optional
11
9
 
12
- import toml
13
-
14
10
  from proj_flow.api import env, release
15
11
  from proj_flow.ext.cplusplus.cmake.parser import get_project
16
12
 
@@ -15,8 +15,9 @@ import sys
15
15
  import typing
16
16
 
17
17
  from proj_flow import log
18
- from proj_flow.api import arg, env
18
+ from proj_flow.api import arg, env, release
19
19
  from proj_flow.base.name_list import name_list
20
+ from proj_flow.ext.github import publishing
20
21
  from proj_flow.flow.configs import Configs
21
22
  from proj_flow.log import commit, hosting, rich_text
22
23
 
@@ -63,7 +64,7 @@ def matrix(
63
64
 
64
65
 
65
66
  @arg.command("github", "release")
66
- def release(
67
+ def release_cmd(
67
68
  rt: env.Runtime,
68
69
  all: typing.Annotated[
69
70
  bool, arg.FlagArgument(help="Take all Conventional Commits.")
@@ -86,9 +87,9 @@ def release(
86
87
  ],
87
88
  ):
88
89
  """
89
- Bumps the project version based on current git logs, creates a "chore"
90
- commit for the change, attaches an annotated tag with the version number
91
- and pushes it all to GitHub.
90
+ Bump the project version based on current git logs, create a "chore"
91
+ commit for the change, attach an annotated tag with the version number
92
+ and push it all to GitHub.
92
93
  """
93
94
 
94
95
  generator = (
@@ -123,3 +124,62 @@ def release(
123
124
  with open(GITHUB_OUTPUT, "a", encoding="UTF-8") as github_output:
124
125
  print(f"tag={next_tag}", file=github_output)
125
126
  print(f"released={json.dumps(released)}", file=github_output)
127
+
128
+
129
+ @arg.command("github", "publish")
130
+ def publish(
131
+ rt: env.Runtime,
132
+ ref: typing.Annotated[
133
+ typing.Optional[str],
134
+ arg.Argument(
135
+ help="Publish this release draft. In case this is called from within "
136
+ "GitHub Actions and your release is named exactly like the tag used to "
137
+ "trigger this flow, you can use ${{github.action_ref}} variable. "
138
+ "Defaults to current tag.",
139
+ meta="release",
140
+ ),
141
+ ],
142
+ upload: typing.Annotated[
143
+ typing.Optional[str],
144
+ arg.Argument(
145
+ help="If present, will upload files from the directory to "
146
+ "the referenced release before publishing.",
147
+ meta="directory",
148
+ ),
149
+ ],
150
+ ):
151
+ """
152
+ Upload package artifacts to a GitHub release and in case the release
153
+ is still in draft, publish it.
154
+ """
155
+
156
+ git = commit.Git(rt)
157
+ gh_links = hosting.github.GitHub.from_repo(git) or commit.NoHosting()
158
+ project = release.get_project(rt)
159
+
160
+ tag_name = ref or project.tag_name
161
+
162
+ release_info = gh_links.locate_release(tag_name)
163
+ if release_info is None and not rt.dry_run:
164
+ rt.fatal(f"No release matches {tag_name}")
165
+
166
+ if upload is not None:
167
+ matcher = publishing.build_regex(project)
168
+ directory, names = publishing.gather_artifacts(upload, matcher)
169
+ if not len(names):
170
+ rt.fatal(f"No artifact matches {matcher.pattern}")
171
+
172
+ publishing.checksums(rt, directory, names, "sha256sum.txt")
173
+
174
+ if release_info is not None:
175
+ gh_links.upload_to_release(release_info, directory, names)
176
+ else:
177
+ rt.message(f"Would upload:", level=env.Msg.STATUS)
178
+ for name in names:
179
+ rt.message(f" * {name}", level=env.Msg.STATUS)
180
+
181
+ if release_info is not None:
182
+ info = gh_links.publish(release_info)
183
+ if info.url:
184
+ msg = "Visit draft at" if info.is_draft else "Visit release at"
185
+ rt.message(msg, info.url, level=env.Msg.ALWAYS)
@@ -0,0 +1,65 @@
1
+ # Copyright (c) 2025 Marcin Zdun
2
+ # This code is licensed under MIT license (see LICENSE for details)
3
+
4
+ """
5
+ The **proj_flow.ext.github.publishing** provides utilities for ``github
6
+ publish`` command.
7
+ """
8
+
9
+
10
+ import hashlib
11
+ import io
12
+ import os
13
+ import re
14
+ import typing
15
+ import zipfile
16
+
17
+ from proj_flow.api import env, release
18
+
19
+
20
+ def _safe_regex(value: str) -> str:
21
+ for esc in "\\.+*?()[]":
22
+ value = value.replace(esc, f"\\{esc}")
23
+ return value
24
+
25
+
26
+ def build_regex(project: release.Project):
27
+ regexPre = _safe_regex(project.package_prefix)
28
+ regexPost = _safe_regex(project.package_suffix)
29
+ regex = f"^{regexPre}(.*){regexPost}$"
30
+ return re.compile(regex)
31
+
32
+
33
+ def gather_artifacts(directory: str, matcher: re.Pattern):
34
+ if os.path.isdir(directory):
35
+ for _, dirnames, filenames in os.walk(directory):
36
+ dirnames[:] = []
37
+ names = [name for name in filenames if matcher.match(name)]
38
+ else:
39
+ next_directory = f"{directory}-dir"
40
+ os.makedirs(next_directory, exist_ok=True)
41
+ with zipfile.ZipFile(directory) as zip:
42
+ names = [name for name in zip.namelist() if matcher.match(name)]
43
+ for name in names:
44
+ zip.extract(name, path=next_directory)
45
+ directory = next_directory
46
+
47
+ return directory, names
48
+
49
+
50
+ def _hash(filename: str) -> str:
51
+ sha = hashlib.sha256()
52
+ with open(filename, "rb") as data:
53
+ for block in iter(lambda: data.read(io.DEFAULT_BUFFER_SIZE), b""):
54
+ sha.update(block)
55
+ return sha.hexdigest()
56
+
57
+
58
+ def checksums(rt: env.Runtime, directory: str, names: typing.List[str], outname: str):
59
+ rt.print("sha256sum", "-b")
60
+ if not rt.dry_run:
61
+ with open(os.path.join(directory, outname), "w") as output:
62
+ for name in names:
63
+ digest = _hash(os.path.join(directory, name))
64
+ print(f"{digest} *{name}", file=output)
65
+ names.append(outname)
@@ -15,6 +15,7 @@ from contextlib import contextmanager
15
15
  from typing import Any, Callable, Dict, List, Optional, cast
16
16
 
17
17
  from proj_flow.api import env, step
18
+ from proj_flow.base import cmd
18
19
 
19
20
 
20
21
  @step.register
@@ -172,16 +173,6 @@ _job_listing = [
172
173
  ]
173
174
 
174
175
 
175
- @contextmanager
176
- def _cd(path: str):
177
- prev = os.getcwd()
178
- os.chdir(path)
179
- try:
180
- yield
181
- finally:
182
- os.chdir(prev)
183
-
184
-
185
176
  def _get_venv_path(root: str):
186
177
  bindir = os.path.join(".venv", "bin")
187
178
  scripts = os.path.join(".venv", "Scripts")
@@ -198,7 +189,7 @@ def _get_venv_path(root: str):
198
189
  def _activate_virtual_env(venv, root: str):
199
190
  global PYTHON_EXECUTABLE
200
191
 
201
- with _cd(root):
192
+ with cmd.cd(root):
202
193
  exec_ext = ".exe" if sys.platform == "win32" else ""
203
194
  python_exec = f"python{exec_ext}"
204
195
  bindir = _get_venv_path(root)
@@ -104,7 +104,7 @@ class SignFiles(SignBase):
104
104
  continue
105
105
 
106
106
  full_path = os.path.join(curr_dir, filename)
107
- if tool.is_executable(full_path, as_package=False):
107
+ if tool.is_signable(full_path, as_package=False):
108
108
  result.append(full_path)
109
109
  return result
110
110
 
@@ -134,7 +134,7 @@ class SignMsi(SignBase):
134
134
  dirnames[:] = []
135
135
  for filename in filenames:
136
136
  full_path = os.path.join(curr_dir, filename)
137
- if tool.is_executable(full_path, as_package=False):
137
+ if tool.is_signable(full_path, as_package=True):
138
138
  result.append(full_path)
139
139
 
140
140
  return result
@@ -142,9 +142,8 @@ class SignMsi(SignBase):
142
142
 
143
143
  class SignInit(init.InitStep):
144
144
  def postprocess(self, rt: env.Runtime, context: dict):
145
- if sys.platform == "win32":
146
- with open(".gitignore", "ab") as ignoref:
147
- ignoref.write("\n/signature.key\n".encode("UTF-8"))
145
+ with open(".gitignore", "ab") as ignoref:
146
+ ignoref.write("\n/signature.key\n".encode("UTF-8"))
148
147
 
149
148
 
150
149
  init.register_init_step(SignInit())
proj_flow/ext/sign/api.py CHANGED
@@ -77,7 +77,7 @@ class SigningTool(ABC):
77
77
  def sign(self, config: env.Config, rt: env.Runtime, files: List[str]): ...
78
78
 
79
79
  @abstractmethod
80
- def is_executable(self, filename: str, as_package: bool): ...
80
+ def is_signable(self, filename: str, as_package: bool): ...
81
81
 
82
82
 
83
83
  signing_tool = base.registry.Registry[SigningTool]("SigningTool")
@@ -34,7 +34,9 @@ if sys.platform == "win32":
34
34
  def is_signable(self, filename: str, as_package: bool):
35
35
  if as_package:
36
36
  _, ext = os.path.splitext(filename)
37
- return ext.lower() == ".msi"
37
+ if ext.lower() == ".msi":
38
+ return True
39
+ # suport NSIS by checking if the archive is a PE
38
40
  return _is_pe_exec(filename)
39
41
 
40
42
  Version = Tuple[int, int, int]
proj_flow/ext/store.py CHANGED
@@ -25,13 +25,6 @@ def _package_name(config: env.Config, pkg: str, group: str):
25
25
  return f"{pkg}-{_system}{_version}-{_arch}{debug}{suffix}"
26
26
 
27
27
 
28
- def _get_project(rt: env.Runtime):
29
- def wrap(suite: release.ProjectSuite):
30
- return suite.get_project(rt)
31
-
32
- return wrap
33
-
34
-
35
28
  @step.register
36
29
  class StorePackages(step.Step):
37
30
  """Stores archives and installers build for ``preset`` config value."""
@@ -52,10 +45,7 @@ class StorePackages(step.Step):
52
45
 
53
46
  global _project_pkg
54
47
  if _project_pkg is None:
55
- _, project = release.project_suites.find(_get_project(rt))
56
- if project is None:
57
- rt.fatal(f"Cannot get project information from {rt.root}")
58
- _project_pkg = project.archive_name
48
+ _project_pkg = release.get_project(rt).archive_name
59
49
 
60
50
  main_group = cast(str, rt._cfg.get("package", {}).get("main-group"))
61
51
  if main_group is not None and not rt.dry_run:
proj_flow/log/commit.py CHANGED
@@ -188,7 +188,10 @@ def _sem_ver(tag: str):
188
188
 
189
189
  @dataclass
190
190
  class ReleaseInfo:
191
- draft_url: Optional[str]
191
+ url: Optional[str] = None
192
+ is_draft: Optional[bool] = None
193
+ ref: Optional[str] = None
194
+ tag: Optional[str] = None
192
195
 
193
196
 
194
197
  class Hosting(ABC):
@@ -241,6 +244,32 @@ class Hosting(ABC):
241
244
  """
242
245
  ...
243
246
 
247
+ @abstractmethod
248
+ def locate_release(self, release_name: str) -> Optional[ReleaseInfo]:
249
+ """
250
+ Locate a release by its name.
251
+ """
252
+ ...
253
+
254
+ @abstractmethod
255
+ def upload_to_release(
256
+ self,
257
+ release: ReleaseInfo,
258
+ directory: str,
259
+ names: list[str],
260
+ ):
261
+ """
262
+ Upload package artifacts to the release.
263
+ """
264
+ ...
265
+
266
+ @abstractmethod
267
+ def publish(self, release: ReleaseInfo) -> ReleaseInfo:
268
+ """
269
+ Publish given release, return updated release info.
270
+ """
271
+ ...
272
+
244
273
 
245
274
  class NoHosting(Hosting):
246
275
  """
@@ -266,7 +295,21 @@ class NoHosting(Hosting):
266
295
  def add_release(
267
296
  self, log: ChangeLog, setup: "LogSetup", git: "Git", draft: bool
268
297
  ) -> ReleaseInfo:
269
- return ReleaseInfo(draft_url=None)
298
+ return ReleaseInfo(is_draft=False)
299
+
300
+ def locate_release(self, release_name: str) -> Optional[ReleaseInfo]:
301
+ return None
302
+
303
+ def upload_to_release(
304
+ self,
305
+ release: ReleaseInfo,
306
+ directory: str,
307
+ names: list[str],
308
+ ):
309
+ return None
310
+
311
+ def publish(self, release: ReleaseInfo):
312
+ return ReleaseInfo(is_draft=False)
270
313
 
271
314
 
272
315
  @dataclass
@@ -23,6 +23,21 @@ class _GitHub(NamedTuple):
23
23
  _NO_GITHUB = _GitHub("", "", "")
24
24
 
25
25
 
26
+ class ReleaseInfo(commit.ReleaseInfo):
27
+ id: int
28
+
29
+ def __init__(
30
+ self,
31
+ url: Optional[str],
32
+ is_draft: Optional[bool],
33
+ id: int,
34
+ ref: Optional[str] = None,
35
+ tag: Optional[str] = None,
36
+ ):
37
+ super().__init__(url, is_draft, ref, tag)
38
+ self.id = id
39
+
40
+
26
41
  class GitHub(commit.Hosting):
27
42
  """
28
43
  Generates links to GitHub.
@@ -78,6 +93,7 @@ class GitHub(commit.Hosting):
78
93
  *args: str,
79
94
  method: Optional[str] = None,
80
95
  capture_output: bool = True,
96
+ ro_call: bool = False,
81
97
  **kwargs,
82
98
  ):
83
99
  url = f"{self.root}{res}"
@@ -94,15 +110,18 @@ class GitHub(commit.Hosting):
94
110
  call.extend(["--method", method.upper()])
95
111
 
96
112
  if capture_output:
97
- if self.rt.dry_run:
113
+ if not ro_call and self.rt.dry_run:
98
114
  self.rt.print(*call, url, *args)
99
115
  return None
100
116
  return self.rt.capture(*call, url, *args)
101
117
 
102
- self.rt.print(*call, url, *args)
118
+ return self._run(*call, url, *args)
119
+
120
+ def _run(self, *args: str):
121
+ self.rt.print(*args)
103
122
  if self.rt.dry_run:
104
123
  return None
105
- return cmd.run(*call, url, *args)
124
+ return cmd.run(*args)
106
125
 
107
126
  def json_from(
108
127
  self,
@@ -111,8 +130,9 @@ class GitHub(commit.Hosting):
111
130
  method: Optional[str] = None,
112
131
  server: Optional[str] = None,
113
132
  default: Any = {},
133
+ ro_call: bool = False,
114
134
  ):
115
- proc = self.gh(res, *args, method=method, server=server)
135
+ proc = self.gh(res, *args, method=method, server=server, ro_call=ro_call)
116
136
  if proc is None:
117
137
  return default
118
138
 
@@ -131,6 +151,15 @@ class GitHub(commit.Hosting):
131
151
 
132
152
  return json.loads(proc.stdout)
133
153
 
154
+ def _release_from_json(self, data: dict, draft: bool = False):
155
+ html_url = cast(Optional[str], data.get("html_url"))
156
+ draft = cast(bool, data.get("draft", draft))
157
+ id = cast(int, data.get("id", 0))
158
+ name = cast(str, data.get("name"))
159
+ tag_name = cast(str, data.get("tag_name"))
160
+
161
+ return ReleaseInfo(url=html_url, is_draft=draft, id=id, ref=name, tag=tag_name)
162
+
134
163
  def add_release(
135
164
  self,
136
165
  log: commit.ChangeLog,
@@ -162,9 +191,46 @@ class GitHub(commit.Hosting):
162
191
  flags.append(f"{name}={value}")
163
192
 
164
193
  data = self.json_from("/releases", *flags, method="POST", default={})
165
- html_url = cast(Optional[str], data.get("html_url"))
194
+ return self._release_from_json(data, draft)
166
195
 
167
- return commit.ReleaseInfo(draft_url=html_url)
196
+ def locate_release(self, release_name: str) -> Optional[commit.ReleaseInfo]:
197
+ releases = self.json_from("/releases", default=[], ro_call=True)
198
+ for release in releases:
199
+ if release.get("name") == release_name:
200
+ return self._release_from_json(release)
201
+
202
+ return None
203
+
204
+ def upload_to_release(
205
+ self,
206
+ release: commit.ReleaseInfo,
207
+ directory: str,
208
+ names: list[str],
209
+ ):
210
+ with cmd.cd(directory):
211
+ return self._run(
212
+ "gh",
213
+ "release",
214
+ "upload",
215
+ release.tag or release.ref or "",
216
+ *names,
217
+ "--clobber",
218
+ )
219
+
220
+ def publish(self, release: commit.ReleaseInfo) -> commit.ReleaseInfo:
221
+ if not isinstance(release, ReleaseInfo):
222
+ return commit.ReleaseInfo(is_draft=False)
223
+
224
+ release_id = release.id
225
+ data = self.json_from(
226
+ f"/releases/{release_id}",
227
+ "-f",
228
+ "draft=false",
229
+ "-F",
230
+ "make_latest=legacy",
231
+ method="PATCH",
232
+ )
233
+ return self._release_from_json(data)
168
234
 
169
235
  @staticmethod
170
236
  def from_repo(git: commit.Git, remote: Optional[str] = None):
proj_flow/log/release.py CHANGED
@@ -130,8 +130,9 @@ def add_release(
130
130
  git.annotated_tag(setup.curr_tag, commit_message)
131
131
 
132
132
  if hosting.is_active:
133
- draft_url = hosting.add_release(changelog, setup, git, draft).draft_url
134
- if draft_url:
135
- rt.message("Visit draft at", draft_url, level=env.Msg.ALWAYS)
133
+ info = hosting.add_release(changelog, setup, git, draft)
134
+ if info.url:
135
+ msg = "Visit draft at" if info.is_draft else "Visit release at"
136
+ rt.message(msg, info.url, level=env.Msg.ALWAYS)
136
137
 
137
138
  return setup.curr_tag
@@ -10,7 +10,7 @@ import subprocess
10
10
  import sys
11
11
  from contextlib import contextmanager
12
12
  from dataclasses import dataclass
13
- from typing import List, Optional, cast
13
+ from typing import Optional, cast
14
14
 
15
15
  CXX_FLOW_VERSION = "{{__flow_version__}}"
16
16
  PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__))
@@ -52,7 +52,7 @@ PYTHON_EXECUTABLE = sys.executable
52
52
 
53
53
 
54
54
  def python(
55
- *args: List[str],
55
+ *args: str,
56
56
  module: Optional[str] = None,
57
57
  capture_output: bool = True,
58
58
  ) -> subprocess.CompletedProcess:
@@ -67,11 +67,11 @@ def python(
67
67
  )
68
68
 
69
69
 
70
- def pip(*args: List[str], capture_output: bool = False):
70
+ def pip(*args: str, capture_output: bool = False):
71
71
  return python(*args, module="pip", capture_output=capture_output)
72
72
 
73
73
 
74
- def venv(*args: List[str], capture_output: bool = False):
74
+ def venv(*args: str, capture_output: bool = False):
75
75
  return python(*args, module="venv", capture_output=capture_output)
76
76
 
77
77
 
@@ -126,13 +126,18 @@ def activate_virtual_env():
126
126
  venv(".venv")
127
127
  bindir = get_venv_path()
128
128
 
129
- os.environ["PATH"] = (
130
- f"{os.path.abspath(bindir)}{os.pathsep}{os.environ['PATH']}"
131
- )
132
- PYTHON_EXECUTABLE = shutil.which("python") or sys.executable
129
+ if bindir is not None:
130
+ os.environ["PATH"] = (
131
+ f"{os.path.abspath(bindir)}{os.pathsep}{os.environ['PATH']}"
132
+ )
133
+ PYTHON_EXECUTABLE = shutil.which("python") or sys.executable
133
134
 
134
135
 
135
136
  def bootstrap_cxx_flow():
137
+ if proj_flow is None:
138
+ print('Cannot parse version "{CXX_FLOW_VERSION}"', file=sys.stderr)
139
+ return False
140
+
136
141
  if proj_flow.compatible_with(cxx_flow_version()):
137
142
  return True
138
143
 
@@ -159,13 +164,15 @@ def bootstrap_cxx_flow():
159
164
 
160
165
  def main():
161
166
  if not bootstrap_cxx_flow():
162
- print("Cannot find a working copy of proj-flow", file=sys.stderr)
167
+ print("Cannot find a working copy of proj-flow package", file=sys.stderr)
163
168
  return 1
164
169
 
165
170
  with cd(PROJECT_ROOT):
166
- return subprocess.run(
167
- [shutil.which("proj-flow"), *sys.argv[1:]], shell=False
168
- ).returncode
171
+ executable = shutil.which("proj-flow")
172
+ if executable is None:
173
+ print("Cannot find a working copy of proj-flow executable", file=sys.stderr)
174
+ return 1
175
+ return subprocess.run([executable, *sys.argv[1:]]).returncode
169
176
 
170
177
 
171
178
  if __name__ == "__main__":
@@ -37,3 +37,5 @@ build/
37
37
  web.log
38
38
  super-linter.log
39
39
  *.profraw
40
+
41
+ .venv/
@@ -5,7 +5,8 @@ set(CPACK_WIX_UI_DIALOG "${PROJECT_SOURCE_DIR}/data/assets/wix_dialog.bmp")
5
5
  set(CPACK_WIX_ROOT_FEATURE_TITLE "{{PROJECT.NAME}} ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
6
6
  set(CPACK_WIX_PATCH_FILE "${CMAKE_CURRENT_BINARY_DIR}/packages/_CPack_Packages/patches.wix")
7
7
  set(PROJECT_WIX_BIN_DIR "CM_DP_bin")
8
- set(PROJECT_WIX_BIN_DIR_COMP "CM_CP_bin.${CMAKE_PROJECT_NAME}.exe")
8
+ string(REPLACE "-" "_" SAVE_PROJECT_NAME "${CMAKE_PROJECT_NAME}")
9
+ set(PROJECT_WIX_BIN_DIR_COMP "CM_CP_bin.${SAVE_PROJECT_NAME}.exe")
9
10
  set(WIX_PATH_ID "{{NAME_PREFIX}}_PATH")
10
11
  set(CPACK_WIX_VERSION 4)
11
12
 
@@ -23,7 +23,7 @@ jobs:
23
23
 
24
24
  - name: Find builds
25
25
  id: flow
26
- run: python ./.flow/flow.py ci matrix
26
+ run: python ./.flow/flow.py github matrix
27
27
 
28
28
  build:
29
29
  needs: M
@@ -137,8 +137,6 @@ jobs:
137
137
  - name: Install Conan
138
138
  id: conan
139
139
  uses: turtlebrowser/get-conan@main
140
- with:
141
- version: 1.66.0
142
140
 
143
141
  - name: Conan cache
144
142
  uses: actions/cache@v4
@@ -237,5 +235,5 @@ jobs:
237
235
  name: Packages
238
236
  path: ${{github.workspace}}/build/download/packages
239
237
 
240
- - name: Upload to release
241
- run: python ./.flow/flow.py ci upload --ref "${{github.action_ref}}" "${{github.workspace}}/build/download/packages"
238
+ - name: Publish the release
239
+ run: python ./.flow/flow.py github publish --upload "${{github.workspace}}/build/download/packages"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proj-flow
3
- Version: 0.11.2
3
+ Version: 0.12.0
4
4
  Summary: C++ project maintenance, automated
5
5
  Project-URL: Changelog, https://github.com/mzdun/proj-flow/blob/main/CHANGELOG.rst
6
6
  Project-URL: Documentation, https://proj-flow.readthedocs.io/en/latest/
@@ -1,4 +1,4 @@
1
- proj_flow/__init__.py,sha256=N5R_dE3paskr8VDXvEr4Ph9hxPztPeQqsJRqn8VNmlM,277
1
+ proj_flow/__init__.py,sha256=Mf9UJykcCb4X0VYyQaxJUTbXNQ01iQ-O5RWYPP6Xi-E,277
2
2
  proj_flow/__main__.py,sha256=HUar_qQ9Ndmchmryegtzu__5wukwCLrFN_SGRl5Ol_M,233
3
3
  proj_flow/dependency.py,sha256=CpcnR6El8AO9hlLc9lQtYQADYlkx3GMHlkLYbEAtdMI,4639
4
4
  proj_flow/api/__init__.py,sha256=gV2f6kll_5JXtvkGASvnx7CbOWr34PHOdck-4ce-qEk,378
@@ -8,11 +8,11 @@ proj_flow/api/ctx.py,sha256=IJu0q0Chivo6b2M4MKkAlV09oi7Cn9VxtDFeAeL_tnc,6646
8
8
  proj_flow/api/env.py,sha256=VOJbyyMQydPLV4-xQ9ylxwhkvJ8EhZro1GOYPfUSnCU,12629
9
9
  proj_flow/api/init.py,sha256=p4ZDGfq6fw4bXbJu2iq0vEmVxbS7nALIhZfe-XnEs44,565
10
10
  proj_flow/api/makefile.py,sha256=AxtGOvmypBtSvoyMEDJq1bGoaVD5yW9YYRZSdunUEeg,3856
11
- proj_flow/api/release.py,sha256=A-t4qcjeaptEgfBCZs2Q9cOjsGACrNM0CzmCorms0U4,2401
11
+ proj_flow/api/release.py,sha256=IM4axJ6dfyilCmpwL_Z8q43XGKsfHaPoMAdqSziwT7s,2810
12
12
  proj_flow/api/step.py,sha256=AOqe1wXYnU_O3pD5KlzfyyOm_D9fcF_4vyhhr1w-NrI,4561
13
13
  proj_flow/base/__cmake_version__.py,sha256=imja0GnhpBvS8Crz-64eOUKhc4i6FeRrjBGRB68x_p0,239
14
14
  proj_flow/base/__init__.py,sha256=RRmqiYjdVlC4i8QijXHNiRh9yzNH8305WXezrSaPjKk,311
15
- proj_flow/base/cmd.py,sha256=Vo3e8kd4CHRzbsUkO-MnbhQxjLXTczv7YQRUkFlcBQE,1560
15
+ proj_flow/base/cmd.py,sha256=XJk_r4Nlq_2XGgD_w92Us4WKwItmQAB8QWdT1pKxUFA,1746
16
16
  proj_flow/base/inspect.py,sha256=lt5P19rvSZ-wMCTrCYAaQFCt2S9fUjEQXlrKK-Tmvwc,2786
17
17
  proj_flow/base/matrix.py,sha256=8XBFGYOwW6Myt_4h3WNk36V2bJ5xeqUv6DvzF4B3q_g,7767
18
18
  proj_flow/base/name_list.py,sha256=KiHSnbDgYplJc25O3EehYhFAhD7Z3mHVAK6UYOdg5PQ,416
@@ -25,38 +25,39 @@ proj_flow/cli/finder.py,sha256=5x7H1nH0k63DetDauhB_wABel_f0RQpsZ5YnhPfbkRc,1402
25
25
  proj_flow/ext/__init__.py,sha256=XD52rUFTPz3GnyRq6KZUNeWdMce7e0bB19iTx-zU6DE,169
26
26
  proj_flow/ext/markdown_changelog.py,sha256=fRGL09jojnv2B-8vAX2prvgNp8e7uyq5NxboSZjFCJ8,436
27
27
  proj_flow/ext/re_structured_changelog.py,sha256=UF23W9eu_YgPO42MiaoDbEKu8In_48mQg6rH9--mI30,459
28
- proj_flow/ext/store.py,sha256=yfyIb2G7UhoIkPmVDnp1RPx2fwFZK8FyLZzrMvPlEUM,3681
28
+ proj_flow/ext/store.py,sha256=zc9yh9M042V5OSLUZjWe9KazhdZ35h1JJsWvKughM0Y,3385
29
29
  proj_flow/ext/cplusplus/__init__.py,sha256=dAmLMyGVQq586jJM_jiAuo5Ecw9U8agpvSRbzzPgh3g,245
30
- proj_flow/ext/cplusplus/cmake/__init__.py,sha256=QRD9qEzIhOuzwyjOFdOWUFiwwGTctss5y6Eam3RzeVc,346
30
+ proj_flow/ext/cplusplus/cmake/__init__.py,sha256=XQ8l1WsEZAgcNX_A0KgY535VhF6s7glS0TW2L0EmdVc,346
31
31
  proj_flow/ext/cplusplus/cmake/parser.py,sha256=ZqQRZqS_VU5VtC8uwax-dknh7sfuLEvtazG8ChSqHDQ,3814
32
+ proj_flow/ext/cplusplus/cmake/project.py,sha256=Cp-5HwEsrQW4RjDThjMBQmaVJiRHo9QvYbw7IvjHKNQ,929
32
33
  proj_flow/ext/cplusplus/cmake/steps.py,sha256=IBxATzcJfDI5XibaKgXDnKDKPIaJM1fAJKfjOBpufzo,4119
33
- proj_flow/ext/cplusplus/cmake/version.py,sha256=E0PAUdO9Wg02pxtU4LWYTNoTB-9Oer3Y9zr1lR2zvgw,962
34
34
  proj_flow/ext/cplusplus/conan/__init__.py,sha256=Fv839SWsKPWMZs9ox9T-bofZ4xDJXOI5UfWKQkm0Vtg,1924
35
35
  proj_flow/ext/cplusplus/conan/_conan.py,sha256=9xnji-f8uN7huXLqavVBUDC33CgnjBIyZX6wVcGm2RA,3352
36
36
  proj_flow/ext/github/__init__.py,sha256=Mgx19YS6SYBXYB66_pOgIgwuB2WKRxqp5UGutq0B9Xk,282
37
- proj_flow/ext/github/cli.py,sha256=zQS2TB7fDeY0VwR1bOw065Trz2NPe07JKcFQaJbwKGg,3934
37
+ proj_flow/ext/github/cli.py,sha256=hTJ9UhZx4oZS-OWhG716emJjZpLqRIovpmHp5CcM4MU,6011
38
38
  proj_flow/ext/github/hosting.py,sha256=3iW8QjeJk7MyqKNbv92nB-5a_Yn_B5_eEIlw_cdgUT0,519
39
+ proj_flow/ext/github/publishing.py,sha256=5dUNFq47X_g9vo25R3lQRkSjV2IiAm2zkIhNe8gLDKs,1921
39
40
  proj_flow/ext/github/switches.py,sha256=g7O2hvrg4mHm3WSHYsRBhEDU0bIkEJgp4Qclhqxk0uI,421
40
41
  proj_flow/ext/python/__init__.py,sha256=GbEKEJJZ3PJ4sRHEykAWjGIR6yyyrYdlUFulldvsAGI,252
41
- proj_flow/ext/python/rtdocs.py,sha256=idm6DTUnbA18L-EpxQiFVXCz9x8AIRSb52ZPqIXzrf8,6354
42
+ proj_flow/ext/python/rtdocs.py,sha256=mICD6EAFgAkWG4wcQZG2_7Ito4lxo7Vl_GdM6-4K9hY,6249
42
43
  proj_flow/ext/python/steps.py,sha256=pDHGAe_CDzzdRFAzM1AIBvkbc14KB3SNUunusKZAaaY,1815
43
44
  proj_flow/ext/python/version.py,sha256=pnyuKATyZwBh1p0gf9KmqbRSZx8hJ5285CiFK_tHEaY,3159
44
- proj_flow/ext/sign/__init__.py,sha256=yvXpqLdvBwkB0GDBl4yWw--iZ2tFxhx-97EP9OAzx2g,4345
45
- proj_flow/ext/sign/api.py,sha256=dlnXYYoBDYXx_WWGBQ8ThKmEMYmw2kt6NNZA8j-MXuM,2288
46
- proj_flow/ext/sign/win32.py,sha256=fV8_Z42KaeDBEf3_5qCzlALb0dy1zN0Pqy8Hr-ZMAQ4,4874
45
+ proj_flow/ext/sign/__init__.py,sha256=b9AN1_BalPtVy7YLBjvGhLamTujHEKcZP7npgDDDuSI,4296
46
+ proj_flow/ext/sign/api.py,sha256=l5SO5RHiHTwxg0aexkGOfApRdojWDcIBY_cfbKSKsC0,2286
47
+ proj_flow/ext/sign/win32.py,sha256=yMAmO-DdIWZdOi_NxycRym8XM9WIsrWKtFANdIwthJ4,4968
47
48
  proj_flow/flow/__init__.py,sha256=5Zo97zJsR7HMbl64jeMB9PbUuxCxpOlNuLmo3apWSVU,277
48
49
  proj_flow/flow/configs.py,sha256=l-pIotrXFm4oMqqpuvKP-RdtlTmDDnK_izhTJfsWbhk,5260
49
50
  proj_flow/flow/layer.py,sha256=6mvbhiOy9KcMP69Q9zew_7jGhf5Wqr7v-veS3YPGnmc,5720
50
51
  proj_flow/flow/steps.py,sha256=PN_C_B6vNvqOsjpDpa5ESvH30Sc6RM1fSSqWqXgqg-4,2804
51
52
  proj_flow/log/__init__.py,sha256=02EIgasE-K7mmbbNiIdX0IebWQMp2Co_D6H4ZBhJgcs,365
52
- proj_flow/log/commit.py,sha256=MQhx5ZIeVQ3_8o4w2giLNTVn6Ig-cUGxTDQhlJoN6Ig,13060
53
+ proj_flow/log/commit.py,sha256=ODPrIB3c72qGfqw5PNtLYI0PdKt5QnNlLuDq3u8B5HE,14070
53
54
  proj_flow/log/error.py,sha256=65Nvhfs_d1xSY4EB-ISdWgjotvg-on3iKjhAWHpsBYM,841
54
55
  proj_flow/log/fmt.py,sha256=o14aO3iEt5_KKp9SqcfkscqbMKuTI83NBoSXHcrb7Kg,330
55
56
  proj_flow/log/format.py,sha256=gp1kUoW0nYj5e7Ysu1c29Fh2ssfE1KBSDIYeUbhzN9g,333
56
57
  proj_flow/log/msg.py,sha256=zARmRZHFV3yG-fBnx00wal4Y0O5aGnL-6XcGwNBNKA4,6758
57
- proj_flow/log/release.py,sha256=-wZNv0VVLuTC_ynNaaDPQ1CcEw-5c-l6vzFOxvLVQnM,4160
58
+ proj_flow/log/release.py,sha256=eKT1on5if_QZ57zD6gPlA-uf2t_7ct-RskLZAabTe4M,4206
58
59
  proj_flow/log/hosting/__init__.py,sha256=9Teyw8jJcxeWH2MegqYEgW0n5OmSAWC7FFJj2u_UcrM,278
59
- proj_flow/log/hosting/github.py,sha256=yLjJ9gSuOnnC87phPFNc4N2yWno0iIi7BruhSrYOjEI,7151
60
+ proj_flow/log/hosting/github.py,sha256=O2BdB50vzVSKKIu3qNEYBiBdEUIPqj6C2xVvGAKjTZ4,9123
60
61
  proj_flow/log/rich_text/__init__.py,sha256=D3Y2jy9xlGgnQZdNC_ekoLzQtwkF_NTgLqDTWPvSRUk,279
61
62
  proj_flow/log/rich_text/api.py,sha256=PCSAGwkmDUMoVlpN7BDsgIA1AiMZEC0H6TUZXpr_Mg8,3571
62
63
  proj_flow/log/rich_text/markdown.py,sha256=jBnNxxhBHzyIZ3Y4HXDfqpl7zlRbbKbKdwdnZwkmNAI,1623
@@ -82,12 +83,12 @@ proj_flow/template/layers/conan.json,sha256=mAhDrxCtDjI_7Rbtr2hlNW5_jZkLdWLiwgfu
82
83
  proj_flow/template/layers/github_actions.json,sha256=ueBF6fvIbOSQov7tkYUZwo2c8Q9XNp9KhfjaFsZLk-U,38
83
84
  proj_flow/template/layers/github_social.json,sha256=bGWW5rfd7aWHvEwA5LlJ1Q1Ei6uMD-MvaueA978MFSs,37
84
85
  proj_flow/template/layers/base/.clang-format,sha256=Z71KG-BE5nOPsh9je5XrdN1w3FNZ5V772FuTAhqkEfQ,799
85
- proj_flow/template/layers/base/.gitignore,sha256=PXhgmN4aJy-Q8va_B9-QGBPRGSzfJ_BpUINK3WkOfxU,325
86
+ proj_flow/template/layers/base/.gitignore,sha256=2Cu5mL_7pSbC6gDHV9sZIJpzvEBuhHQznoUnmOOtOsc,333
86
87
  proj_flow/template/layers/base/README.md.mustache,sha256=toKfa5VQ23k8VKnsyogRojZZLfPakH8RHBzp0qpC-eY,43
87
88
  proj_flow/template/layers/base/flow,sha256=aPBLCgYNAZFl3FcP3HPwPnUxuRGxeV1Dp_6qFxDoGvk,196
88
89
  proj_flow/template/layers/base/flow.cmd,sha256=LQplRzLw4n86xvMXxOd1SzH2xJAN71Ou1uWsST8D6Ik,239
89
90
  proj_flow/template/layers/base/.flow/config.yml,sha256=7dc7u0FVBWP0nwOCzWNHEGeOM_APR467vyMuT_Mjfto,773
90
- proj_flow/template/layers/base/.flow/flow.py.mustache,sha256=UpCW3-DVa3d_4XmVkmzQu_L6vfiDr7RGUJiyFz5ykRM,4679
91
+ proj_flow/template/layers/base/.flow/flow.py.mustache,sha256=HxjPhBsVFZ5HYvozG2qjdjC6wmCXK3OgVgoLWqEFSJo,4972
91
92
  proj_flow/template/layers/base/.flow/matrix.yml,sha256=XgDC-F8YfeZEILBwHPkGNeRCxgaDK79f-JoWfHlVrgc,1257
92
93
  proj_flow/template/layers/base/.flow/official.yml,sha256=2nQgxoQJ5IAnPQVn50LrVD0B2p9KpLYSVz1b7IFu0Zc,71
93
94
  proj_flow/template/layers/cmake/CMakeGraphVizOptions.cmake,sha256=lAuNOIFDOAPnU_Zfz0ZCxephIp2bqteVlMSsuVg0BTM,368
@@ -100,7 +101,7 @@ proj_flow/template/layers/cmake/.flow/extensions/wall/icons/magick.py.mustache,s
100
101
  proj_flow/template/layers/cmake/.flow/packages/base.cmake.mustache,sha256=SLrqL0NJDUk6LcY_AiAgpVg5uq8mtrSSapT0bCdLdPE,1715
101
102
  proj_flow/template/layers/cmake/.flow/packages/config.cmake,sha256=-tVXUIFloAYcYgExJDvma-tx5xKS2iDyFLZlPJsMKQU,362
102
103
  proj_flow/template/layers/cmake/.flow/packages/cpack.cmake,sha256=Lh3ljXbvIHI3hqyMClC0BGUosBEFMBoE4z_hV77y69c,99
103
- proj_flow/template/layers/cmake/.flow/packages/wix.cmake.mustache,sha256=WtOFzdIZT01Tiid4Dh4Oa767wZMhs5j1G2z7H4-iUwg,870
104
+ proj_flow/template/layers/cmake/.flow/packages/wix.cmake.mustache,sha256=xMlQfrU29pQw570AvEI5Zhis0fLpA4yODrrOL2Ugyws,935
104
105
  proj_flow/template/layers/cmake/.flow/packages/wix/cpack.cmake.mustache,sha256=64RK_icmQeJbz_mbo2BcQke262dv3x8Q0SMXaYaYeGQ,43
105
106
  proj_flow/template/layers/cmake/.flow/packages/wix/patches.in.wix,sha256=5KCB9vydLuIw594P3QTYEfBhctHUBT_8wXQjM_njbtg,601
106
107
  proj_flow/template/layers/cmake/data/assets/appicon.ico,sha256=PlHHXa-by3FscaO_uXKO6HQOhBP7ZNaDO4BFpVOhlDE,29009
@@ -119,7 +120,7 @@ proj_flow/template/layers/conan/.flow/cmake/output_dirs_setup.cmake,sha256=-GSDi
119
120
  proj_flow/template/layers/github_actions/CPPLINT.cfg,sha256=JX88FBnJ3pECU0QS-8CjOfvWo_my6lSgX8kc1oi-mmY,385
120
121
  proj_flow/template/layers/github_actions/.github/linters/.isort.cfg,sha256=9gpUkeq6rXGxXeeokeGkTDjOBvDpdlcup8ipxCpJQ9s,79
121
122
  proj_flow/template/layers/github_actions/.github/linters/.mypy.ini,sha256=13DnGOzVFyXtkXH-48LlblCLfiXc2Xbzk78pllCKbSo,37
122
- proj_flow/template/layers/github_actions/.github/workflows/build.yml,sha256=K1a_bk3ybA2NfVazDQNvInVHHSD4lG-zxaRSuDjw994,8166
123
+ proj_flow/template/layers/github_actions/.github/workflows/build.yml,sha256=bgzdp18fIWzIQOfFRE9Q9T_JlA04XW2d9mk9mz8UgSw,8119
123
124
  proj_flow/template/layers/github_actions/.github/workflows/linter.yml,sha256=Ffg-ptQ9kjw4X0UyQax9-3AMIP2H62ikDkbgPi0ypeM,1545
124
125
  proj_flow/template/layers/github_social/CODE_OF_CONDUCT.md.mustache,sha256=XzXW9p1pX7AA5DcWW7cz-Onrxa0J32WclgCYWeYLT3Y,3347
125
126
  proj_flow/template/layers/github_social/CONTRIBUTING.md,sha256=nw1FZgDtKXubnRqvBaQgSmUqpsvDy_433VGmy5W-BH4,239
@@ -130,8 +131,8 @@ proj_flow/template/licenses/MIT.mustache,sha256=NncPoQaNsuy-WmRmboik3fyhJJ8m5pc2
130
131
  proj_flow/template/licenses/Unlicense.mustache,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
131
132
  proj_flow/template/licenses/WTFPL.mustache,sha256=lvF4V_PrKKfZPa2TC8CZo8tlqaKvs3Bpv9G6XsWWQ4k,483
132
133
  proj_flow/template/licenses/Zlib.mustache,sha256=uIj-mhSjes2HJ3rRapyy2ALflKRz4xQgS4mVM9827C0,868
133
- proj_flow-0.11.2.dist-info/METADATA,sha256=DiH0uZ6r9quuUyflFOU-IuZSgOVmdtSCLxY4hGZUjt4,2868
134
- proj_flow-0.11.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
135
- proj_flow-0.11.2.dist-info/entry_points.txt,sha256=d_OmGKZzpY7FCWz0sZ4wnBAPZC75oMEzTgJZWtpDELo,49
136
- proj_flow-0.11.2.dist-info/licenses/LICENSE,sha256=vpOQJ5QlrTedF3coEWvA4wJzVJH304f66ZitR7Od4iU,1068
137
- proj_flow-0.11.2.dist-info/RECORD,,
134
+ proj_flow-0.12.0.dist-info/METADATA,sha256=zhb6Ved7RWYcbf7vLuNfj13PB8h58AdNb-0pbX-n3rU,2868
135
+ proj_flow-0.12.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
136
+ proj_flow-0.12.0.dist-info/entry_points.txt,sha256=d_OmGKZzpY7FCWz0sZ4wnBAPZC75oMEzTgJZWtpDELo,49
137
+ proj_flow-0.12.0.dist-info/licenses/LICENSE,sha256=vpOQJ5QlrTedF3coEWvA4wJzVJH304f66ZitR7Od4iU,1068
138
+ proj_flow-0.12.0.dist-info/RECORD,,