proj-flow 0.9.2__py3-none-any.whl → 0.9.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- proj_flow/__init__.py +1 -1
- proj_flow/api/env.py +7 -2
- proj_flow/api/makefile.py +1 -1
- proj_flow/base/plugins.py +1 -36
- proj_flow/base/registry.py +2 -1
- proj_flow/cli/__init__.py +1 -3
- proj_flow/cli/argument.py +1 -1
- proj_flow/ext/cplusplus/__init__.py +10 -0
- proj_flow/ext/cplusplus/cmake/__init__.py +12 -0
- proj_flow/{plugins → ext/cplusplus}/cmake/context.py +2 -2
- proj_flow/{plugins → ext/cplusplus}/cmake/parser.py +6 -28
- proj_flow/ext/cplusplus/cmake/steps.py +142 -0
- proj_flow/ext/cplusplus/cmake/version.py +35 -0
- proj_flow/{plugins → ext/cplusplus}/conan/__init__.py +2 -1
- proj_flow/{plugins → ext/cplusplus}/conan/_conan.py +7 -2
- proj_flow/ext/github/__init__.py +2 -2
- proj_flow/ext/github/cli.py +11 -2
- proj_flow/{plugins/github.py → ext/github/switches.py} +1 -1
- proj_flow/ext/{markdown_changelist.py → markdown_changelog.py} +2 -1
- proj_flow/ext/python/rtdocs.py +1 -1
- proj_flow/ext/python/version.py +1 -2
- proj_flow/ext/{re_structured_changelist.py → re_structured_changelog.py} +3 -1
- proj_flow/{plugins → ext}/sign/__init__.py +64 -44
- proj_flow/ext/sign/api.py +83 -0
- proj_flow/ext/sign/win32.py +152 -0
- proj_flow/{plugins/store/store_packages.py → ext/store.py} +51 -9
- proj_flow/log/release.py +12 -0
- proj_flow/log/rich_text/markdown.py +1 -1
- proj_flow/log/rich_text/re_structured_text.py +1 -1
- proj_flow/minimal/__init__.py +2 -2
- proj_flow/{plugins → minimal}/base.py +2 -2
- proj_flow/{plugins/commands → minimal}/init.py +1 -1
- {proj_flow-0.9.2.dist-info → proj_flow-0.9.4.dist-info}/METADATA +2 -1
- {proj_flow-0.9.2.dist-info → proj_flow-0.9.4.dist-info}/RECORD +38 -47
- proj_flow/plugins/__init__.py +0 -8
- proj_flow/plugins/cmake/__init__.py +0 -11
- proj_flow/plugins/cmake/build.py +0 -29
- proj_flow/plugins/cmake/config.py +0 -59
- proj_flow/plugins/cmake/pack.py +0 -37
- proj_flow/plugins/cmake/test.py +0 -29
- proj_flow/plugins/commands/__init__.py +0 -12
- proj_flow/plugins/commands/ci/__init__.py +0 -17
- proj_flow/plugins/commands/ci/changelog.py +0 -47
- proj_flow/plugins/commands/ci/matrix.py +0 -46
- proj_flow/plugins/commands/ci/release.py +0 -116
- proj_flow/plugins/sign/win32.py +0 -191
- proj_flow/plugins/store/__init__.py +0 -11
- proj_flow/plugins/store/store_both.py +0 -22
- proj_flow/plugins/store/store_tests.py +0 -21
- /proj_flow/{plugins → ext/cplusplus}/cmake/__version__.py +0 -0
- {proj_flow-0.9.2.dist-info → proj_flow-0.9.4.dist-info}/WHEEL +0 -0
- {proj_flow-0.9.2.dist-info → proj_flow-0.9.4.dist-info}/entry_points.txt +0 -0
- {proj_flow-0.9.2.dist-info → proj_flow-0.9.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# This code is licensed under MIT license (see LICENSE for details)
|
|
3
3
|
|
|
4
4
|
"""
|
|
5
|
-
The **proj_flow.
|
|
5
|
+
The **proj_flow.ext.sign** provides the ``"Sign"`` and ``"SignPackages"``
|
|
6
6
|
steps.
|
|
7
7
|
"""
|
|
8
8
|
|
|
@@ -14,23 +14,7 @@ from typing import List, cast
|
|
|
14
14
|
|
|
15
15
|
from proj_flow.api import env, init, step
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
from . import win32
|
|
19
|
-
|
|
20
|
-
else:
|
|
21
|
-
|
|
22
|
-
class win32:
|
|
23
|
-
@staticmethod
|
|
24
|
-
def is_active(*args):
|
|
25
|
-
return False
|
|
26
|
-
|
|
27
|
-
@staticmethod
|
|
28
|
-
def sign(*args):
|
|
29
|
-
return 0
|
|
30
|
-
|
|
31
|
-
@staticmethod
|
|
32
|
-
def is_pe_exec(arg):
|
|
33
|
-
return False
|
|
17
|
+
from . import api, win32
|
|
34
18
|
|
|
35
19
|
|
|
36
20
|
def should_exclude(filename: str, exclude: List[str], config_os: str):
|
|
@@ -44,34 +28,69 @@ def should_exclude(filename: str, exclude: List[str], config_os: str):
|
|
|
44
28
|
|
|
45
29
|
|
|
46
30
|
class SignBase(step.Step):
|
|
31
|
+
_name: str
|
|
32
|
+
_runs_after: List[str] = []
|
|
33
|
+
_runs_before: List[str] = []
|
|
34
|
+
|
|
35
|
+
_active_tools: List[api.SigningTool] = []
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def name(self):
|
|
39
|
+
return self._name
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def runs_after(self):
|
|
43
|
+
return self._runs_after
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def runs_before(self):
|
|
47
|
+
return self._runs_before
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self, name: str, runs_after: List[str] = [], runs_before: List[str] = []
|
|
51
|
+
):
|
|
52
|
+
super().__init__()
|
|
53
|
+
self._name = name
|
|
54
|
+
self._runs_after = runs_after
|
|
55
|
+
self._runs_before = runs_before
|
|
56
|
+
|
|
47
57
|
def is_active(self, config: env.Config, rt: env.Runtime) -> int:
|
|
48
|
-
|
|
58
|
+
self._active_tools = [
|
|
59
|
+
tool for tool in api.signing_tool.get() if tool.is_active(config, rt)
|
|
60
|
+
]
|
|
61
|
+
return len(self._active_tools) > 0
|
|
49
62
|
|
|
50
63
|
@abstractmethod
|
|
51
|
-
def get_files(
|
|
64
|
+
def get_files(
|
|
65
|
+
self, tool: api.SigningTool, config: env.Config, rt: env.Runtime
|
|
66
|
+
) -> List[str]: ...
|
|
52
67
|
|
|
53
68
|
def run(self, config: env.Config, rt: env.Runtime) -> int:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
69
|
+
for tool in self._active_tools:
|
|
70
|
+
files = [
|
|
71
|
+
file.replace(os.sep, "/") for file in self.get_files(tool, config, rt)
|
|
72
|
+
]
|
|
57
73
|
|
|
58
|
-
|
|
74
|
+
if len(files) == 0:
|
|
75
|
+
continue
|
|
59
76
|
|
|
60
|
-
|
|
61
|
-
|
|
77
|
+
result = tool.sign(config, rt, files)
|
|
78
|
+
if result:
|
|
79
|
+
return result
|
|
62
80
|
|
|
63
|
-
return
|
|
81
|
+
return 0
|
|
64
82
|
|
|
65
83
|
|
|
66
84
|
@step.register
|
|
67
85
|
class SignFiles(SignBase):
|
|
68
86
|
"""*(Windows)* Signs executable files in build directory"""
|
|
69
87
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
runs_before = ["Pack"]
|
|
88
|
+
def __init__(self):
|
|
89
|
+
super().__init__(name="Sign", runs_after=["Build"], runs_before=["Pack"])
|
|
73
90
|
|
|
74
|
-
def get_files(
|
|
91
|
+
def get_files(
|
|
92
|
+
self, tool: api.SigningTool, config: env.Config, rt: env.Runtime
|
|
93
|
+
) -> List[str]:
|
|
75
94
|
cfg = cast(dict, rt._cfg.get("sign", {}))
|
|
76
95
|
roots = cfg.get("directories", ["bin", "lib", "libexec", "share"])
|
|
77
96
|
exclude = cfg.get("exclude", ["*-test"])
|
|
@@ -85,10 +104,8 @@ class SignFiles(SignBase):
|
|
|
85
104
|
continue
|
|
86
105
|
|
|
87
106
|
full_path = os.path.join(curr_dir, filename)
|
|
88
|
-
if
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
result.append(full_path)
|
|
107
|
+
if tool.is_executable(full_path, as_package=False):
|
|
108
|
+
result.append(full_path)
|
|
92
109
|
return result
|
|
93
110
|
|
|
94
111
|
|
|
@@ -96,26 +113,29 @@ class SignFiles(SignBase):
|
|
|
96
113
|
class SignMsi(SignBase):
|
|
97
114
|
"""*(Windows)* Signs MSI installers in build directory"""
|
|
98
115
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
116
|
+
def __init__(self):
|
|
117
|
+
super().__init__(
|
|
118
|
+
name="SignPackages",
|
|
119
|
+
runs_after=["Pack"],
|
|
120
|
+
runs_before=["StorePackages", "Store"],
|
|
121
|
+
)
|
|
102
122
|
|
|
103
123
|
def is_active(self, config: env.Config, rt: env.Runtime) -> int:
|
|
104
124
|
return super().is_active(config, rt) and "WIX" in config.items.get(
|
|
105
125
|
"cpack_generator", []
|
|
106
126
|
)
|
|
107
127
|
|
|
108
|
-
def get_files(
|
|
128
|
+
def get_files(
|
|
129
|
+
self, tool: api.SigningTool, config: env.Config, rt: env.Runtime
|
|
130
|
+
) -> List[str]:
|
|
109
131
|
result: List[str] = []
|
|
110
132
|
pkg_dir = os.path.join(config.build_dir, "packages")
|
|
111
133
|
for curr_dir, dirnames, filenames in os.walk(pkg_dir):
|
|
112
134
|
dirnames[:] = []
|
|
113
135
|
for filename in filenames:
|
|
114
|
-
|
|
115
|
-
if
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
result.append(os.path.join(curr_dir, filename))
|
|
136
|
+
full_path = os.path.join(curr_dir, filename)
|
|
137
|
+
if tool.is_executable(full_path, as_package=False):
|
|
138
|
+
result.append(full_path)
|
|
119
139
|
|
|
120
140
|
return result
|
|
121
141
|
|
|
@@ -0,0 +1,83 @@
|
|
|
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.sign.api** defines an extension point for per-platform
|
|
6
|
+
sign tools.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import base64
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from typing import List, NamedTuple, Optional
|
|
14
|
+
|
|
15
|
+
from proj_flow import base
|
|
16
|
+
from proj_flow.api import env
|
|
17
|
+
|
|
18
|
+
ENV_KEY = "SIGN_TOKEN"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Key(NamedTuple):
|
|
22
|
+
token: str
|
|
23
|
+
secret: bytes
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _get_key_from_contents(key: str, rt: env.Runtime):
|
|
27
|
+
try:
|
|
28
|
+
obj = json.loads(key)
|
|
29
|
+
except json.decoder.JSONDecodeError:
|
|
30
|
+
rt.message("sign: the signature is not a valid JSON document")
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
if not isinstance(obj, dict):
|
|
34
|
+
rt.message("sign: the signature is missing required fields")
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
token = obj.get("token")
|
|
38
|
+
secret = obj.get("secret")
|
|
39
|
+
if not isinstance(token, str) or not isinstance(secret, str):
|
|
40
|
+
rt.message("sign: the signature is missing required fields")
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
return Key(
|
|
44
|
+
base64.b64decode(token).decode("UTF-8"),
|
|
45
|
+
base64.b64decode(secret),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_key(rt: env.Runtime) -> Optional[Key]:
|
|
50
|
+
rt.message(f"sign: trying ${ENV_KEY}")
|
|
51
|
+
env = os.environ.get(ENV_KEY)
|
|
52
|
+
if env:
|
|
53
|
+
key = _get_key_from_contents(env, rt)
|
|
54
|
+
if key is not None:
|
|
55
|
+
return key
|
|
56
|
+
local_signature = os.path.join(".", "signature.key")
|
|
57
|
+
home_signature = os.path.join(os.path.expanduser("~"), "signature.key")
|
|
58
|
+
for filename in [local_signature, home_signature]:
|
|
59
|
+
rt.message(f"sign: trying {filename}")
|
|
60
|
+
if os.path.isfile(filename):
|
|
61
|
+
with open(filename, encoding="UTF-8") as file:
|
|
62
|
+
result = file.read().strip()
|
|
63
|
+
key = _get_key_from_contents(result, rt)
|
|
64
|
+
if key is not None:
|
|
65
|
+
return key
|
|
66
|
+
|
|
67
|
+
rt.message("sign: no key set up")
|
|
68
|
+
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class SigningTool(ABC):
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def is_active(self, config: env.Config, rt: env.Runtime): ...
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def sign(self, config: env.Config, rt: env.Runtime, files: List[str]): ...
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def is_executable(self, filename: str, as_package: bool): ...
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
signing_tool = base.registry.Registry[SigningTool]("SigningTool")
|
|
@@ -0,0 +1,152 @@
|
|
|
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.sign.win32** provides code signing with SignTool
|
|
6
|
+
from Windows SDKs.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import base64
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import platform
|
|
13
|
+
import struct
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
from typing import Iterable, List, Optional, Tuple
|
|
17
|
+
|
|
18
|
+
from proj_flow.api.env import Config, Msg, Runtime
|
|
19
|
+
|
|
20
|
+
from .api import ENV_KEY, SigningTool, get_key, signing_tool
|
|
21
|
+
|
|
22
|
+
if sys.platform == "win32":
|
|
23
|
+
import winreg
|
|
24
|
+
|
|
25
|
+
@signing_tool.add
|
|
26
|
+
class Win32SigningTool(SigningTool):
|
|
27
|
+
def is_active(self, config: Config, rt: Runtime):
|
|
28
|
+
return _is_active(config.os, rt)
|
|
29
|
+
|
|
30
|
+
def sign(self, config: Config, rt: Runtime, files: List[str]):
|
|
31
|
+
rt.print("signtool", *(os.path.basename(file) for file in files))
|
|
32
|
+
return _sign(files, rt)
|
|
33
|
+
|
|
34
|
+
def is_signable(self, filename: str, as_package: bool):
|
|
35
|
+
if as_package:
|
|
36
|
+
_, ext = os.path.splitext(filename)
|
|
37
|
+
return ext.lower() == ".msi"
|
|
38
|
+
return _is_pe_exec(filename)
|
|
39
|
+
|
|
40
|
+
Version = Tuple[int, int, int]
|
|
41
|
+
|
|
42
|
+
machine = {"ARM64": "arm64", "AMD64": "x64", "X86": "x86"}.get(
|
|
43
|
+
platform.machine(), "x86"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def _find_sign_tool(rt: Runtime) -> Optional[str]:
|
|
47
|
+
with winreg.OpenKeyEx( # type: ignore
|
|
48
|
+
winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows Kits\Installed Roots" # type: ignore
|
|
49
|
+
) as kits:
|
|
50
|
+
try:
|
|
51
|
+
kits_root = winreg.QueryValueEx(kits, "KitsRoot10")[0] # type: ignore
|
|
52
|
+
except FileNotFoundError:
|
|
53
|
+
rt.message("sign/win32: No KitsRoot10 value")
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
versions: List[Tuple[Version, str]] = []
|
|
57
|
+
try:
|
|
58
|
+
index = 0
|
|
59
|
+
while True:
|
|
60
|
+
ver_str = winreg.EnumKey(kits, index) # type: ignore
|
|
61
|
+
ver = tuple(int(chunk) for chunk in ver_str.split("."))
|
|
62
|
+
index += 1
|
|
63
|
+
versions.append((ver, ver_str)) # type: ignore
|
|
64
|
+
except OSError:
|
|
65
|
+
pass
|
|
66
|
+
versions.sort()
|
|
67
|
+
versions.reverse()
|
|
68
|
+
rt.message(
|
|
69
|
+
"sign/win32: Regarding versions:",
|
|
70
|
+
", ".join(version[1] for version in versions),
|
|
71
|
+
)
|
|
72
|
+
for _, version in versions:
|
|
73
|
+
# C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe
|
|
74
|
+
sign_tool = os.path.join(kits_root, "bin", version, machine, "signtool.exe")
|
|
75
|
+
if os.path.isfile(sign_tool):
|
|
76
|
+
rt.message("sign/win32: using:", sign_tool)
|
|
77
|
+
return sign_tool
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
def _is_active(os_name: str, rt: Runtime):
|
|
81
|
+
if os_name != "windows":
|
|
82
|
+
return False
|
|
83
|
+
key = get_key(rt)
|
|
84
|
+
return (
|
|
85
|
+
key is not None
|
|
86
|
+
and key.token is not None
|
|
87
|
+
and key.secret is not None
|
|
88
|
+
and _find_sign_tool(rt) is not None
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
_IMAGE_DOS_HEADER = "HHHHHHHHHHHHHH8sHH20sI"
|
|
92
|
+
_IMAGE_NT_HEADERS_Signature = "H"
|
|
93
|
+
_IMAGE_DOS_HEADER_size = struct.calcsize(_IMAGE_DOS_HEADER)
|
|
94
|
+
_IMAGE_NT_HEADERS_Signature_size = struct.calcsize(_IMAGE_NT_HEADERS_Signature)
|
|
95
|
+
_MZ = 23117
|
|
96
|
+
_PE = 17744
|
|
97
|
+
|
|
98
|
+
def _is_pe_exec(path: str):
|
|
99
|
+
with open(path, "rb") as exe:
|
|
100
|
+
mz_header = exe.read(_IMAGE_DOS_HEADER_size)
|
|
101
|
+
dos_header = struct.unpack(_IMAGE_DOS_HEADER, mz_header)
|
|
102
|
+
if dos_header[0] != _MZ:
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
PE_offset = dos_header[-1]
|
|
106
|
+
if PE_offset < _IMAGE_DOS_HEADER_size:
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
if PE_offset > _IMAGE_DOS_HEADER_size:
|
|
110
|
+
exe.read(PE_offset - _IMAGE_DOS_HEADER_size)
|
|
111
|
+
|
|
112
|
+
pe_header = exe.read(_IMAGE_NT_HEADERS_Signature_size)
|
|
113
|
+
signature = struct.unpack(_IMAGE_NT_HEADERS_Signature, pe_header)[0]
|
|
114
|
+
return signature == _PE
|
|
115
|
+
|
|
116
|
+
def _sign(files: Iterable[str], rt: Runtime):
|
|
117
|
+
key = get_key(rt)
|
|
118
|
+
|
|
119
|
+
if key is None or key.token is None or key.secret is None:
|
|
120
|
+
rt.fatal("sign: the key is missing")
|
|
121
|
+
|
|
122
|
+
sign_tool = _find_sign_tool(rt)
|
|
123
|
+
if sign_tool is None:
|
|
124
|
+
rt.message("proj-flow: sign: signtool.exe not found", level=Msg.ALWAYS)
|
|
125
|
+
return 0
|
|
126
|
+
|
|
127
|
+
with open("temp.pfx", "wb") as pfx:
|
|
128
|
+
pfx.write(key.secret)
|
|
129
|
+
|
|
130
|
+
args = [
|
|
131
|
+
sign_tool,
|
|
132
|
+
"sign",
|
|
133
|
+
"/f",
|
|
134
|
+
"temp.pfx",
|
|
135
|
+
"/p",
|
|
136
|
+
key.token,
|
|
137
|
+
"/tr",
|
|
138
|
+
"http://timestamp.digicert.com",
|
|
139
|
+
"/fd",
|
|
140
|
+
"sha256",
|
|
141
|
+
"/td",
|
|
142
|
+
"sha256",
|
|
143
|
+
*files,
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
result = 1
|
|
147
|
+
try:
|
|
148
|
+
result = subprocess.run(args, shell=False).returncode
|
|
149
|
+
finally:
|
|
150
|
+
os.remove("temp.pfx")
|
|
151
|
+
|
|
152
|
+
return result
|
|
@@ -2,18 +2,17 @@
|
|
|
2
2
|
# This code is licensed under MIT license (see LICENSE for details)
|
|
3
3
|
|
|
4
4
|
"""
|
|
5
|
-
The **proj_flow.
|
|
5
|
+
The **proj_flow.ext.store** provides ``"Store"``, ``"StoreTests"`` and
|
|
6
|
+
``"StorePackages"`` steps.
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
import os
|
|
9
10
|
import shutil
|
|
10
11
|
from typing import List, cast
|
|
11
12
|
|
|
12
|
-
from proj_flow.api import env, step
|
|
13
|
+
from proj_flow.api import env, release, step
|
|
13
14
|
from proj_flow.base.uname import uname
|
|
14
15
|
|
|
15
|
-
from ..cmake.parser import get_project
|
|
16
|
-
|
|
17
16
|
_system, _version, _arch = uname()
|
|
18
17
|
_version = "" if _version is None else f"-{_version}"
|
|
19
18
|
_project_pkg = None
|
|
@@ -26,12 +25,24 @@ def _package_name(config: env.Config, pkg: str, group: str):
|
|
|
26
25
|
return f"{pkg}-{_system}{_version}-{_arch}{debug}{suffix}"
|
|
27
26
|
|
|
28
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
|
+
|
|
29
35
|
@step.register
|
|
30
|
-
class StorePackages:
|
|
36
|
+
class StorePackages(step.Step):
|
|
31
37
|
"""Stores archives and installers build for ``preset`` config value."""
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
@property
|
|
40
|
+
def name(self):
|
|
41
|
+
return "StorePackages"
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def runs_after(self):
|
|
45
|
+
return ["Pack"]
|
|
35
46
|
|
|
36
47
|
def run(self, config: env.Config, rt: env.Runtime) -> int:
|
|
37
48
|
if not rt.dry_run:
|
|
@@ -41,10 +52,10 @@ class StorePackages:
|
|
|
41
52
|
|
|
42
53
|
global _project_pkg
|
|
43
54
|
if _project_pkg is None:
|
|
44
|
-
project =
|
|
55
|
+
_, project = release.project_suites.find(_get_project(rt))
|
|
45
56
|
if project is None:
|
|
46
57
|
rt.fatal(f"Cannot get project information from {rt.root}")
|
|
47
|
-
_project_pkg = project.
|
|
58
|
+
_project_pkg = project.archive_name
|
|
48
59
|
|
|
49
60
|
main_group = cast(str, rt._cfg.get("package", {}).get("main-group"))
|
|
50
61
|
if main_group is not None and not rt.dry_run:
|
|
@@ -77,3 +88,34 @@ class StorePackages:
|
|
|
77
88
|
"build/artifacts/packages",
|
|
78
89
|
f"^{_project_pkg}-.*$",
|
|
79
90
|
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@step.register
|
|
94
|
+
class StoreTests(step.Step):
|
|
95
|
+
"""Stores test results gathered during tests for ``preset`` config value."""
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def name(self):
|
|
99
|
+
return "StoreTests"
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def runs_after(self):
|
|
103
|
+
return ["Test"]
|
|
104
|
+
|
|
105
|
+
def run(self, config: env.Config, rt: env.Runtime) -> int:
|
|
106
|
+
return rt.cp(
|
|
107
|
+
f"build/{config.preset}/test-results", "build/artifacts/test-results"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@step.register
|
|
112
|
+
class StoreBoth(step.SerialStep):
|
|
113
|
+
"""Stores all artifacts created for ``preset`` config value."""
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def name(self):
|
|
117
|
+
return "Store"
|
|
118
|
+
|
|
119
|
+
def __init__(self):
|
|
120
|
+
super().__init__()
|
|
121
|
+
self.children = [StoreTests(), StorePackages()]
|
proj_flow/log/release.py
CHANGED
|
@@ -79,9 +79,19 @@ def add_release(
|
|
|
79
79
|
|
|
80
80
|
prev_tag = tags[-1] if len(tags) > 0 else None
|
|
81
81
|
|
|
82
|
+
rt.message("Tags:")
|
|
83
|
+
for tag in reversed(tags):
|
|
84
|
+
rt.message(" >", tag, "*" if tag == prev_tag else "")
|
|
85
|
+
|
|
82
86
|
setup = commit.LogSetup(hosting, prev_tag, None, take_all=take_all)
|
|
83
87
|
changelog, log_level = git.get_log(setup)
|
|
84
88
|
|
|
89
|
+
rt.message("Changelog:")
|
|
90
|
+
for grp, links in changelog.items():
|
|
91
|
+
rt.message(" >", grp)
|
|
92
|
+
for link in links:
|
|
93
|
+
rt.message(" ", str(link.summary))
|
|
94
|
+
|
|
85
95
|
project_version = f"{project.version}"
|
|
86
96
|
next_version = _bump_version(project_version, forced_level or log_level)
|
|
87
97
|
setup.curr_tag = f"v{next_version}"
|
|
@@ -123,3 +133,5 @@ def add_release(
|
|
|
123
133
|
draft_url = hosting.add_release(changelog, setup, git, draft).draft_url
|
|
124
134
|
if draft_url:
|
|
125
135
|
rt.message("Visit draft at", draft_url, level=env.Msg.ALWAYS)
|
|
136
|
+
|
|
137
|
+
return setup.curr_tag
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# This code is licensed under MIT license (see LICENSE for details)
|
|
3
3
|
|
|
4
4
|
"""
|
|
5
|
-
The **proj_flow.log.
|
|
5
|
+
The **proj_flow.log.rich_text.re_structured_text** provides details of making
|
|
6
6
|
reStructuredText changelogs.
|
|
7
7
|
"""
|
|
8
8
|
|
proj_flow/minimal/__init__.py
CHANGED
|
@@ -6,6 +6,6 @@ The **proj_flow.minimal** defines minimal extension package: ``bootstrap``
|
|
|
6
6
|
and ``run`` commands, with basic set of steps.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from . import bootstrap, list, run, system
|
|
9
|
+
from . import bootstrap, init, list, run, system
|
|
10
10
|
|
|
11
|
-
__all__ = ["bootstrap", "list", "run", "system"]
|
|
11
|
+
__all__ = ["bootstrap", "init", "list", "run", "system"]
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
# This code is licensed under MIT license (see LICENSE for details)
|
|
3
3
|
|
|
4
4
|
"""
|
|
5
|
-
The **proj_flow.
|
|
6
|
-
projects.
|
|
5
|
+
The **proj_flow.minimal.base** provides basic initialization setup for all
|
|
6
|
+
new projects.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from proj_flow import __version__, api
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proj-flow
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.4
|
|
4
4
|
Summary: C++ project maintenance, automated
|
|
5
|
+
Project-URL: Changelog, https://github.com/mzdun/proj-flow/blob/main/CHANGELOG.rst
|
|
5
6
|
Project-URL: Documentation, https://proj-flow.readthedocs.io/en/latest/
|
|
6
7
|
Project-URL: Homepage, https://pypi.org/project/proj-flow/
|
|
7
8
|
Project-URL: Source Code, https://github.com/mzdun/proj-flow
|