native-packages 0.3.1 → 0.4.0
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.
- checksums.yaml +4 -4
- data/README.md +10 -2
- data/docs/configuration.md +59 -0
- data/docs/native-recipes.md +7 -1
- data/docs/releasing.md +1 -1
- data/examples/native-packages-all-formats.yaml +1 -1
- data/examples/native-packages.yaml +1 -1
- data/lib/native_packages/build.rb +108 -18
- data/lib/native_packages/cli.rb +7 -3
- data/lib/native_packages/support.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: db11bd5b39c572e4983697e34112948cf182169145c1504a5c2223bd6c01bad0
|
|
4
|
+
data.tar.gz: 379244306eeb13a7e501cd3808dbe3fd81337079e70b129ca446e243a311daaa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4169d1495a88f410c99a59ff1de20b9d6162468e5ddd4a3082a89e843ded13f84d74401cd91f4fde3b6e4457dfa91aa9ea78e19c7c0560c7fd89c3bd18785ec8
|
|
7
|
+
data.tar.gz: 2e89f5a56a85d642e8139c1c6725c3f90db7d664580c33ac64c6a432cac1998f5fa1e7cb220e415597532a83802b068c72f3be47c36a301ce51c795e89494244
|
data/README.md
CHANGED
|
@@ -84,6 +84,14 @@ Release mode verifies the declared binary assets against the release checksum fi
|
|
|
84
84
|
|
|
85
85
|
Outputs include packages, prepared recipes, checksums and `build.json`. Publication verifies their hashes, configuration identity and complete target set. Partial builds can be combined with `aggregate DIR... --output DIRECTORY`. Native filenames come from nFPM.
|
|
86
86
|
|
|
87
|
+
Since 0.4.0, `build --defer-recipes` lets each platform package its
|
|
88
|
+
own inputs without acquiring global recipe assets or requiring AUR tools.
|
|
89
|
+
Then `aggregate --finalize-recipes` generates the downstream recipes once,
|
|
90
|
+
using hashes of the completed packages. This allows a Homebrew cask to reference
|
|
91
|
+
the DMG being built in the same release. Deferred builds cannot be published.
|
|
92
|
+
See [deferred recipe generation](docs/configuration.md#deferred-recipe-generation)
|
|
93
|
+
for the finalizer's inputs and checks.
|
|
94
|
+
|
|
87
95
|
Optional `repositories` and `templates` sections replace the old separate registries. Existing `stage`, `diff`, `publish TARGET` and `status` commands support reviewed downstream updates. AUR uses PKGBUILD/`.SRCINFO`; Homebrew uses formulae/casks. Each destination has an independent ignored Git clone, with no application submodules or extra remotes. Native source repositories still apply their own validation and review.
|
|
88
96
|
|
|
89
97
|
## GitHub Actions
|
|
@@ -95,7 +103,7 @@ packaging:
|
|
|
95
103
|
needs: release
|
|
96
104
|
permissions:
|
|
97
105
|
contents: write
|
|
98
|
-
uses: crmne/native-packages/.github/workflows/package.yml@v0.
|
|
106
|
+
uses: crmne/native-packages/.github/workflows/package.yml@v0.4.0
|
|
99
107
|
with:
|
|
100
108
|
version: ${{ github.ref_name }}
|
|
101
109
|
publish: true
|
|
@@ -121,7 +129,7 @@ Migration combines `packaging/project.yml`, its nFPM definition and repository r
|
|
|
121
129
|
bundle install
|
|
122
130
|
bundle exec ruby -Ilib -e 'Dir["test/*_test.rb"].sort.each { |path| require_relative path }'
|
|
123
131
|
gem build native-packages.gemspec
|
|
124
|
-
ruby test/gem_install.rb native-packages-0.
|
|
132
|
+
ruby test/gem_install.rb native-packages-0.4.0.gem
|
|
125
133
|
```
|
|
126
134
|
|
|
127
135
|
Tests build real packages, inspect their payloads and exercise repository publication against local Git fixtures. CI additionally runs disposable Linux install/upgrade/remove checks, SRPM rebuilds and Windows MSIX acceptance. Runtime build manifests report installation as `not-tested`: CI fixture coverage is not a substitute for testing each application's packages.
|
data/docs/configuration.md
CHANGED
|
@@ -65,3 +65,62 @@ native-packages publish --from dist/complete --to github
|
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
Each build must use the same configuration, release version and timestamp. Aggregate rejects duplicate target/format outputs, conflicting recipe files and incomplete sets. Publishing recipes can still use the existing reviewed sequence: `stage TARGET DIRECTORY/recipes`, `diff TARGET`, then `publish TARGET --body-file FILE` where required by a submission destination.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
## Deferred recipe generation
|
|
71
|
+
|
|
72
|
+
Since 0.4.0, use this opt-in sequence when downstream recipes depend
|
|
73
|
+
on packages built in separate jobs. The existing build and aggregate commands
|
|
74
|
+
keep generating and checking recipes as before when the new flags are absent.
|
|
75
|
+
There are no new configuration keys.
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
# Run each target on its required host, with the same checkout and epoch.
|
|
79
|
+
native-packages doctor --target linux-amd64 --defer-recipes
|
|
80
|
+
native-packages build --version 1.2.3 --target linux-amd64 --defer-recipes --output dist/linux
|
|
81
|
+
native-packages build --version 1.2.3 --target macos-universal --defer-recipes --output dist/macos
|
|
82
|
+
native-packages build --version 1.2.3 --target windows-amd64 --defer-recipes --output dist/windows
|
|
83
|
+
|
|
84
|
+
# Download those build directories and remaining recipe assets onto Linux.
|
|
85
|
+
native-packages aggregate dist/linux dist/macos dist/windows --finalize-recipes --output dist/complete
|
|
86
|
+
# Run the application's package installation/upgrade/removal checks here.
|
|
87
|
+
native-packages publish --from dist/complete --to github,aur,homebrew
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A deferred build still validates configuration, selected inputs, architecture,
|
|
91
|
+
container contents and signing hooks. It does not acquire the global `assets`
|
|
92
|
+
or generate `templates`. `doctor --defer-recipes` likewise omits recipe tooling;
|
|
93
|
+
the selected package formats still need their own tools. Asset filenames and
|
|
94
|
+
URLs are available as tokens, but unknown `@KEY_SHA256@` values cannot be used
|
|
95
|
+
by a target's input, package definition or hooks. Such a target must use the
|
|
96
|
+
normal build path with its assets already available. Release-mode target inputs
|
|
97
|
+
still require their published checksums, even with `--defer-recipes`.
|
|
98
|
+
|
|
99
|
+
The finalizer first verifies every input manifest and the complete target set.
|
|
100
|
+
All inputs must defer recipes and agree on configuration, version, timestamp,
|
|
101
|
+
tool and recipe metadata, including Git version. Use the same Git history depth
|
|
102
|
+
and `SOURCE_DATE_EPOCH` on every host. Mixing ordinary and deferred builds fails.
|
|
103
|
+
Neither a partial target set nor a deferred build can be published. An ordinary
|
|
104
|
+
aggregate rejects deferred inputs unless `--finalize-recipes` is supplied.
|
|
105
|
+
|
|
106
|
+
For each global asset, finalization first looks for a verified package whose
|
|
107
|
+
release filename exactly matches the asset's rendered `file`. For example,
|
|
108
|
+
`assets.MACOS.file: app-v@VERSION@-macos-universal.dmg` resolves to a native target
|
|
109
|
+
with that output filename. Its hash includes the completed signing/notarization
|
|
110
|
+
hook. A stale `local` file cannot override that package. Duplicate package
|
|
111
|
+
filenames are rejected. The manifest records this asset by package filename
|
|
112
|
+
and digest, without a temporary staging path.
|
|
113
|
+
|
|
114
|
+
Other assets, such as portable or source archives, must be staged at their
|
|
115
|
+
configured `local` paths on the finalization host. Finalization reads local
|
|
116
|
+
inputs and does not download recipe assets from a release. This also applies
|
|
117
|
+
when the deferred target builds used `--release`. Recipe tools run only here:
|
|
118
|
+
AUR templates require makepkg or working Docker, and recipe archives require
|
|
119
|
+
tar and xz. The finalizer renders and validates recipes, writes the recipe
|
|
120
|
+
archive and complete manifest, and atomically creates a fresh output directory.
|
|
121
|
+
Failure leaves the original target directories intact and creates no completed
|
|
122
|
+
output. Existing destinations are never overwritten.
|
|
123
|
+
|
|
124
|
+
Deferral does not enable stable downstream recipes in a prerelease configuration.
|
|
125
|
+
The existing preview-format and stable-recipe restrictions still apply. Use a
|
|
126
|
+
preview configuration without downstream templates when building previews.
|
data/docs/native-recipes.md
CHANGED
|
@@ -43,7 +43,13 @@ targets:
|
|
|
43
43
|
|
|
44
44
|
Run each target on its own host using `build --version VERSION --target ID`.
|
|
45
45
|
Use the normal `aggregate` command to combine complete target results later.
|
|
46
|
-
Use the same configuration and `SOURCE_DATE_EPOCH` across jobs.
|
|
46
|
+
Use the same configuration and `SOURCE_DATE_EPOCH` across jobs.
|
|
47
|
+
Since 0.4.0, configurations with AUR/Homebrew recipes can use
|
|
48
|
+
`build --defer-recipes` on each host, then `aggregate --finalize-recipes` on
|
|
49
|
+
Linux. This avoids requiring the finished DMG before its own build or running
|
|
50
|
+
AUR tools on macOS/Windows. The finalizer resolves recipe assets from the
|
|
51
|
+
completed package filenames and locally staged external assets. See
|
|
52
|
+
[deferred recipe generation](configuration.md#deferred-recipe-generation). No nFPM binary
|
|
47
53
|
is required when only native targets are selected; its version remains part of
|
|
48
54
|
the configuration for compatibility with mixed Linux/native builds.
|
|
49
55
|
|
data/docs/releasing.md
CHANGED
|
@@ -23,7 +23,7 @@ Update the gemspec, `NativePackages::VERSION`, example/version references and ch
|
|
|
23
23
|
Create a GitHub release for that commit:
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
|
-
gh release create v0.
|
|
26
|
+
gh release create v0.4.0 --target main --title 'native-packages 0.4.0' --notes-file release-notes.md
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Adding `--draft` permits review before publication and does not trigger the publisher. The tag must match the exact gem version and point to a commit on `main`. Set `--prerelease` only for a prerelease gem version.
|
|
@@ -27,7 +27,7 @@ module NativePackages
|
|
|
27
27
|
version
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
def doctor(ids: [], formats: [], release: false)
|
|
30
|
+
def doctor(ids: [], formats: [], release: false, defer_recipes: false)
|
|
31
31
|
configuration.validate
|
|
32
32
|
selected = configuration.select(ids: ids, formats: formats)
|
|
33
33
|
nfpm_version if selected.values.any? { |target| (target.fetch("formats") & Configuration::NATIVE_FORMATS).empty? }
|
|
@@ -40,13 +40,19 @@ module NativePackages
|
|
|
40
40
|
required << "readelf" if selected.values.any? { |target| target["platform"] == "linux" && target.fetch("kind", "binary") == "binary" }
|
|
41
41
|
required << "bsdtar" if selected.values.any? { |target| target.fetch("input").fetch("kind", "archive") == "archive" }
|
|
42
42
|
required << "curl" if release
|
|
43
|
-
required += %w[tar xz] unless configuration.data.fetch("templates").empty?
|
|
44
43
|
missing = required.uniq.reject { |tool| available?(tool) }
|
|
45
44
|
raise Error, "install required tools: #{missing.join(', ')}" unless missing.empty?
|
|
45
|
+
recipe_tools unless defer_recipes
|
|
46
|
+
selected
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def recipe_tools
|
|
50
|
+
return if configuration.data.fetch("templates").empty?
|
|
51
|
+
missing = %w[tar xz].reject { |tool| available?(tool) }
|
|
52
|
+
raise Error, "install required recipe tools: #{missing.join(', ')}" unless missing.empty?
|
|
46
53
|
if configuration.data.fetch("templates").keys.any? { |path| path.match?(%r{\Aarch/[^/]+/PKGBUILD\z}) }
|
|
47
54
|
raise Error, "AUR metadata needs makepkg or accessible Docker" unless available?("makepkg") || available?("docker")
|
|
48
55
|
end
|
|
49
|
-
selected
|
|
50
56
|
end
|
|
51
57
|
|
|
52
58
|
def version(value, release)
|
|
@@ -73,7 +79,7 @@ module NativePackages
|
|
|
73
79
|
configuration.tokens(version, epoch: epoch).merge("GIT_VERSION" => git_version)
|
|
74
80
|
end
|
|
75
81
|
|
|
76
|
-
def run_build(value: nil, release: nil, ids: [], formats: [], output: nil, dry_run: false)
|
|
82
|
+
def run_build(value: nil, release: nil, ids: [], formats: [], output: nil, dry_run: false, defer_recipes: false)
|
|
77
83
|
configuration.validate
|
|
78
84
|
selected = configuration.select(ids: ids, formats: formats)
|
|
79
85
|
number = version(value, release)
|
|
@@ -84,15 +90,26 @@ module NativePackages
|
|
|
84
90
|
output = Pathname.new(output || root / "dist/packages" / number).expand_path(root)
|
|
85
91
|
if dry_run
|
|
86
92
|
puts JSON.pretty_generate("version" => number, "mode" => release ? "release" : "local", "output" => output.to_s,
|
|
87
|
-
"targets" => selected.transform_values { |target| target.slice("formats", "platform", "arch", "input", "before_build", "after_package", "native") })
|
|
93
|
+
"recipes" => defer_recipes ? "deferred" : "included", "targets" => selected.transform_values { |target| target.slice("formats", "platform", "arch", "input", "before_build", "after_package", "native") })
|
|
88
94
|
return
|
|
89
95
|
end
|
|
90
96
|
raise Error, "output exists: #{output}; choose a fresh --output" if output.exist?
|
|
91
|
-
doctor(ids: ids, formats: formats, release: !!release)
|
|
97
|
+
doctor(ids: ids, formats: formats, release: !!release, defer_recipes: defer_recipes)
|
|
92
98
|
info = metadata(number, release: !!release)
|
|
93
99
|
sources = []
|
|
94
100
|
expected = release ? release_checksums(info) : nil
|
|
95
|
-
|
|
101
|
+
if defer_recipes
|
|
102
|
+
# Asset hashes are unknown until finalization. Do not silently pass the
|
|
103
|
+
# validation placeholders into a target's input, hooks or package data.
|
|
104
|
+
configuration.data.fetch("assets").each_key { |key| info.delete("#{key}_SHA256") }
|
|
105
|
+
selected.each do |id, target|
|
|
106
|
+
tokens = configuration.target_tokens(info, id, target, "/payload").merge("PACKAGE" => "/output/package", "FORMAT" => target.fetch("formats").first)
|
|
107
|
+
render_tree(target, tokens)
|
|
108
|
+
render_tree(configuration.package(target), tokens)
|
|
109
|
+
end
|
|
110
|
+
else
|
|
111
|
+
acquire_assets(info, expected, sources)
|
|
112
|
+
end
|
|
96
113
|
output.dirname.mkpath
|
|
97
114
|
Dir.mktmpdir(".native-packages-", output.dirname) do |temporary|
|
|
98
115
|
staging = Pathname.new(temporary) / "result"
|
|
@@ -161,17 +178,14 @@ module NativePackages
|
|
|
161
178
|
end
|
|
162
179
|
end
|
|
163
180
|
end
|
|
164
|
-
|
|
165
|
-
project.generate(recipes, info)
|
|
166
|
-
project.check(recipes, prerelease: number.include?("-"))
|
|
167
|
-
if !configuration.data.fetch("templates").empty?
|
|
168
|
-
project.recipe_archive(recipes, staging, name: configuration.name, version: number, epoch: info.fetch("SOURCE_DATE_EPOCH"))
|
|
169
|
-
(staging / "packaging-checksums.txt").delete
|
|
170
|
-
end
|
|
181
|
+
write_recipes(staging, info) unless defer_recipes
|
|
171
182
|
manifest = { "schema" => 1, "name" => configuration.name, "version" => number, "configuration" => configuration.digest,
|
|
172
183
|
"tool" => configuration.data.fetch("tool"), "epoch" => info.fetch("SOURCE_DATE_EPOCH"), "inputs" => sources,
|
|
173
184
|
"targets" => selected.transform_values { |target| target.fetch("formats") }, "packages" => records,
|
|
174
185
|
"files" => files(staging).to_h { |file| [file.relative_path_from(staging).to_s, sha256(file)] } }
|
|
186
|
+
if defer_recipes
|
|
187
|
+
manifest["recipes"] = { "state" => "deferred", "metadata" => info.reject { |key, _| key == "ROOT" } }
|
|
188
|
+
end
|
|
175
189
|
write(staging / "build.json", JSON.pretty_generate(manifest) + "\n")
|
|
176
190
|
write(staging / "packaging-checksums.txt", files(staging).map { |file| "#{sha256(file)} #{file.relative_path_from(staging)}\n" }.join)
|
|
177
191
|
staging.rename(output)
|
|
@@ -212,10 +226,12 @@ module NativePackages
|
|
|
212
226
|
path
|
|
213
227
|
end
|
|
214
228
|
|
|
215
|
-
def acquire_assets(info, expected, sources)
|
|
229
|
+
def acquire_assets(info, expected, sources, packages: {})
|
|
216
230
|
configuration.data.fetch("assets").each do |key, definition|
|
|
217
231
|
asset = render_tree(definition, info)
|
|
218
|
-
path = if
|
|
232
|
+
path = if packages.key?(asset.fetch("file"))
|
|
233
|
+
packages.fetch(asset.fetch("file"))
|
|
234
|
+
elsif expected
|
|
219
235
|
release_input(asset.merge("release_asset" => asset.fetch("file")), info, expected, checksummed: asset.fetch("checksummed", true))
|
|
220
236
|
else
|
|
221
237
|
root / asset.fetch("local") { raise Error, "assets.#{key}: declare local input or use --release" }
|
|
@@ -224,7 +240,15 @@ module NativePackages
|
|
|
224
240
|
info["#{key}_SHA256"] = digest
|
|
225
241
|
info["#{key}_FILE"] = asset.fetch("file")
|
|
226
242
|
info["#{key}_URL"] = asset.fetch("url", "#{info.fetch('UPSTREAM')}/releases/download/#{info.fetch('TAG')}/#{asset.fetch('file')}")
|
|
227
|
-
|
|
243
|
+
source = { "asset" => key, "sha256" => digest }
|
|
244
|
+
# An aggregated package survives under its release filename; the
|
|
245
|
+
# finalizer's temporary staging path does not survive the rename.
|
|
246
|
+
if packages.key?(asset.fetch("file"))
|
|
247
|
+
source["package"] = asset.fetch("file")
|
|
248
|
+
else
|
|
249
|
+
source["path"] = path.to_s
|
|
250
|
+
end
|
|
251
|
+
sources << source
|
|
228
252
|
end
|
|
229
253
|
end
|
|
230
254
|
|
|
@@ -248,6 +272,48 @@ module NativePackages
|
|
|
248
272
|
end
|
|
249
273
|
end
|
|
250
274
|
|
|
275
|
+
def write_recipes(output, info)
|
|
276
|
+
recipes = output / "recipes"
|
|
277
|
+
project.generate(recipes, info)
|
|
278
|
+
project.check(recipes, prerelease: info.fetch("VERSION").include?("-"))
|
|
279
|
+
unless configuration.data.fetch("templates").empty?
|
|
280
|
+
project.recipe_archive(recipes, output, name: configuration.name, version: info.fetch("VERSION"), epoch: info.fetch("SOURCE_DATE_EPOCH"))
|
|
281
|
+
(output / "packaging-checksums.txt").delete
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def verify_recipes(output, manifest, complete:)
|
|
286
|
+
if manifest.key?("recipes")
|
|
287
|
+
phase = manifest.fetch("recipes")
|
|
288
|
+
unless phase.is_a?(Hash) && phase["state"] == "deferred" && phase["metadata"].is_a?(Hash)
|
|
289
|
+
raise Error, "invalid recipe phase"
|
|
290
|
+
end
|
|
291
|
+
info = phase.fetch("metadata")
|
|
292
|
+
unless info.values_at("NAME", "VERSION", "SOURCE_DATE_EPOCH") == manifest.values_at("name", "version", "epoch")
|
|
293
|
+
raise Error, "deferred recipe metadata disagrees with build"
|
|
294
|
+
end
|
|
295
|
+
package_paths = manifest.fetch("packages").map { |entry| entry.fetch("path") }
|
|
296
|
+
raise Error, "deferred build contains recipe files" unless (manifest.fetch("files").keys - package_paths).empty?
|
|
297
|
+
raise Error, "recipes are deferred; aggregate with --finalize-recipes before publishing" if complete
|
|
298
|
+
return
|
|
299
|
+
end
|
|
300
|
+
# Require the recipe files as well as hashes, so dropping the deferred
|
|
301
|
+
# marker cannot make an unfinished build appear publishable.
|
|
302
|
+
required = ["recipes/release.json"]
|
|
303
|
+
raise Error, "missing recipe metadata" unless manifest.fetch("files").key?(required.first)
|
|
304
|
+
info = JSON.parse((output / required.first).read)
|
|
305
|
+
configuration.data.fetch("templates").each_key do |path|
|
|
306
|
+
relative = render(path, info.merge("ROOT" => root.to_s))
|
|
307
|
+
required << "recipes/#{relative_path(relative)}"
|
|
308
|
+
required << "recipes/#{File.dirname(relative)}/.SRCINFO" if relative.match?(%r{\Aarch/[^/]+/PKGBUILD\z})
|
|
309
|
+
end
|
|
310
|
+
unless configuration.data.fetch("templates").empty?
|
|
311
|
+
required << "#{configuration.name}-#{manifest.fetch('version')}-packaging.tar.xz"
|
|
312
|
+
end
|
|
313
|
+
missing = required - manifest.fetch("files").keys
|
|
314
|
+
raise Error, "missing recipe outputs: #{missing.join(', ')}" unless missing.empty?
|
|
315
|
+
end
|
|
316
|
+
|
|
251
317
|
def verify(output, complete: true)
|
|
252
318
|
output = Pathname.new(output).expand_path(root)
|
|
253
319
|
manifest = JSON.parse((output / "build.json").read)
|
|
@@ -271,6 +337,7 @@ module NativePackages
|
|
|
271
337
|
manifest.fetch("packages").each do |entry|
|
|
272
338
|
raise Error, "package hash does not match file manifest" unless entry.fetch("sha256") == manifest.fetch("files").fetch(entry.fetch("path"))
|
|
273
339
|
end
|
|
340
|
+
verify_recipes(output, manifest, complete: complete)
|
|
274
341
|
manifest
|
|
275
342
|
end
|
|
276
343
|
|
|
@@ -304,7 +371,7 @@ module NativePackages
|
|
|
304
371
|
end
|
|
305
372
|
end
|
|
306
373
|
|
|
307
|
-
def aggregate(inputs, output:)
|
|
374
|
+
def aggregate(inputs, output:, finalize_recipes: false)
|
|
308
375
|
configuration.validate
|
|
309
376
|
output = Pathname.new(output).expand_path(root)
|
|
310
377
|
raise Error, "output exists: #{output}" if output.exist?
|
|
@@ -314,6 +381,14 @@ module NativePackages
|
|
|
314
381
|
%w[version epoch tool].each do |key|
|
|
315
382
|
raise Error, "builds disagree on #{key}" unless manifests.map { |entry| entry.fetch(key) }.uniq.length == 1
|
|
316
383
|
end
|
|
384
|
+
phases = manifests.map { |entry| entry["recipes"] }
|
|
385
|
+
if phases.any?
|
|
386
|
+
raise Error, "do not mix deferred and completed recipe builds" if phases.any?(&:nil?)
|
|
387
|
+
raise Error, "deferred recipe metadata differs between builds" unless phases.uniq.length == 1
|
|
388
|
+
raise Error, "deferred builds require --finalize-recipes" unless finalize_recipes
|
|
389
|
+
elsif finalize_recipes
|
|
390
|
+
raise Error, "--finalize-recipes requires builds made with --defer-recipes"
|
|
391
|
+
end
|
|
317
392
|
result = Marshal.load(Marshal.dump(manifests.first))
|
|
318
393
|
result.merge!("targets" => {}, "packages" => [], "files" => {}, "inputs" => manifests.flat_map { |entry| entry.fetch("inputs") }.uniq)
|
|
319
394
|
output.dirname.mkpath
|
|
@@ -338,6 +413,21 @@ module NativePackages
|
|
|
338
413
|
configuration.targets.each do |id, target|
|
|
339
414
|
result["targets"][id] = target.fetch("formats").select { |format| result["targets"].fetch(id, []).include?(format) }
|
|
340
415
|
end
|
|
416
|
+
if finalize_recipes
|
|
417
|
+
expected = configuration.targets.transform_values { |target| target.fetch("formats") }
|
|
418
|
+
raise Error, "incomplete target set; aggregate all configured targets before finalizing recipes" unless result.fetch("targets") == expected
|
|
419
|
+
recipe_tools
|
|
420
|
+
info = result.fetch("recipes").fetch("metadata").merge("ROOT" => root.to_s)
|
|
421
|
+
packages = result.fetch("packages").each_with_object({}) do |entry, paths|
|
|
422
|
+
name = File.basename(entry.fetch("path"))
|
|
423
|
+
raise Error, "ambiguous recipe asset filename: #{name}" if paths.key?(name)
|
|
424
|
+
paths[name] = staging / entry.fetch("path")
|
|
425
|
+
end
|
|
426
|
+
acquire_assets(info, nil, result.fetch("inputs"), packages: packages)
|
|
427
|
+
write_recipes(staging, info)
|
|
428
|
+
result.delete("recipes")
|
|
429
|
+
result["files"] = files(staging).to_h { |file| [file.relative_path_from(staging).to_s, sha256(file)] }
|
|
430
|
+
end
|
|
341
431
|
write(staging / "build.json", JSON.pretty_generate(result) + "\n")
|
|
342
432
|
write(staging / "packaging-checksums.txt", files(staging).map { |file| "#{sha256(file)} #{file.relative_path_from(staging)}\n" }.join)
|
|
343
433
|
verify(staging)
|
data/lib/native_packages/cli.rb
CHANGED
|
@@ -13,7 +13,9 @@ module NativePackages
|
|
|
13
13
|
build [--version VERSION | --release TAG] Build all configured packages
|
|
14
14
|
[--target ID] [--format FORMAT] Select targets/formats (repeatable)
|
|
15
15
|
[--output DIRECTORY] [--dry-run] Choose output or inspect the plan
|
|
16
|
+
[--defer-recipes] Build targets without recipe assets/tools
|
|
16
17
|
aggregate DIR... --output DIRECTORY Combine target builds before publishing
|
|
18
|
+
[--finalize-recipes] Generate recipes from deferred builds
|
|
17
19
|
publish --from DIRECTORY --to github,aur Publish a complete, verified build
|
|
18
20
|
migrate [--dry-run] Convert packaging/project.yml
|
|
19
21
|
repositories List downstream destinations
|
|
@@ -50,11 +52,13 @@ module NativePackages
|
|
|
50
52
|
flags.on("--target ID") { |value| options[:ids] << value }
|
|
51
53
|
flags.on("--format FORMAT") { |value| options[:formats] << value }
|
|
52
54
|
flags.on("--release TAG") { |value| options[:release] = value }
|
|
55
|
+
flags.on("--defer-recipes") { options[:defer_recipes] = true }
|
|
53
56
|
end
|
|
54
57
|
if command == "build"
|
|
55
58
|
flags.on("--version VERSION") { |value| options[:value] = value }
|
|
56
59
|
flags.on("--dry-run") { options[:dry_run] = true }
|
|
57
60
|
end
|
|
61
|
+
flags.on("--finalize-recipes") { options[:finalize_recipes] = true } if command == "aggregate"
|
|
58
62
|
if command == "init"
|
|
59
63
|
flags.on("--interactive") { options[:interactive] = true }
|
|
60
64
|
flags.on("--name NAME") { |value| options[:name] = value }
|
|
@@ -88,13 +92,13 @@ module NativePackages
|
|
|
88
92
|
raise Error, "create native-packages.yaml with init, or convert existing packaging with migrate" unless configuration
|
|
89
93
|
builder = Build.new(configuration)
|
|
90
94
|
case command
|
|
91
|
-
when "build" then return builder.run_build(**options.slice(:value, :release, :ids, :formats, :output, :dry_run))
|
|
95
|
+
when "build" then return builder.run_build(**options.slice(:value, :release, :ids, :formats, :output, :dry_run, :defer_recipes))
|
|
92
96
|
when "doctor"
|
|
93
|
-
builder.doctor(**options.slice(:ids, :formats, :release))
|
|
97
|
+
builder.doctor(**options.slice(:ids, :formats, :release, :defer_recipes))
|
|
94
98
|
return puts "Configuration and packaging tools are ready. Build checks input files and package contents."
|
|
95
99
|
when "aggregate"
|
|
96
100
|
raise Error, "aggregate requires --output" unless options[:output]
|
|
97
|
-
return builder.aggregate(arguments, output: options.fetch(:output))
|
|
101
|
+
return builder.aggregate(arguments, output: options.fetch(:output), finalize_recipes: options.fetch(:finalize_recipes, false))
|
|
98
102
|
when "publish" then return builder.publish(options.fetch(:from), options.fetch(:destinations), body_file: options[:body_file])
|
|
99
103
|
end
|
|
100
104
|
end
|