pg-maker 0.1.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.
Files changed (181) hide show
  1. pg_maker/__init__.py +3 -0
  2. pg_maker/__main__.py +3 -0
  3. pg_maker/api.py +594 -0
  4. pg_maker/artifacts/__init__.py +4 -0
  5. pg_maker/artifacts/catalog.py +67 -0
  6. pg_maker/artifacts/manifest.py +301 -0
  7. pg_maker/assets.py +93 -0
  8. pg_maker/builders/__init__.py +4 -0
  9. pg_maker/builders/autotools.py +430 -0
  10. pg_maker/builders/components.py +359 -0
  11. pg_maker/bundled/PROJECT.md +57 -0
  12. pg_maker/bundled/build.yaml +29 -0
  13. pg_maker/bundled/components/pg-stat-kcache.yaml +30 -0
  14. pg_maker/bundled/components/pg-stat-statements.yaml +24 -0
  15. pg_maker/bundled/components/pg-wait-sampling.yaml +30 -0
  16. pg_maker/bundled/config/defaults.yaml +80 -0
  17. pg_maker/bundled/config/documentation.yaml +24 -0
  18. pg_maker/bundled/config/features/my-feature.yaml +17 -0
  19. pg_maker/bundled/config/vendor.yaml +13 -0
  20. pg_maker/bundled/documentation/README.md +20 -0
  21. pg_maker/bundled/documentation/pg-stat-kcache.sgml +99 -0
  22. pg_maker/bundled/documentation/pg-stat-kcache.yaml +15 -0
  23. pg_maker/bundled/documentation/pg-wait-sampling.sgml +122 -0
  24. pg_maker/bundled/documentation/pg-wait-sampling.yaml +15 -0
  25. pg_maker/bundled/patches/unix-socket-directory.patch +5 -0
  26. pg_maker/changes/__init__.py +1 -0
  27. pg_maker/changes/apply.py +298 -0
  28. pg_maker/changes/loader.py +135 -0
  29. pg_maker/changes/workspace.py +221 -0
  30. pg_maker/cli.py +546 -0
  31. pg_maker/components/__init__.py +4 -0
  32. pg_maker/config.py +514 -0
  33. pg_maker/development/__init__.py +1 -0
  34. pg_maker/development/cli.py +104 -0
  35. pg_maker/development/git.py +362 -0
  36. pg_maker/development/session.py +735 -0
  37. pg_maker/documentation/__init__.py +4 -0
  38. pg_maker/documentation/builder.py +467 -0
  39. pg_maker/documentation/cli.py +24 -0
  40. pg_maker/documentation/config.py +127 -0
  41. pg_maker/documentation/install.py +79 -0
  42. pg_maker/documentation/site.py +166 -0
  43. pg_maker/documentation/sphinx_ext.py +150 -0
  44. pg_maker/documentation/theme/_static/pg-maker.css +26 -0
  45. pg_maker/documentation/theme/_static/pg-maker.js +35 -0
  46. pg_maker/documentation/theme/_templates/footer_en.html +5 -0
  47. pg_maker/documentation/theme/_templates/footer_metadata.html +13 -0
  48. pg_maker/documentation/theme/_templates/footer_ru.html +5 -0
  49. pg_maker/documentation/translation.py +341 -0
  50. pg_maker/documentation/validation.py +132 -0
  51. pg_maker/documentation/worker.py +62 -0
  52. pg_maker/documentation/writer.py +324 -0
  53. pg_maker/documentation/xml.py +210 -0
  54. pg_maker/documents.py +118 -0
  55. pg_maker/errors.py +50 -0
  56. pg_maker/execution/__init__.py +1 -0
  57. pg_maker/execution/build.py +502 -0
  58. pg_maker/execution/cache.py +287 -0
  59. pg_maker/execution/cancellation.py +60 -0
  60. pg_maker/execution/checkpoints.py +218 -0
  61. pg_maker/execution/cleanup.py +122 -0
  62. pg_maker/execution/component_cache.py +164 -0
  63. pg_maker/execution/development.py +109 -0
  64. pg_maker/execution/engine.py +171 -0
  65. pg_maker/execution/events.py +65 -0
  66. pg_maker/execution/incremental.py +178 -0
  67. pg_maker/execution/preparation.py +130 -0
  68. pg_maker/execution/process.py +155 -0
  69. pg_maker/execution/resources.py +41 -0
  70. pg_maker/execution/state.py +82 -0
  71. pg_maker/execution/worker.py +45 -0
  72. pg_maker/identity.py +116 -0
  73. pg_maker/integrations/__init__.py +1 -0
  74. pg_maker/integrations/machine.py +163 -0
  75. pg_maker/integrations/runtime.py +387 -0
  76. pg_maker/models.py +245 -0
  77. pg_maker/packaging/__init__.py +4 -0
  78. pg_maker/packaging/deb.py +337 -0
  79. pg_maker/packaging/service.py +165 -0
  80. pg_maker/packaging/spec.py +64 -0
  81. pg_maker/packaging/templates/deb-control.in +8 -0
  82. pg_maker/packaging/tree.py +239 -0
  83. pg_maker/planning/__init__.py +1 -0
  84. pg_maker/planning/documents.py +65 -0
  85. pg_maker/planning/environments.py +318 -0
  86. pg_maker/planning/graph.py +23 -0
  87. pg_maker/planning/plan.py +418 -0
  88. pg_maker/planning/resolve.py +117 -0
  89. pg_maker/planning/reuse.py +80 -0
  90. pg_maker/planning/runtime_repository.py +90 -0
  91. pg_maker/postgresql.py +59 -0
  92. pg_maker/progress.py +64 -0
  93. pg_maker/publishing/__init__.py +4 -0
  94. pg_maker/py.typed +0 -0
  95. pg_maker/recipes/README.md +13 -0
  96. pg_maker/recipes/platforms/debian-13.yaml +125 -0
  97. pg_maker/recipes/postgresql.yaml +114 -0
  98. pg_maker/runners/__init__.py +1 -0
  99. pg_maker/runners/base.py +29 -0
  100. pg_maker/runners/docker.py +312 -0
  101. pg_maker/schemas/build.schema.json +5 -0
  102. pg_maker/schemas/change-set.schema.json +5 -0
  103. pg_maker/schemas/component-envelope.schema.json +69 -0
  104. pg_maker/schemas/component.schema.json +5 -0
  105. pg_maker/schemas/defaults.schema.json +5 -0
  106. pg_maker/schemas/doc-rules.schema.json +5 -0
  107. pg_maker/schemas/documentation.schema.json +5 -0
  108. pg_maker/schemas/environment.schema.json +228 -0
  109. pg_maker/schemas/feature.schema.json +5 -0
  110. pg_maker/schemas/insertions.schema.json +5 -0
  111. pg_maker/schemas/lock.schema.json +3963 -0
  112. pg_maker/schemas/operation-input.schema.json +306 -0
  113. pg_maker/schemas/operation-state.schema.json +127 -0
  114. pg_maker/schemas/plan.schema.json +284 -0
  115. pg_maker/schemas/planning-common.schema.json +8192 -0
  116. pg_maker/schemas/platform.schema.json +5 -0
  117. pg_maker/schemas/postgresql-catalog.schema.json +5 -0
  118. pg_maker/schemas/run-event.schema.json +72 -0
  119. pg_maker/schemas/translations.schema.json +5 -0
  120. pg_maker/schemas/vendor.schema.json +5 -0
  121. pg_maker/security.py +73 -0
  122. pg_maker/sources/__init__.py +1 -0
  123. pg_maker/sources/resolver.py +238 -0
  124. pg_maker/sources/store.py +279 -0
  125. pg_maker/storage.py +127 -0
  126. pg_maker/validation/__init__.py +4 -0
  127. pg_maker/validation/installed.py +667 -0
  128. pg_maker/validation/packages.py +309 -0
  129. pg_maker/validation/systemctl.py +185 -0
  130. pg_maker/yamlio.py +54 -0
  131. pg_maker-0.1.0.data/data/share/doc/pg-maker/CHANGELOG.md +22 -0
  132. pg_maker-0.1.0.data/data/share/doc/pg-maker/CONTRIBUTING.md +63 -0
  133. pg_maker-0.1.0.data/data/share/doc/pg-maker/LICENSE +21 -0
  134. pg_maker-0.1.0.data/data/share/doc/pg-maker/README.md +163 -0
  135. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/architecture.md +55 -0
  136. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/development-storage.md +72 -0
  137. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/development.md +86 -0
  138. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/README.md +32 -0
  139. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/api.md +75 -0
  140. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/build.md +112 -0
  141. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/changes.md +58 -0
  142. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/cleanup.md +17 -0
  143. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/components.md +39 -0
  144. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/configuration.md +83 -0
  145. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/development.md +88 -0
  146. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/documentation.md +241 -0
  147. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/documents.md +42 -0
  148. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/environments.md +40 -0
  149. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/execution.md +49 -0
  150. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/machine.md +31 -0
  151. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/observability.md +28 -0
  152. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/packaging.md +107 -0
  153. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/planning.md +63 -0
  154. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/postgresql.md +79 -0
  155. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/publication.md +20 -0
  156. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/recovery.md +83 -0
  157. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/runtime.md +31 -0
  158. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/sources.md +48 -0
  159. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/state.md +49 -0
  160. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/interfaces/storage.md +24 -0
  161. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/releasing.md +98 -0
  162. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/reuse.md +24 -0
  163. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/roadmap.md +54 -0
  164. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/default-components.md +56 -0
  165. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/development-release.md +49 -0
  166. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/documentation-theme.md +45 -0
  167. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/integration.md +49 -0
  168. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/postgresql-majors.md +107 -0
  169. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/review-fixes.md +68 -0
  170. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/stage4.md +65 -0
  171. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/stage56.md +66 -0
  172. pg_maker-0.1.0.data/data/share/doc/pg-maker/docs/validation/stage7.md +72 -0
  173. pg_maker-0.1.0.data/data/share/doc/pg-maker/tests/fixtures/guc/README.md +11 -0
  174. pg_maker-0.1.0.data/data/share/doc/pg-maker/tests/fixtures/stage4/README.md +31 -0
  175. pg_maker-0.1.0.data/data/share/doc/pg-maker/tests/fixtures/stage7/README.md +16 -0
  176. pg_maker-0.1.0.dist-info/METADATA +200 -0
  177. pg_maker-0.1.0.dist-info/RECORD +181 -0
  178. pg_maker-0.1.0.dist-info/WHEEL +5 -0
  179. pg_maker-0.1.0.dist-info/entry_points.txt +4 -0
  180. pg_maker-0.1.0.dist-info/licenses/LICENSE +21 -0
  181. pg_maker-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,359 @@
