proj-flow 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.
Files changed (126) hide show
  1. proj_flow/__init__.py +4 -0
  2. proj_flow/__main__.py +10 -0
  3. proj_flow/api/__init__.py +10 -0
  4. proj_flow/api/arg.py +134 -0
  5. proj_flow/api/completers.py +93 -0
  6. proj_flow/api/ctx.py +238 -0
  7. proj_flow/api/env.py +416 -0
  8. proj_flow/api/init.py +26 -0
  9. proj_flow/api/makefile.py +140 -0
  10. proj_flow/api/step.py +173 -0
  11. proj_flow/base/__init__.py +11 -0
  12. proj_flow/base/cmd.py +50 -0
  13. proj_flow/base/inspect.py +133 -0
  14. proj_flow/base/matrix.py +240 -0
  15. proj_flow/base/plugins.py +44 -0
  16. proj_flow/base/uname.py +71 -0
  17. proj_flow/flow/__init__.py +11 -0
  18. proj_flow/flow/cli/__init__.py +66 -0
  19. proj_flow/flow/cli/cmds.py +385 -0
  20. proj_flow/flow/cli/finder.py +59 -0
  21. proj_flow/flow/configs.py +162 -0
  22. proj_flow/flow/dependency.py +153 -0
  23. proj_flow/flow/init.py +65 -0
  24. proj_flow/flow/interact.py +134 -0
  25. proj_flow/flow/layer.py +176 -0
  26. proj_flow/flow/steps.py +104 -0
  27. proj_flow/log/__init__.py +10 -0
  28. proj_flow/log/commit.py +463 -0
  29. proj_flow/log/fmt.py +12 -0
  30. proj_flow/log/format.py +13 -0
  31. proj_flow/log/hosting/__init__.py +11 -0
  32. proj_flow/log/hosting/github.py +248 -0
  33. proj_flow/log/msg.py +201 -0
  34. proj_flow/log/release.py +34 -0
  35. proj_flow/log/rich_text/__init__.py +22 -0
  36. proj_flow/log/rich_text/api.py +126 -0
  37. proj_flow/log/rich_text/markdown.py +61 -0
  38. proj_flow/log/rich_text/re_structured_text.py +68 -0
  39. proj_flow/plugins/__init__.py +8 -0
  40. proj_flow/plugins/base.py +30 -0
  41. proj_flow/plugins/cmake/__init__.py +11 -0
  42. proj_flow/plugins/cmake/__version__.py +5 -0
  43. proj_flow/plugins/cmake/build.py +29 -0
  44. proj_flow/plugins/cmake/config.py +59 -0
  45. proj_flow/plugins/cmake/context.py +112 -0
  46. proj_flow/plugins/cmake/pack.py +37 -0
  47. proj_flow/plugins/cmake/parser.py +166 -0
  48. proj_flow/plugins/cmake/test.py +29 -0
  49. proj_flow/plugins/commands/__init__.py +12 -0
  50. proj_flow/plugins/commands/bootstrap.py +21 -0
  51. proj_flow/plugins/commands/ci/__init__.py +17 -0
  52. proj_flow/plugins/commands/ci/changelog.py +47 -0
  53. proj_flow/plugins/commands/ci/matrix.py +46 -0
  54. proj_flow/plugins/commands/ci/release.py +116 -0
  55. proj_flow/plugins/commands/init.py +75 -0
  56. proj_flow/plugins/commands/list.py +167 -0
  57. proj_flow/plugins/commands/run.py +147 -0
  58. proj_flow/plugins/commands/system.py +60 -0
  59. proj_flow/plugins/conan/__init__.py +66 -0
  60. proj_flow/plugins/conan/_conan.py +146 -0
  61. proj_flow/plugins/github.py +13 -0
  62. proj_flow/plugins/sign/__init__.py +130 -0
  63. proj_flow/plugins/sign/win32.py +191 -0
  64. proj_flow/plugins/store/__init__.py +11 -0
  65. proj_flow/plugins/store/store_both.py +22 -0
  66. proj_flow/plugins/store/store_packages.py +79 -0
  67. proj_flow/plugins/store/store_tests.py +21 -0
  68. proj_flow/template/layers/base/.clang-format +27 -0
  69. proj_flow/template/layers/base/.flow/config.yml +38 -0
  70. proj_flow/template/layers/base/.flow/flow.py.mustache +172 -0
  71. proj_flow/template/layers/base/.flow/matrix.yml +63 -0
  72. proj_flow/template/layers/base/.flow/official.yml +4 -0
  73. proj_flow/template/layers/base/.gitignore +39 -0
  74. proj_flow/template/layers/base/README.md.mustache +2 -0
  75. proj_flow/template/layers/base/flow +7 -0
  76. proj_flow/template/layers/base/flow.cmd +9 -0
  77. proj_flow/template/layers/base.json +2 -0
  78. proj_flow/template/layers/cmake/.flow/cmake/common.cmake.mustache +27 -0
  79. proj_flow/template/layers/cmake/.flow/extensions/wall/__init__.py.mustache +2 -0
  80. proj_flow/template/layers/cmake/.flow/extensions/wall/icons/__init__.py.mustache +54 -0
  81. proj_flow/template/layers/cmake/.flow/extensions/wall/icons/magick.py.mustache +97 -0
  82. proj_flow/template/layers/cmake/.flow/packages/base.cmake.mustache +33 -0
  83. proj_flow/template/layers/cmake/.flow/packages/config.cmake +12 -0
  84. proj_flow/template/layers/cmake/.flow/packages/cpack.cmake +3 -0
  85. proj_flow/template/layers/cmake/.flow/packages/wix/cpack.cmake.mustache +1 -0
  86. proj_flow/template/layers/cmake/.flow/packages/wix/patches.in.wix +10 -0
  87. proj_flow/template/layers/cmake/.flow/packages/wix.cmake.mustache +13 -0
  88. proj_flow/template/layers/cmake/CMakeGraphVizOptions.cmake +8 -0
  89. proj_flow/template/layers/cmake/CMakeLists.txt.mustache +213 -0
  90. proj_flow/template/layers/cmake/CMakePresets.json +196 -0
  91. proj_flow/template/layers/cmake/data/assets/appicon.ico +0 -0
  92. proj_flow/template/layers/cmake/data/assets/appicon.png +0 -0
  93. proj_flow/template/layers/cmake/data/assets/wix_banner.bmp +0 -0
  94. proj_flow/template/layers/cmake/data/assets/wix_dialog.bmp +0 -0
  95. proj_flow/template/layers/cmake/data/icons/.gitignore +3 -0
  96. proj_flow/template/layers/cmake/data/icons/appicon-mask.svg +6 -0
  97. proj_flow/template/layers/cmake/data/icons/appicon.svg +27 -0
  98. proj_flow/template/layers/cmake/src/main.cc.mustache +38 -0
  99. proj_flow/template/layers/cmake/src/version.hpp.in.mustache +36 -0
  100. proj_flow/template/layers/cmake/tests/test.cc.mustache +17 -0
  101. proj_flow/template/layers/cmake.json +15 -0
  102. proj_flow/template/layers/conan/.flow/cmake/libcxx_toolchain.cmake +4 -0
  103. proj_flow/template/layers/conan/.flow/cmake/output_dirs_setup.cmake +19 -0
  104. proj_flow/template/layers/conan/conanfile.txt +9 -0
  105. proj_flow/template/layers/conan.json +3 -0
  106. proj_flow/template/layers/github_actions/.github/linters/.isort.cfg +4 -0
  107. proj_flow/template/layers/github_actions/.github/linters/.mypy.ini +2 -0
  108. proj_flow/template/layers/github_actions/.github/workflows/build.yml +241 -0
  109. proj_flow/template/layers/github_actions/.github/workflows/linter.yml +59 -0
  110. proj_flow/template/layers/github_actions/CPPLINT.cfg +16 -0
  111. proj_flow/template/layers/github_actions.json +3 -0
  112. proj_flow/template/layers/github_social/.github/ISSUE_TEMPLATE/bug_report.md.mustache +42 -0
  113. proj_flow/template/layers/github_social/.github/ISSUE_TEMPLATE/feature_request.md.mustache +28 -0
  114. proj_flow/template/layers/github_social/CODE_OF_CONDUCT.md.mustache +76 -0
  115. proj_flow/template/layers/github_social/CONTRIBUTING.md +10 -0
  116. proj_flow/template/layers/github_social.json +3 -0
  117. proj_flow/template/licenses/0BSD.mustache +12 -0
  118. proj_flow/template/licenses/MIT.mustache +21 -0
  119. proj_flow/template/licenses/Unlicense.mustache +24 -0
  120. proj_flow/template/licenses/WTFPL.mustache +13 -0
  121. proj_flow/template/licenses/Zlib.mustache +19 -0
  122. proj_flow-0.8.1.dist-info/METADATA +81 -0
  123. proj_flow-0.8.1.dist-info/RECORD +126 -0
  124. proj_flow-0.8.1.dist-info/WHEEL +4 -0
  125. proj_flow-0.8.1.dist-info/entry_points.txt +2 -0
  126. proj_flow-0.8.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,146 @@
