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.
- proj_flow/__init__.py +4 -0
- proj_flow/__main__.py +10 -0
- proj_flow/api/__init__.py +10 -0
- proj_flow/api/arg.py +134 -0
- proj_flow/api/completers.py +93 -0
- proj_flow/api/ctx.py +238 -0
- proj_flow/api/env.py +416 -0
- proj_flow/api/init.py +26 -0
- proj_flow/api/makefile.py +140 -0
- proj_flow/api/step.py +173 -0
- proj_flow/base/__init__.py +11 -0
- proj_flow/base/cmd.py +50 -0
- proj_flow/base/inspect.py +133 -0
- proj_flow/base/matrix.py +240 -0
- proj_flow/base/plugins.py +44 -0
- proj_flow/base/uname.py +71 -0
- proj_flow/flow/__init__.py +11 -0
- proj_flow/flow/cli/__init__.py +66 -0
- proj_flow/flow/cli/cmds.py +385 -0
- proj_flow/flow/cli/finder.py +59 -0
- proj_flow/flow/configs.py +162 -0
- proj_flow/flow/dependency.py +153 -0
- proj_flow/flow/init.py +65 -0
- proj_flow/flow/interact.py +134 -0
- proj_flow/flow/layer.py +176 -0
- proj_flow/flow/steps.py +104 -0
- proj_flow/log/__init__.py +10 -0
- proj_flow/log/commit.py +463 -0
- proj_flow/log/fmt.py +12 -0
- proj_flow/log/format.py +13 -0
- proj_flow/log/hosting/__init__.py +11 -0
- proj_flow/log/hosting/github.py +248 -0
- proj_flow/log/msg.py +201 -0
- proj_flow/log/release.py +34 -0
- proj_flow/log/rich_text/__init__.py +22 -0
- proj_flow/log/rich_text/api.py +126 -0
- proj_flow/log/rich_text/markdown.py +61 -0
- proj_flow/log/rich_text/re_structured_text.py +68 -0
- proj_flow/plugins/__init__.py +8 -0
- proj_flow/plugins/base.py +30 -0
- proj_flow/plugins/cmake/__init__.py +11 -0
- proj_flow/plugins/cmake/__version__.py +5 -0
- proj_flow/plugins/cmake/build.py +29 -0
- proj_flow/plugins/cmake/config.py +59 -0
- proj_flow/plugins/cmake/context.py +112 -0
- proj_flow/plugins/cmake/pack.py +37 -0
- proj_flow/plugins/cmake/parser.py +166 -0
- proj_flow/plugins/cmake/test.py +29 -0
- proj_flow/plugins/commands/__init__.py +12 -0
- proj_flow/plugins/commands/bootstrap.py +21 -0
- proj_flow/plugins/commands/ci/__init__.py +17 -0
- proj_flow/plugins/commands/ci/changelog.py +47 -0
- proj_flow/plugins/commands/ci/matrix.py +46 -0
- proj_flow/plugins/commands/ci/release.py +116 -0
- proj_flow/plugins/commands/init.py +75 -0
- proj_flow/plugins/commands/list.py +167 -0
- proj_flow/plugins/commands/run.py +147 -0
- proj_flow/plugins/commands/system.py +60 -0
- proj_flow/plugins/conan/__init__.py +66 -0
- proj_flow/plugins/conan/_conan.py +146 -0
- proj_flow/plugins/github.py +13 -0
- proj_flow/plugins/sign/__init__.py +130 -0
- proj_flow/plugins/sign/win32.py +191 -0
- proj_flow/plugins/store/__init__.py +11 -0
- proj_flow/plugins/store/store_both.py +22 -0
- proj_flow/plugins/store/store_packages.py +79 -0
- proj_flow/plugins/store/store_tests.py +21 -0
- proj_flow/template/layers/base/.clang-format +27 -0
- proj_flow/template/layers/base/.flow/config.yml +38 -0
- proj_flow/template/layers/base/.flow/flow.py.mustache +172 -0
- proj_flow/template/layers/base/.flow/matrix.yml +63 -0
- proj_flow/template/layers/base/.flow/official.yml +4 -0
- proj_flow/template/layers/base/.gitignore +39 -0
- proj_flow/template/layers/base/README.md.mustache +2 -0
- proj_flow/template/layers/base/flow +7 -0
- proj_flow/template/layers/base/flow.cmd +9 -0
- proj_flow/template/layers/base.json +2 -0
- proj_flow/template/layers/cmake/.flow/cmake/common.cmake.mustache +27 -0
- proj_flow/template/layers/cmake/.flow/extensions/wall/__init__.py.mustache +2 -0
- proj_flow/template/layers/cmake/.flow/extensions/wall/icons/__init__.py.mustache +54 -0
- proj_flow/template/layers/cmake/.flow/extensions/wall/icons/magick.py.mustache +97 -0
- proj_flow/template/layers/cmake/.flow/packages/base.cmake.mustache +33 -0
- proj_flow/template/layers/cmake/.flow/packages/config.cmake +12 -0
- proj_flow/template/layers/cmake/.flow/packages/cpack.cmake +3 -0
- proj_flow/template/layers/cmake/.flow/packages/wix/cpack.cmake.mustache +1 -0
- proj_flow/template/layers/cmake/.flow/packages/wix/patches.in.wix +10 -0
- proj_flow/template/layers/cmake/.flow/packages/wix.cmake.mustache +13 -0
- proj_flow/template/layers/cmake/CMakeGraphVizOptions.cmake +8 -0
- proj_flow/template/layers/cmake/CMakeLists.txt.mustache +213 -0
- proj_flow/template/layers/cmake/CMakePresets.json +196 -0
- proj_flow/template/layers/cmake/data/assets/appicon.ico +0 -0
- proj_flow/template/layers/cmake/data/assets/appicon.png +0 -0
- proj_flow/template/layers/cmake/data/assets/wix_banner.bmp +0 -0
- proj_flow/template/layers/cmake/data/assets/wix_dialog.bmp +0 -0
- proj_flow/template/layers/cmake/data/icons/.gitignore +3 -0
- proj_flow/template/layers/cmake/data/icons/appicon-mask.svg +6 -0
- proj_flow/template/layers/cmake/data/icons/appicon.svg +27 -0
- proj_flow/template/layers/cmake/src/main.cc.mustache +38 -0
- proj_flow/template/layers/cmake/src/version.hpp.in.mustache +36 -0
- proj_flow/template/layers/cmake/tests/test.cc.mustache +17 -0
- proj_flow/template/layers/cmake.json +15 -0
- proj_flow/template/layers/conan/.flow/cmake/libcxx_toolchain.cmake +4 -0
- proj_flow/template/layers/conan/.flow/cmake/output_dirs_setup.cmake +19 -0
- proj_flow/template/layers/conan/conanfile.txt +9 -0
- proj_flow/template/layers/conan.json +3 -0
- proj_flow/template/layers/github_actions/.github/linters/.isort.cfg +4 -0
- proj_flow/template/layers/github_actions/.github/linters/.mypy.ini +2 -0
- proj_flow/template/layers/github_actions/.github/workflows/build.yml +241 -0
- proj_flow/template/layers/github_actions/.github/workflows/linter.yml +59 -0
- proj_flow/template/layers/github_actions/CPPLINT.cfg +16 -0
- proj_flow/template/layers/github_actions.json +3 -0
- proj_flow/template/layers/github_social/.github/ISSUE_TEMPLATE/bug_report.md.mustache +42 -0
- proj_flow/template/layers/github_social/.github/ISSUE_TEMPLATE/feature_request.md.mustache +28 -0
- proj_flow/template/layers/github_social/CODE_OF_CONDUCT.md.mustache +76 -0
- proj_flow/template/layers/github_social/CONTRIBUTING.md +10 -0
- proj_flow/template/layers/github_social.json +3 -0
- proj_flow/template/licenses/0BSD.mustache +12 -0
- proj_flow/template/licenses/MIT.mustache +21 -0
- proj_flow/template/licenses/Unlicense.mustache +24 -0
- proj_flow/template/licenses/WTFPL.mustache +13 -0
- proj_flow/template/licenses/Zlib.mustache +19 -0
- proj_flow-0.8.1.dist-info/METADATA +81 -0
- proj_flow-0.8.1.dist-info/RECORD +126 -0
- proj_flow-0.8.1.dist-info/WHEEL +4 -0
- proj_flow-0.8.1.dist-info/entry_points.txt +2 -0
- 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 }
|