1
+ """Explicit Make/PGXS components and fresh-cluster acceptance checks."""
2
+
3
+ import re
4
+ import shutil
5
+ import sys
6
+ from contextlib import contextmanager
7
+ from pathlib import Path
8
+
9
+ from pg_maker.builders.autotools import regression_evidence
10
+ from pg_maker.documents import relative_output
11
+ from pg_maker.errors import CommandError, MakerError, PreconditionError, ValidationError
12
+ from pg_maker.storage import read_json, write_json
13
+ from pg_maker.yamlio import load_yaml
14
+
15
+
16
+ def integrate(branch, components):
17
+ for component in components:
18
+ spec, identifier = component["spec"], component["metadata"]["id"]
19
+ if spec["type"] != "source-tree":
20
+ continue
21
+ relative = spec.get("source_directory")
22
+ if not relative or not relative_output(relative):
23
+ raise ValidationError("source-tree component requires a relative integration directory")
24
+ destination = branch.source / relative
25
+ if destination.exists() or destination.is_symlink():
26
+ raise PreconditionError("source-tree component must not overwrite PostgreSQL files")
27
+ if any(p.is_symlink() for p in destination.parents):
28
+ raise ValidationError("component destination traverses a symlink")
29
+ shutil.copytree(
30
+ branch.inputs.tree(spec["source"]["source_hash"]), destination, symlinks=True
31
+ )
32
+ branch.report.setdefault("integrated_components", []).append(identifier)
33
+
34
+
35
+ @contextmanager
36
+ def cluster(branch, name, *, database="postgres", preload=()):
37
+ if not re.fullmatch(r"[A-Za-z0-9_.-]+", name):
38
+ raise ValidationError("invalid cluster identifier")
39
+ root = branch.root / "clusters" / name
40
+ root.mkdir(parents=True)
41
+ container = branch.container_root + "/clusters/" + name
42
+ data = container + "/data"
43
+ sockets = container + "/socket"
44
+ (root / "socket").mkdir()
45
+ prefix = branch.prefix
46
+ env = {
47
+ "PGHOST": sockets,
48
+ "PGPORT": "55432",
49
+ "PGUSER": "builder",
50
+ "PGDATABASE": database,
51
+ "PATH": prefix + "/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
52
+ "LC_ALL": "C.UTF-8",
53
+ }
54
+ branch.command(
55
+ [prefix + "/bin/initdb", "-D", data, "-A", "trust", "--no-locale", "-U", "builder"]
56
+ )
57
+ settings = "listen_addresses = ''\nport = 55432\nunix_socket_directories = '" + sockets + "'\n"
58
+ if preload:
59
+ if any(not re.fullmatch(r"[A-Za-z0-9_/-]+", item) for item in preload):
60
+ raise ValidationError("invalid preload library")
61
+ settings += "shared_preload_libraries = '" + ",".join(preload) + "'\n"
62
+ with (root / "data/postgresql.conf").open("a") as stream:
63
+ stream.write(settings)
64
+ # pg_ctl readiness is bounded; all PostgreSQL file logs stay under PGDATA.
65
+ branch.command(
66
+ [prefix + "/bin/pg_ctl", "-D", data, "-l", data + "/server.log", "-w", "-t", "60", "start"]
67
+ )
68
+ try:
69
+ if database != "postgres":
70
+ branch.command(
71
+ [prefix + "/bin/createdb", database], env={**env, "PGDATABASE": "postgres"}
72
+ )
73
+ yield env, root / "data/server.log"
74
+ finally:
75
+ # Cancellation stops the entire owned container; no unrelated host process is touched.
76
+ if not branch.context.token.requested():
77
+ original_error = sys.exc_info()[0] is not None
78
+ try:
79
+ branch.command(
80
+ [
81
+ prefix + "/bin/pg_ctl",
82
+ "-D",
83
+ data,
84
+ "-w",
85
+ "-t",
86
+ "60",
87
+ "-m",
88
+ "immediate",
89
+ "stop",
90
+ ]
91
+ )
92
+ except MakerError:
93
+ # A timed-out command may already have stopped the container. Preserve its
94
+ # failure; the outer engine still releases/verifies the entire owned resource.
95
+ if not original_error:
96
+ raise
97
+
98
+
99
+ def build_components(branch, components, *, run_tests=True):
100
+ reports = []
101
+ for component in components:
102
+ spec, identifier = component["spec"], component["metadata"]["id"]
103
+ kind = spec["type"]
104
+ if kind in {"contrib", "source-tree"}:
105
+ relative = spec.get("source_directory", "contrib/" + identifier)
106
+ if not (branch.source / relative / "Makefile").is_file():
107
+ raise PreconditionError("component source directory is missing")
108
+ cwd = branch.container_root + "/obj/" + relative
109
+ (branch.obj / relative).mkdir(parents=True, exist_ok=True)
110
+ component_source = branch.source / relative
111
+ container_source = branch.container_root + "/source/" + relative
112
+ else:
113
+ path = branch.root / "components" / identifier
114
+ if not path.exists():
115
+ shutil.copytree(
116
+ branch.inputs.tree(spec["source"]["source_hash"]), path, symlinks=True
117
+ )
118
+ cwd = branch.container_root + "/components/" + identifier
119
+ if spec.get("source_directory"):
120
+ cwd += "/" + spec["source_directory"]
121
+ path /= spec["source_directory"]
122
+ component_source, container_source = path, cwd
123
+ if spec.get("configure_args") or (
124
+ kind in {"utility", "library"} and (path / "configure").is_file()
125
+ ):
126
+ branch.command(
127
+ ["./configure", "--prefix=" + branch.prefix, *spec.get("configure_args", [])],
128
+ cwd=cwd,
129
+ )
130
+ make = ["make", "--output-sync=target", "-j", str(branch.variant["build"]["jobs"])]
131
+ if kind in {"contrib", "source-tree"}:
132
+ make += ["-f", container_source + "/Makefile"]
133
+ if kind == "pgxs":
134
+ make += ["USE_PGXS=1", "PG_CONFIG=" + branch.prefix + "/bin/pg_config"]
135
+ elif kind in {"utility", "library"}:
136
+ make += ["prefix=" + branch.prefix, "PG_CONFIG=" + branch.prefix + "/bin/pg_config"]
137
+ for targets in (spec["make_targets"], spec["install_targets"]):
138
+ if targets:
139
+ branch.command([*make, *targets], cwd=cwd)
140
+ if not run_tests:
141
+ reports.append(
142
+ {"id": identifier, "built": True, "native_targets": [], "tests": "skipped"}
143
+ )
144
+ continue
145
+ expected = None
146
+ if kind in {"pgxs", "contrib", "source-tree"} and spec["test_targets"]:
147
+ result = branch.command(
148
+ [
149
+ *make,
150
+ *(["-f", "Makefile"] if kind == "pgxs" else []),
151
+ "-f",
152
+ "/work/source/inventory.mk",
153
+ "maker-inventory",
154
+ ],
155
+ cwd=cwd,
156
+ )
157
+ values = dict(
158
+ re.findall(r"^PG_MAKER_([A-Z_]+)=(.*)$", Path(result.stdout_log).read_text(), re.M)
159
+ )
160
+ expected = {
161
+ "regression": values["REGRESS"].split(),
162
+ "isolation": values["ISOLATION"].split(),
163
+ "tap": [
164
+ p.relative_to(component_source).as_posix()
165
+ for p in sorted((component_source / "t").glob("*.pl"))
166
+ ]
167
+ if values["TAP"] == "1"
168
+ else [],
169
+ }
170
+ if not any(expected.values()):
171
+ raise PreconditionError("component has no declared native test inventory")
172
+ write_json(branch.output / "components" / identifier / "expected.json", expected)
173
+ with cluster(branch, "component-" + identifier, preload=spec.get("preload", [])) as (
174
+ env,
175
+ _,
176
+ ):
177
+ if spec.get("shared_library"):
178
+ library = spec["shared_library"].replace("'", "''")
179
+ branch.command(
180
+ [
181
+ branch.prefix + "/bin/psql",
182
+ "-X",
183
+ "-v",
184
+ "ON_ERROR_STOP=1",
185
+ "-c",
186
+ "LOAD '" + library + "'",
187
+ ],
188
+ env=env,
189
+ )
190
+ if spec.get("sql_extension"):
191
+ extension = spec["sql_extension"].replace('"', '""')
192
+ branch.command(
193
+ [
194
+ branch.prefix + "/bin/psql",
195
+ "-X",
196
+ "-v",
197
+ "ON_ERROR_STOP=1",
198
+ "-c",
199
+ 'CREATE EXTENSION "' + extension + '" CASCADE',
200
+ ],
201
+ env=env,
202
+ )
203
+ for target in spec["test_targets"]:
204
+ tap = branch.output / "components" / identifier / target / "tap"
205
+ tap.mkdir(parents=True)
206
+ test_env = {
207
+ **env,
208
+ "PERL5LIB": "/work/source",
209
+ "PG_MAKER_SOURCE": container_source,
210
+ "PG_MAKER_TAP": "/work/output/"
211
+ + branch.id
212
+ + "/components/"
213
+ + identifier
214
+ + "/"
215
+ + target
216
+ + "/tap",
217
+ }
218
+ result = branch.command(
219
+ [*make, target, "PROVE_FLAGS=--formatter=PgMaker::Formatter"],
220
+ cwd=cwd,
221
+ env=test_env,
222
+ )
223
+ if expected is not None:
224
+ verify_component_tests(expected, Path(result.stdout_log).read_text(), tap)
225
+ reports.append(
226
+ {
227
+ "id": identifier,
228
+ "built": True,
229
+ "native_targets": spec["test_targets"],
230
+ "tests": "passed",
231
+ "passed": True,
232
+ }
233
+ )
234
+ branch.report.setdefault("components", []).extend(reports)
235
+
236
+
237
+ def verify_component_tests(expected, stdout, tap):
238
+ evidence = regression_evidence(stdout)
239
+ for kind in ("regression", "isolation"):
240
+ values = [v for (k, _), v in evidence.items() if k == kind]
241
+ actual = [name for v in values for name in v["tests"]]
242
+ if sorted(expected[kind]) != sorted(actual) or any(
243
+ v["failed"] or v["plans"] != [len(v["tests"])] for v in values
244
+ ):
245
+ raise CommandError("component regression inventory mismatch", details={"kind": kind})
246
+ values = [read_json(p) for p in tap.glob("*.json")]
247
+ if sorted(v["path"] for v in values) != sorted(expected["tap"]) or any(
248
+ v["tests"] <= len(v["skipped"])
249
+ or v["tests"] != v["planned"]
250
+ or v["failed"]
251
+ or v["errors"]
252
+ or v["skip_all"]
253
+ or v["exit"]
254
+ or v["wait"]
255
+ for v in values
256
+ ):
257
+ raise CommandError("component TAP inventory mismatch")
258
+
259
+
260
+ def acceptance(branch, changes):
261
+ reports, seen = [], set()
262
+ for change in changes:
263
+ for test in change["spec"]["acceptance"]:
264
+ if branch.branch not in test["expectations"]:
265
+ continue
266
+ if test["id"] in seen:
267
+ continue
268
+ seen.add(test["id"])
269
+ blob = branch.inputs.blob(test["script"]["sha256"])
270
+ script = branch.helper / ("acceptance-" + test["id"])
271
+ shutil.copyfile(blob, script)
272
+ script_path = "/work/source/" + script.name
273
+ with cluster(
274
+ branch, "acceptance-" + test["id"], database=test["setup"]["database"]
275
+ ) as (env, log):
276
+ if test["kind"] == "command":
277
+ document = load_yaml(blob)
278
+ if (
279
+ not isinstance(document, dict)
280
+ or set(document) != {"argv"}
281
+ or not isinstance(document["argv"], list)
282
+ or not document["argv"]
283
+ or not all(isinstance(v, str) and "\x00" not in v for v in document["argv"])
284
+ ):
285
+ raise ValidationError(
286
+ "command acceptance script must be YAML {argv: [program, arguments...]}"
287
+ )
288
+ argv = [arg.replace("{prefix}", branch.prefix) for arg in document["argv"]]
289
+ else:
290
+ argv = [
291
+ branch.prefix + "/bin/psql",
292
+ "-X",
293
+ "-A",
294
+ "-t",
295
+ "-v",
296
+ "ON_ERROR_STOP=1",
297
+ "-v",
298
+ "VERBOSITY=verbose",
299
+ "-f",
300
+ script_path,
301
+ ]
302
+ sqlstate_assertion = test["kind"] != "command" and any(
303
+ a["source"] == "sqlstate" for a in test["assertions"]
304
+ )
305
+ result = branch.command(
306
+ argv,
307
+ env=env,
308
+ timeout=test["timeout_seconds"],
309
+ accepted_exit_codes=(0, 3) if sqlstate_assertion else (0,),
310
+ )
311
+ stdout, stderr = (
312
+ Path(result.stdout_log).read_text(),
313
+ Path(result.stderr_log).read_text(),
314
+ )
315
+ outputs = {
316
+ "stdout": stdout,
317
+ "stderr": stderr,
318
+ "sql": stdout,
319
+ "server-log": log.read_text(),
320
+ "sqlstate": sqlstate(result.returncode, stderr),
321
+ }
322
+ failures = []
323
+ for assertion in test["assertions"]:
324
+ output = outputs[assertion["source"]]
325
+ if "contains" in assertion:
326
+ passed = assertion["contains"] in output
327
+ else:
328
+ expected = assertion["equals"]
329
+ if isinstance(expected, bool):
330
+ expected = "t" if expected else "f"
331
+ passed = output.strip() == str(expected)
332
+ if not passed:
333
+ failures.append(assertion["id"])
334
+ expectation = test["expectations"][branch.branch]
335
+ passed = (
336
+ not failures
337
+ if expectation["outcome"] == "pass"
338
+ else failures == [expectation.get("assertion_id")]
339
+ )
340
+ report = {
341
+ "id": test["id"],
342
+ "expectation": expectation,
343
+ "failed_assertions": failures,
344
+ "passed": passed,
345
+ }
346
+ reports.append(report)
347
+ write_json(branch.output / "acceptance.json", reports)
348
+ if not passed:
349
+ raise CommandError("acceptance expectation mismatch", details=report)
350
+ branch.report["acceptance"] = reports
351
+
352
+
353
+ def sqlstate(returncode, stderr):
354
+ if returncode == 0:
355
+ return "00000"
356
+ match = re.search(r"ERROR:\s+([0-9A-Z]{5}):", stderr)
357
+ if returncode != 3 or not match:
358
+ raise CommandError("SQL failure has no valid SQLSTATE evidence")
359
+ return match[1]
@@ -0,0 +1,57 @@
1
+ # pg_maker project
2
+
3
+ The initial target is PostgreSQL 18.1 / Debian 13 / x86_64 with feature my-feature.
4
+ Edit vendor Test, paths, build modes and resources in config/. Defaults enable the
5
+ common socket patch. Configuration and file logs live in PGDATA; headers, PGXS and
6
+ ecpg are not shipped.
7
+
8
+ The feature selects pg_stat_statements, pg_stat_kcache 2.3.2 and pg_wait_sampling 1.1.11
9
+ (SQL version 1.1). Editable component recipes pin external sources by commit SHA;
10
+ pg_stat_statements uses PostgreSQL contrib. Set components: [] for no extra extensions.
11
+ Do not remove pg_stat_statements while pg_stat_kcache requires it.
12
+
13
+ ```bash
14
+ pg-maker validate build.yaml
15
+ pg-maker config show build.yaml --resolved
16
+ pg-maker package names build.yaml
17
+ pg-maker env prepare build.yaml --out environments.json
18
+ pg-maker resolve build.yaml --environments environments.json --out lock.json --input-store inputs
19
+ pg-maker plan lock.json --out plan.json --goal build
20
+ pg-maker build lock.json --plan plan.json --detach
21
+ ```
22
+
23
+ Environment preparation installs dependencies into separate Docker images. Resolve
24
+ freezes sources/changes; offline resolve reuses stored inputs without networking.
25
+ Plan verifies hashes and describes work. Database/component tests, acceptance and
26
+ baseline are disabled by default for fast development; results remain unverified.
27
+ Use build --run-db-tests for full testing, or create a matching saved plan first.
28
+ The default is config/defaults.yaml spec.validation.run_db_tests. Utility tests are
29
+ unaffected. Build --goal package produces three DEBs and manifests; verified requests
30
+ long source/installed suites. The systemctl emulator does not qualify native systemd.
31
+ The build user must match the non-root host workspace owner.
32
+
33
+ Enable documentation in config/documentation.yaml before environment preparation and
34
+ resolve. Docs build produces selected HTML/man/PDF; prepare-translations exports bundles
35
+ for an external translator. Register completed bundles under spec.translations and
36
+ resolve again. Rendering is offline. With docs, a fourth docs package is added and
37
+ server only Suggests it. Selected external-component guides join navigation/search/PDF;
38
+ Russian builds require corresponding translation bundles.
39
+
40
+ Package installation does not activate extensions in existing clusters. Configure
41
+ shared_preload_libraries in order pg_stat_statements,pg_stat_kcache,pg_wait_sampling,
42
+ restart and create extensions in the needed databases, following their manuals.
43
+
44
+ Long operations support --detach and --request-id. Status/events/wait/cancel accept
45
+ operations/OPERATION_ID. Do not edit active input/state/journal files. Logs live under
46
+ attempts/N/logs, progress under attempts/N/progress.log and worker output in worker.log.
47
+ Repeated init retains identical files and reports conflicts before any remaining writes.
48
+
49
+ Resume build with the original plan hash; preparation requires its input hash. Changed
50
+ code belongs in a new resolve/build or a dev session. Dev open --from-run creates an
51
+ owned Git workspace for intermediate commits, patch replay and selected component builds.
52
+
53
+ Export preserves packages/reports/evidence; --include-inputs adds source trees/blobs,
54
+ not Docker images. Shared cache defaults to operations/.cache. No-cache disables shared
55
+ reuse but preserves private checkpoints. Preview cache prune before applying it.
56
+ Monitor df/du/docker system df. Export needed results before explicit scratch cleanup;
57
+ cleaned runs cannot resume. Global Docker prune is never automatic.
@@ -0,0 +1,29 @@
1
+ api_version: pg_maker/v1
2
+ kind: PostgreSQLBuild
3
+ metadata:
4
+ name: test-18-1
5
+ spec:
6
+ config_dir: ./config
7
+ source:
8
+ kind: git
9
+ repository: https://github.com/postgres/postgres.git
10
+ ref: REL_18_1
11
+ postgresql:
12
+ version: '18.1'
13
+ product:
14
+ release: 0
15
+ package_revision: 1
16
+ features:
17
+ - my-feature
18
+ targets:
19
+ - id: debian13-x64
20
+ platform: debian-13
21
+ architecture: x86_64
22
+ cpu_target: x86-64
23
+ artifacts:
24
+ - deb
25
+ documentation:
26
+ profile: ./config/documentation.yaml
27
+ validation:
28
+ profile: full
29
+ baseline: auto
@@ -0,0 +1,30 @@
1
+ api_version: pg_maker/component/v1
2
+ kind: ComponentRecipe
3
+ metadata:
4
+ id: pg-stat-kcache
5
+ revision: 1
6
+ spec:
7
+ type: pgxs
8
+ source:
9
+ kind: git
10
+ repository: https://github.com/powa-team/pg_stat_kcache.git
11
+ # Release REL2_3_2 (2.3.2).
12
+ ref: 1e39814453ab747a3817cfdf4f6f6bd5c0fb9507
13
+ compatible_postgresql: '>=14,<20'
14
+ requires: [pg-stat-statements]
15
+ conflicts: []
16
+ build_dependencies: []
17
+ runtime_dependencies: []
18
+ make_targets: [all]
19
+ test_targets: [installcheck]
20
+ install_targets: [install]
21
+ required_tests: []
22
+ sql_extension: pg_stat_kcache
23
+ shared_library: pg_stat_kcache
24
+ preload: [pg_stat_statements, pg_stat_kcache]
25
+ runtime_files:
26
+ - lib/postgresql/pg_stat_kcache.so
27
+ - share/postgresql/extension/pg_stat_kcache.control
28
+ - share/postgresql/extension/pg_stat_kcache--*.sql
29
+ documentation:
30
+ rules: [../documentation/pg-stat-kcache.yaml]
@@ -0,0 +1,24 @@
1
+ api_version: pg_maker/component/v1
2
+ kind: ComponentRecipe
3
+ metadata:
4
+ id: pg-stat-statements
5
+ revision: 1
6
+ spec:
7
+ type: contrib
8
+ source_directory: contrib/pg_stat_statements
9
+ compatible_postgresql: '>=14,<20'
10
+ requires: []
11
+ conflicts: []
12
+ build_dependencies: []
13
+ runtime_dependencies: []
14
+ make_targets: [all]
15
+ test_targets: [check]
16
+ install_targets: [install]
17
+ required_tests: []
18
+ sql_extension: pg_stat_statements
19
+ shared_library: pg_stat_statements
20
+ preload: [pg_stat_statements]
21
+ runtime_files:
22
+ - lib/postgresql/pg_stat_statements.so
23
+ - share/postgresql/extension/pg_stat_statements.control
24
+ - share/postgresql/extension/pg_stat_statements--*.sql
@@ -0,0 +1,30 @@
1
+ api_version: pg_maker/component/v1
2
+ kind: ComponentRecipe
3
+ metadata:
4
+ id: pg-wait-sampling
5
+ revision: 1
6
+ spec:
7
+ type: pgxs
8
+ source:
9
+ kind: git
10
+ repository: https://github.com/postgrespro/pg_wait_sampling.git
11
+ # Release v1.1.11; the SQL extension version is 1.1.
12
+ ref: 7e3a5fc937393596c0a51c5881dd63d7208408d1
13
+ compatible_postgresql: '>=14,<20'
14
+ requires: []
15
+ conflicts: []
16
+ build_dependencies: []
17
+ runtime_dependencies: []
18
+ make_targets: [all]
19
+ test_targets: [installcheck]
20
+ install_targets: [install]
21
+ required_tests: []
22
+ sql_extension: pg_wait_sampling
23
+ shared_library: pg_wait_sampling
24
+ preload: [pg_wait_sampling]
25
+ runtime_files:
26
+ - lib/postgresql/pg_wait_sampling.so
27
+ - share/postgresql/extension/pg_wait_sampling.control
28
+ - share/postgresql/extension/pg_wait_sampling--*.sql
29
+ documentation:
30
+ rules: [../documentation/pg-wait-sampling.yaml]
@@ -0,0 +1,80 @@
1
+ api_version: pg_maker/defaults/v1
2
+ kind: MakerDefaults
3
+ metadata:
4
+ id: default
5
+ spec:
6
+ paths:
7
+ runtime_prefix: /opt/{vendor.slug}/{feature.id}/{postgresql.major}{build.mode_suffix}
8
+ data_directory: /var/lib/{vendor.slug}/{feature.id}/{postgresql.major}{build.mode_suffix}/data
9
+ documentation_directory: /opt/{vendor.slug}/{feature.id}/{postgresql.major}{build.mode_suffix}/share/doc/{product.version}
10
+ service:
11
+ user: postgres
12
+ group: postgres
13
+ home: /var/lib/postgresql
14
+ port: 5432
15
+ naming:
16
+ package: '{vendor.slug}-{feature.id}-{role}{role.abi_suffix}-{postgresql.major}{build.mode_suffix}'
17
+ service: '{vendor.slug}-{feature.id}-server-{postgresql.major}{build.mode_suffix}'
18
+ deb_version: '{product.version}-{package_revision}.{platform.package_suffix}'
19
+ rpm_version: '{product.version}'
20
+ rpm_release: '{package_revision}.{platform.package_suffix}'
21
+ common_changes:
22
+ - builtin: unix-socket-directory
23
+ build:
24
+ backend: autotools
25
+ mode: release
26
+ options:
27
+ llvm: true
28
+ openssl: true
29
+ icu: true
30
+ nls_languages:
31
+ - en
32
+ - ru
33
+ message_locale: C.UTF-8
34
+ jobs: 4
35
+ flags:
36
+ cppflags:
37
+ - -Wno-missing-braces
38
+ - -Wformat
39
+ - -Werror=format-security
40
+ - -fstack-protector-strong
41
+ ldflags:
42
+ - -Wl,-z,relro
43
+ - -Wl,-z,now
44
+ lto: false
45
+ build_modes:
46
+ release:
47
+ naming_suffix: ''
48
+ configure_options: []
49
+ cflags:
50
+ - -pipe
51
+ - -O2
52
+ cppflags:
53
+ - -D_FORTIFY_SOURCE=2
54
+ debug:
55
+ naming_suffix: -debug
56
+ configure_options:
57
+ - --enable-debug
58
+ - --enable-cassert
59
+ cflags:
60
+ - -pipe
61
+ - -O0
62
+ - -ggdb3
63
+ cppflags: []
64
+ validation:
65
+ profile: full
66
+ # Temporary development default; enable explicitly for source verification.
67
+ run_db_tests: false
68
+ baseline: auto
69
+ database_locales:
70
+ - en_US.UTF-8
71
+ - ru_RU.UTF-8
72
+ execution:
73
+ runner: docker
74
+ max_parallel_targets: 1
75
+ build_user: 1000:1000
76
+ limits:
77
+ memory_bytes: 8589934592
78
+ min_free_disk_bytes: 21474836480
79
+ stage_timeout_seconds: 7200
80
+ keep_failed_environment: false
@@ -0,0 +1,24 @@
1
+ api_version: pg_maker/documentation/v1
2
+ kind: DocumentationProfile
3
+ metadata:
4
+ id: default
5
+ spec:
6
+ enabled: false
7
+ source: postgres-tree
8
+ source_language: en
9
+ languages:
10
+ - en
11
+ language_aliases: {en: en, en-US: en, ru: ru, ru-RU: ru}
12
+ formats:
13
+ - html
14
+ - man
15
+ html_backend: sphinx
16
+ branding: vendor-metadata
17
+ rules: []
18
+ translations: []
19
+ missing_translation: error
20
+ require_reviewed: false
21
+ glossary: {revision: '1', terms: {}}
22
+ install:
23
+ html: docs-package
24
+ man: owning-package
@@ -0,0 +1,17 @@
1
+ api_version: pg_maker/feature/v1
2
+ kind: FeatureProfile
3
+ metadata:
4
+ id: my-feature
5
+ revision: 2
6
+ spec:
7
+ description: Test
8
+ numeric_id: null
9
+ changes: []
10
+ components:
11
+ - ../../components/pg-stat-statements.yaml
12
+ - ../../components/pg-stat-kcache.yaml
13
+ - ../../components/pg-wait-sampling.yaml
14
+ build_options: {}
15
+ documentation:
16
+ rules: []
17
+ required_tests: []
@@ -0,0 +1,13 @@
1
+ api_version: pg_maker/vendor/v1
2
+ kind: VendorConfig
3
+ metadata:
4
+ id: test
5
+ spec:
6
+ company: Test
7
+ brand: Test
8
+ product_name: Test
9
+ description: Test
10
+ slug: test
11
+ homepage: null
12
+ documentation_url: null
13
+ contact_email: test@example.invalid