1
+ # Copyright (c) 2025 Marcin Zdun
2
+ # This code is licensed under MIT license (see LICENSE for details)
3
+
4
+ """
5
+ The **proj_flow.plugins.conan._conan** adds support for both Conan v1 and v2.
6
+ """
7
+
8
+ import shutil
9
+ from abc import ABC, abstractmethod
10
+ from typing import Callable, List, cast
11
+
12
+ from proj_flow.api.env import Config, Runtime
13
+ from proj_flow.base import cmd
14
+ from proj_flow.flow.dependency import VER_REGEX
15
+
16
+
17
+ class conan(ABC):
18
+ version: int
19
+
20
+ def __init__(self, version: int = 1):
21
+ self.version = version
22
+
23
+ def settings(self, cfg: Config):
24
+ result: List[str] = []
25
+ for threshold, name in enumerate(
26
+ ["conan_settings", "conan2_settings"],
27
+ ):
28
+ if self.version <= threshold:
29
+ break
30
+ result = cast(List[str], cfg.items.get(name, result))
31
+ return result
32
+
33
+ @abstractmethod
34
+ def config(
35
+ self,
36
+ rt: Runtime,
37
+ conan_output_dir: str,
38
+ compiler_profile_name: str,
39
+ build_type_profile_name: str,
40
+ ) -> int: ...
41
+
42
+
43
+ class conan_1(conan):
44
+ def __init__(self):
45
+ super().__init__(1)
46
+
47
+ def config(
48
+ self,
49
+ rt: Runtime,
50
+ conan_output_dir: str,
51
+ compiler_profile_name: str,
52
+ build_type_profile_name: str,
53
+ ) -> int:
54
+ if rt.cmd(
55
+ "conan",
56
+ "profile",
57
+ "new",
58
+ "--detect",
59
+ "--force",
60
+ compiler_profile_name,
61
+ ):
62
+ return 1
63
+
64
+ return rt.cmd(
65
+ "conan",
66
+ "install",
67
+ "-if",
68
+ conan_output_dir,
69
+ "-of",
70
+ conan_output_dir,
71
+ "--build",
72
+ "missing",
73
+ "-pr:b",
74
+ build_type_profile_name,
75
+ "-pr:h",
76
+ build_type_profile_name,
77
+ ".",
78
+ )
79
+
80
+
81
+ class conan_2(conan):
82
+ def __init__(self):
83
+ super().__init__(2)
84
+
85
+ def config(
86
+ self,
87
+ rt: Runtime,
88
+ conan_output_dir: str,
89
+ compiler_profile_name: str,
90
+ build_type_profile_name: str,
91
+ ) -> int:
92
+ if rt.cmd(
93
+ "conan",
94
+ "profile",
95
+ "detect",
96
+ "--force",
97
+ "--name",
98
+ compiler_profile_name,
99
+ ):
100
+ return 1
101
+
102
+ return rt.cmd(
103
+ "conan",
104
+ "install",
105
+ "-of",
106
+ conan_output_dir,
107
+ "--build",
108
+ "missing",
109
+ "-pr:b",
110
+ build_type_profile_name,
111
+ "-pr:h",
112
+ build_type_profile_name,
113
+ ".",
114
+ )
115
+
116
+
117
+ def _conan_version():
118
+ found = shutil.which("conan")
119
+ if found is None:
120
+ return 1
121
+
122
+ proc = cmd.run(found, "--version", capture_output=True)
123
+ if proc.returncode != 0:
124
+ return 1
125
+ m = VER_REGEX.search(proc.stdout)
126
+ version = m.group(0) if m is not None else None
127
+ if version is None:
128
+ return 1
129
+ chunks = [int(v.strip()) for v in version.split(".")]
130
+ if len(chunks) < 1:
131
+ return 1
132
+ return chunks[0]
133
+
134
+
135
+ ctors: List[Callable[[], conan]] = [
136
+ conan_1,
137
+ conan_2,
138
+ ]
139
+
140
+
141
+ def conan_api() -> conan:
142
+ version = _conan_version()
143
+ index = version - 1
144
+ if index >= len(ctors):
145
+ return ctors[-1]()
146
+ return ctors[index]()
@@ -0,0 +1,13 @@
1
+ # Copyright (c) 2025 Marcin Zdun
2
+ # This code is licensed under MIT license (see LICENSE for details)
3
+ """
4
+ The **proj_flow.plugins.github** provides GitHub-related switches for new
5
+ projects.
6
+ """
7
+
8
+ from proj_flow.api import ctx
9
+
10
+ ctx.register_switch("with_github_actions", "Use Github Actions", True)
11
+ ctx.register_switch(
12
+ "with_github_social", "Use Github ISSUE_TEMPLATE, CONTRIBUTING.md, etc.", True
13
+ )
@@ -0,0 +1,130 @@
1
+ # Copyright (c) 2025 Marcin Zdun
2
+ # This code is licensed under MIT license (see LICENSE for details)
3
+
4
+ """
5
+ The **proj_flow.plugins.sign** provides the ``"Sign"`` and ``"SignPackages"``
6
+ steps.
7
+ """
8
+
9
+ import fnmatch
10
+ import os
11
+ import sys
12
+ from abc import abstractmethod
13
+ from typing import List, cast
14
+
15
+ from proj_flow.api import env, init, step
16
+
17
+ if sys.platform == "win32":
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
34
+
35
+
36
+ def should_exclude(filename: str, exclude: List[str], config_os: str):
37
+ basename = os.path.splitext(filename)[0] if config_os == "windows" else filename
38
+
39
+ for pattern in exclude:
40
+ if fnmatch.fnmatch(basename, pattern):
41
+ return True
42
+
43
+ return False
44
+
45
+
46
+ class SignBase(step.Step):
47
+ def is_active(self, config: env.Config, rt: env.Runtime) -> int:
48
+ return win32.is_active(config.os, rt)
49
+
50
+ @abstractmethod
51
+ def get_files(self, config: env.Config, rt: env.Runtime) -> List[str]: ...
52
+
53
+ def run(self, config: env.Config, rt: env.Runtime) -> int:
54
+ files = [file.replace(os.sep, "/") for file in self.get_files(config, rt)]
55
+ if len(files) == 0:
56
+ return 0
57
+
58
+ rt.print("signtool", *(os.path.basename(file) for file in files))
59
+
60
+ if rt.dry_run:
61
+ return 0
62
+
63
+ return win32.sign(files, rt)
64
+
65
+
66
+ @step.register
67
+ class SignFiles(SignBase):
68
+ """*(Windows)* Signs executable files in build directory"""
69
+
70
+ name = "Sign"
71
+ runs_after = ["Build"]
72
+ runs_before = ["Pack"]
73
+
74
+ def get_files(self, config: env.Config, rt: env.Runtime) -> List[str]:
75
+ cfg = cast(dict, rt._cfg.get("sign", {}))
76
+ roots = cfg.get("directories", ["bin", "lib", "libexec", "share"])
77
+ exclude = cfg.get("exclude", ["*-test"])
78
+
79
+ result: List[str] = []
80
+ build_dir = config.build_dir
81
+ for root in roots:
82
+ for curr_dir, _, filenames in os.walk(os.path.join(build_dir, root)):
83
+ for filename in filenames:
84
+ if should_exclude(filename, exclude, config.os):
85
+ continue
86
+
87
+ full_path = os.path.join(curr_dir, filename)
88
+ if not win32.is_pe_exec(full_path):
89
+ continue
90
+
91
+ result.append(full_path)
92
+ return result
93
+
94
+
95
+ @step.register
96
+ class SignMsi(SignBase):
97
+ """*(Windows)* Signs MSI installers in build directory"""
98
+
99
+ name = "SignPackages"
100
+ runs_after = ["Pack"]
101
+ runs_before = ["StorePackages", "Store"]
102
+
103
+ def is_active(self, config: env.Config, rt: env.Runtime) -> int:
104
+ return super().is_active(config, rt) and "WIX" in config.items.get(
105
+ "cpack_generator", []
106
+ )
107
+
108
+ def get_files(self, config: env.Config, rt: env.Runtime) -> List[str]:
109
+ result: List[str] = []
110
+ pkg_dir = os.path.join(config.build_dir, "packages")
111
+ for curr_dir, dirnames, filenames in os.walk(pkg_dir):
112
+ dirnames[:] = []
113
+ for filename in filenames:
114
+ _, ext = os.path.splitext(filename)
115
+ if ext.lower() != ".msi":
116
+ continue
117
+
118
+ result.append(os.path.join(curr_dir, filename))
119
+
120
+ return result
121
+
122
+
123
+ class SignInit(init.InitStep):
124
+ def postprocess(self, rt: env.Runtime, context: dict):
125
+ if sys.platform == "win32":
126
+ with open(".gitignore", "ab") as ignoref:
127
+ ignoref.write("\n/signature.key\n".encode("UTF-8"))
128
+
129
+
130
+ init.register_init_step(SignInit())
@@ -0,0 +1,191 @@
1
+ # Copyright (c) 2025 Marcin Zdun
2
+ # This code is licensed under MIT license (see LICENSE for details)
3
+
4
+ """
5
+ The **proj_flow.plugins.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
+ import winreg
17
+ from typing import Iterable, List, NamedTuple, Optional, Tuple
18
+
19
+ from proj_flow.api.env import Runtime
20
+
21
+ ENV_KEY = "SIGN_TOKEN"
22
+
23
+ Version = Tuple[int, int, int]
24
+
25
+ machine = {"ARM64": "arm64", "AMD64": "x64", "X86": "x86"}.get(
26
+ platform.machine(), "x86"
27
+ )
28
+
29
+
30
+ def find_sign_tool(rt: Runtime) -> Optional[str]:
31
+ with winreg.OpenKeyEx(
32
+ winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows Kits\Installed Roots"
33
+ ) as kits:
34
+ try:
35
+ kits_root = winreg.QueryValueEx(kits, "KitsRoot10")[0]
36
+ except FileNotFoundError:
37
+ rt.message("sign/win32: No KitsRoot10 value")
38
+ return None
39
+
40
+ versions: List[Tuple[Version, str]] = []
41
+ try:
42
+ index = 0
43
+ while True:
44
+ ver_str = winreg.EnumKey(kits, index)
45
+ ver = tuple(int(chunk) for chunk in ver_str.split("."))
46
+ index += 1
47
+ versions.append((ver, ver_str))
48
+ except OSError:
49
+ pass
50
+ versions.sort()
51
+ versions.reverse()
52
+ rt.message(
53
+ "sign/win32: Regarding versions:", ", ".join(version[1] for version in versions)
54
+ )
55
+ for _, version in versions:
56
+ # C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe
57
+ sign_tool = os.path.join(kits_root, "bin", version, machine, "signtool.exe")
58
+ if os.path.isfile(sign_tool):
59
+ rt.message("sign/win32: using:", sign_tool)
60
+ return sign_tool
61
+ return None
62
+
63
+
64
+ class Key(NamedTuple):
65
+ token: str
66
+ secret: bytes
67
+
68
+
69
+ def _get_key_from_contents(key: str, rt: Runtime):
70
+ try:
71
+ obj = json.loads(key)
72
+ except json.decoder.JSONDecodeError:
73
+ rt.message("sign/win32: the signature is not a valid JSON document")
74
+ return None
75
+
76
+ if not isinstance(obj, dict):
77
+ rt.message("sign/win32: the signature is missing required fields")
78
+ return None
79
+
80
+ token = obj.get("token")
81
+ secret = obj.get("secret")
82
+ if not isinstance(token, str) or not isinstance(secret, str):
83
+ rt.message("sign/win32: the signature is missing required fields")
84
+ return None
85
+
86
+ return Key(
87
+ base64.b64decode(token).decode("UTF-8"),
88
+ base64.b64decode(secret),
89
+ )
90
+
91
+
92
+ def get_key(rt: Runtime) -> Optional[Key]:
93
+ rt.message(f"sign/win32: trying ${ENV_KEY}")
94
+ env = os.environ.get(ENV_KEY)
95
+ if env:
96
+ key = _get_key_from_contents(env, rt)
97
+ if key is not None:
98
+ return key
99
+ local_signature = os.path.join(".", "signature.key")
100
+ home_signature = os.path.join(os.path.expanduser("~"), "signature.key")
101
+ for filename in [local_signature, home_signature]:
102
+ rt.message(f"sign/win32: trying {filename}")
103
+ if os.path.isfile(filename):
104
+ with open(filename, encoding="UTF-8") as file:
105
+ result = file.read().strip()
106
+ key = _get_key_from_contents(result, rt)
107
+ if key is not None:
108
+ return key
109
+
110
+ rt.message("sign/win32: no key set up")
111
+
112
+ return None
113
+
114
+
115
+ def is_active(os_name: str, rt: Runtime):
116
+ if os_name != "windows":
117
+ return False
118
+ key = get_key(rt)
119
+ return (
120
+ key is not None
121
+ and key.token is not None
122
+ and key.secret is not None
123
+ and find_sign_tool(rt) is not None
124
+ )
125
+
126
+
127
+ _IMAGE_DOS_HEADER = "HHHHHHHHHHHHHH8sHH20sI"
128
+ _IMAGE_NT_HEADERS_Signature = "H"
129
+ _IMAGE_DOS_HEADER_size = struct.calcsize(_IMAGE_DOS_HEADER)
130
+ _IMAGE_NT_HEADERS_Signature_size = struct.calcsize(_IMAGE_NT_HEADERS_Signature)
131
+ _MZ = 23117
132
+ _PE = 17744
133
+
134
+
135
+ def is_pe_exec(path: str):
136
+ with open(path, "rb") as exe:
137
+ mz_header = exe.read(_IMAGE_DOS_HEADER_size)
138
+ dos_header = struct.unpack(_IMAGE_DOS_HEADER, mz_header)
139
+ if dos_header[0] != _MZ:
140
+ return False
141
+
142
+ PE_offset = dos_header[-1]
143
+ if PE_offset < _IMAGE_DOS_HEADER_size:
144
+ return False
145
+
146
+ if PE_offset > _IMAGE_DOS_HEADER_size:
147
+ exe.read(PE_offset - _IMAGE_DOS_HEADER_size)
148
+
149
+ pe_header = exe.read(_IMAGE_NT_HEADERS_Signature_size)
150
+ signature = struct.unpack(_IMAGE_NT_HEADERS_Signature, pe_header)[0]
151
+ return signature == _PE
152
+
153
+
154
+ def sign(files: Iterable[str], rt: Runtime):
155
+ key = get_key(rt)
156
+
157
+ if key is None or key.token is None or key.secret is None:
158
+ print("proj-flow: sign: the key is missing", file=sys.stderr)
159
+ return 1
160
+
161
+ sign_tool = find_sign_tool(rt)
162
+ if sign_tool is None:
163
+ print("proj-flow: sign: signtool.exe not found", file=sys.stderr)
164
+ sys.exit(0)
165
+
166
+ with open("temp.pfx", "wb") as pfx:
167
+ pfx.write(key.secret)
168
+
169
+ args = [
170
+ sign_tool,
171
+ "sign",
172
+ "/f",
173
+ "temp.pfx",
174
+ "/p",
175
+ key.token,
176
+ "/tr",
177
+ "http://timestamp.digicert.com",
178
+ "/fd",
179
+ "sha256",
180
+ "/td",
181
+ "sha256",
182
+ *files,
183
+ ]
184
+
185
+ result = 1
186
+ try:
187
+ result = subprocess.run(args, shell=False).returncode
188
+ finally:
189
+ os.remove("temp.pfx")
190
+
191
+ return result
@@ -0,0 +1,11 @@
1
+ # Copyright (c) 2025 Marcin Zdun
2
+ # This code is licensed under MIT license (see LICENSE for details)
3
+
4
+ """
5
+ The **proj_flow.plugins.store** provides ``"Store"``, ``"StoreTests"`` and
6
+ ``"StorePackages"`` steps.
7
+ """
8
+
9
+ from . import store_both, store_packages, store_tests
10
+
11
+ __all__ = ["store_both", "store_tests", "store_packages"]
@@ -0,0 +1,22 @@
1
+ # Copyright (c) 2025 Marcin Zdun
2
+ # This code is licensed under MIT license (see LICENSE for details)
3
+
4
+ """
5
+ The **proj_flow.plugins.store** provides ``"Store"`` step.
6
+ """
7
+
8
+ from proj_flow.api import step
9
+
10
+ from .store_packages import StorePackages
11
+ from .store_tests import StoreTests
12
+
13
+
14
+ @step.register
15
+ class StoreBoth(step.SerialStep):
16
+ """Stores all artifacts created for ``preset`` config value."""
17
+
18
+ name = "Store"
19
+
20
+ def __init__(self):
21
+ super().__init__()
22
+ self.children = [StoreTests(), StorePackages()]
@@ -0,0 +1,79 @@
1
+ # Copyright (c) 2025 Marcin Zdun
2
+ # This code is licensed under MIT license (see LICENSE for details)
3
+
4
+ """
5
+ The **proj_flow.plugins.store** provides ``"StorePackages"`` step.
6
+ """
7
+
8
+ import os
9
+ import shutil
10
+ from typing import List, cast
11
+
12
+ from proj_flow.api import env, step
13
+ from proj_flow.base.uname import uname
14
+
15
+ from ..cmake.parser import get_project
16
+
17
+ _system, _version, _arch = uname()
18
+ _version = "" if _version is None else f"-{_version}"
19
+ _project_pkg = None
20
+
21
+
22
+ def _package_name(config: env.Config, pkg: str, group: str):
23
+ debug = "-dbg" if config.build_type.lower() == "debug" else ""
24
+ suffix = group and f"-{group}" or ""
25
+
26
+ return f"{pkg}-{_system}{_version}-{_arch}{debug}{suffix}"
27
+
28
+
29
+ @step.register
30
+ class StorePackages:
31
+ """Stores archives and installers build for ``preset`` config value."""
32
+
33
+ name = "StorePackages"
34
+ runs_after = ["Pack"]
35
+
36
+ def run(self, config: env.Config, rt: env.Runtime) -> int:
37
+ if not rt.dry_run:
38
+ os.makedirs("build/artifacts", exist_ok=True)
39
+
40
+ packages_dir = f"build/{config.preset}/packages"
41
+
42
+ global _project_pkg
43
+ if _project_pkg is None:
44
+ project = get_project("")
45
+ if project is None:
46
+ rt.fatal(f"Cannot get project information from {rt.root}")
47
+ _project_pkg = project.pkg
48
+
49
+ main_group = cast(str, rt._cfg.get("package", {}).get("main-group"))
50
+ if main_group is not None and not rt.dry_run:
51
+ src = _package_name(config, _project_pkg, main_group)
52
+ dst = _package_name(config, _project_pkg, "")
53
+ rt.print("mv", *(f"{package}.*" for package in (src, dst)), raw=True)
54
+ for _, dirnames, filenames in os.walk(packages_dir):
55
+ dirnames[:] = []
56
+ extensions = [
57
+ filename[len(src) :]
58
+ for filename in filenames
59
+ if len(filename) > len(src)
60
+ and filename[: len(src)] == src
61
+ and filename[len(src)] == "."
62
+ ]
63
+ for extension in extensions:
64
+ shutil.move(
65
+ f"{packages_dir}/{src}{extension}",
66
+ f"{packages_dir}/{dst}{extension}",
67
+ )
68
+
69
+ GITHUB_OUTPUT = os.environ.get("GITHUB_OUTPUT")
70
+ if GITHUB_OUTPUT is not None:
71
+ with open(GITHUB_OUTPUT, "a", encoding="UTF-8") as github_output:
72
+ generators = ",".join(config.items.get("cpack_generator", []))
73
+ print(f"CPACK_GENERATORS={generators}", file=github_output)
74
+
75
+ return rt.cp(
76
+ packages_dir,
77
+ "build/artifacts/packages",
78
+ f"^{_project_pkg}-.*$",
79
+ )
@@ -0,0 +1,21 @@
1
+ # Copyright (c) 2025 Marcin Zdun
2
+ # This code is licensed under MIT license (see LICENSE for details)
3
+
4
+ """
5
+ The **proj_flow.plugins.store** provides ``"StoreTests"`` step.
6
+ """
7
+
8
+ from proj_flow.api import env, step
9
+
10
+
11
+ @step.register
12
+ class StoreTests(step.Step):
13
+ """Stores test results gathered during tests for ``preset`` config value."""
14
+
15
+ name = "StoreTests"
16
+ runs_after = ["Test"]
17
+
18
+ def run(self, config: env.Config, rt: env.Runtime) -> int:
19
+ return rt.cp(
20
+ f"build/{config.preset}/test-results", "build/artifacts/test-results"
21
+ )
@@ -0,0 +1,27 @@
1
+ ---
2
+ Language: Cpp
3
+ BasedOnStyle: Chromium
4
+ AccessModifierOffset: -4
5
+ AllowShortIfStatementsOnASingleLine: true
6
+ AllowShortEnumsOnASingleLine: true
7
+ AllowShortFunctionsOnASingleLine: All
8
+ AllowShortLambdasOnASingleLine: All
9
+ BreakConstructorInitializersBeforeComma: true
10
+ IndentWidth: 4
11
+ NamespaceIndentation: All
12
+ TabWidth: 4
13
+ UseTab: ForIndentation
14
+ ...
15
+ Language: JavaScript
16
+ BasedOnStyle: Chromium
17
+ AccessModifierOffset: -4
18
+ AllowShortIfStatementsOnASingleLine: true
19
+ AllowShortEnumsOnASingleLine: true
20
+ AllowShortFunctionsOnASingleLine: All
21
+ AllowShortLambdasOnASingleLine: All
22
+ BreakConstructorInitializersBeforeComma: true
23
+ IndentWidth: 4
24
+ NamespaceIndentation: All
25
+ TabWidth: 4
26
+ UseTab: ForIndentation
27
+ ...
@@ -0,0 +1,38 @@
1
+ entry:
2
+ config: [ Conan, CMake ]
3
+ build: [ Build ]
4
+ test: [ Build, Test ]
5
+ verify:
6
+ - Build
7
+ - Test
8
+ - Sign
9
+ - Pack
10
+ - SignPackages
11
+ - Store
12
+ - BinInst
13
+ - DevInst
14
+
15
+ compiler:
16
+ names:
17
+ clang: [ clang, clang++ ]
18
+ gcc: [ gcc, g++ ]
19
+ os-default: { ubuntu: gcc, windows: msvc }
20
+
21
+ lts:
22
+ ubuntu:
23
+ - ubuntu-20.04
24
+ - ubuntu-22.04
25
+ - ubuntu-24.04
26
+
27
+ postproc:
28
+ exclude:
29
+ - { github_os: ubuntu-20.04, sanitizer: true }
30
+ - { github_os: ubuntu-24.04, sanitizer: true }
31
+ - { github_os: ubuntu-20.04, compiler: clang }
32
+
33
+
34
+ shortcuts:
35
+ dbg: { build_type: Debug, sanitizer: false }
36
+ rel: { build_type: Release, sanitizer: false }
37
+ both: { build_type: [ Debug, Release ], sanitizer: false }
38
+ sane: { build_type: Debug, sanitizer: true }