native-packages 0.5.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ccc727393fcd07bd511465e0b2c46b19679a387b6ffee081693231061424fbd9
4
- data.tar.gz: 9a402ad593dd1035f1ab350e9cdc1a81292f0976303d6454c2ad9a3ecc53ae91
3
+ metadata.gz: 1c07643e9e68f0b147f4e0e53867fb60bb19364460e8b8959aae3cb590cc495e
4
+ data.tar.gz: e7bc9be87ac919c2f943ad26c9076825c990cc8ffccdbb84afb86ed436caa1af
5
5
  SHA512:
6
- metadata.gz: ee7950fbfd3a6d8e8d934e2c51a2620e717644c30803d660617d3e44d9c2274819fdc37dd815208493244b553976df685d2d3ec2d129eede0301bb61f7dd80d5
7
- data.tar.gz: a57e20ba12b60b53bcbe0b9e5d896fb874d2535ae73f6f24a03b067ef9310eb35133bd51654d29a58d6e2990577a7dd6d4d056bf796ed44905c00866a087d983
6
+ metadata.gz: fa5823b8ee458ebf4b751dc5693ad5d7d47c356a4a99791a44dda34285aafb89ae60f75ac16ac8a7b0e0d318131ed5d73448e16515e9c52cffb6edd497296cf6
7
+ data.tar.gz: 37440431bcf73aec4eee92929f42fa8b42fe37b4b2911c744ee51ebf643bafd7a34ccc56eb74bca492795a5756b4cae2b203def2fb486b8c6c170ee3bd876e14
data/README.md CHANGED
@@ -46,7 +46,9 @@ targets:
46
46
  release_asset: my-app_@VERSION@_linux_amd64.tar.gz
47
47
  ```
48
48
 
49
- `build` creates all configured outputs. `--target ID` and `--format FORMAT` select a subset; repeat either option for several values. A missing target input fails the build.
49
+ `build` creates all configured outputs. `--target ID` and `--format FORMAT` select a subset; repeat either option for several values. A missing selected target input fails the build. `publish --target ID` requires
50
+ every format for exactly the requested targets, while publication without a
51
+ selection still requires all configured targets.
50
52
 
51
53
  Inputs support `kind: archive` (default), `directory` or `file`. Paths resolve from the configuration directory. Targets can add `nfpm` overrides; maps merge recursively and arrays replace. `nfpm` can also reference a separate YAML file. `.yml` and `--config FILE` are supported.
52
54
 
@@ -92,7 +94,8 @@ Since 0.4.0, `build --defer-recipes` lets each platform package its
92
94
  own inputs without acquiring global recipe assets or requiring AUR tools.
93
95
  Then `aggregate --finalize-recipes` generates the downstream recipes once,
94
96
  using hashes of the completed packages. This allows a Homebrew cask to reference
95
- the DMG being built in the same release. Deferred builds cannot be published.
97
+ the DMG being built in the same release. Deferred builds cannot be published. Native prerelease jobs may defer the
98
+ stable recipes in a shared configuration; those recipes remain stable-only.
96
99
  See [deferred recipe generation](docs/configuration.md#deferred-recipe-generation)
97
100
  for the finalizer's inputs and checks.
98
101
 
@@ -107,7 +110,7 @@ packaging:
107
110
  needs: release
108
111
  permissions:
109
112
  contents: write
110
- uses: crmne/native-packages/.github/workflows/package.yml@v0.5.0
113
+ uses: crmne/native-packages/.github/workflows/package.yml@v0.6.0
111
114
  with:
112
115
  version: ${{ github.ref_name }}
113
116
  publish: true
@@ -116,6 +119,8 @@ packaging:
116
119
 
117
120
  Pin the corresponding commit SHA for an immutable workflow reference. With no version, the workflow validates configuration only. With a version, it installs the configured gem and nFPM, builds packages and uploads an Actions artifact. `publish: true` attaches packages to the existing release.
118
121
 
122
+ Separate configurations have independent concurrency groups. A workflow can validate its stable, alpha and macOS configurations without one queued call cancelling another. The released `v0.5.0` workflow predates this fix; serialize multiple calls with `needs` when using that tag or its commit.
123
+
119
124
  To consume an Actions artifact instead, set `source-artifact` and, if necessary, `source-directory` (default `dist`). Its files must match the local input paths in the configuration. This workflow packages on Linux, including MSIX creation from Windows binaries; native application builds and platform-specific signing jobs can use the CLI separately.
120
125
 
121
126
  AUR publication additionally requires `PUBLISH_AUR=true`, `AUR_SSH_KEY` and `AUR_KNOWN_HOSTS`. Homebrew publication requires `PUBLISH_HOMEBREW=true` and `HOMEBREW_TAP_GITHUB_TOKEN`. Keep existing GoReleaser publishers for destinations they already own.
@@ -133,9 +138,14 @@ Migration combines `packaging/project.yml`, its nFPM definition and repository r
133
138
  bundle install
134
139
  bundle exec ruby -Ilib -e 'Dir["test/*_test.rb"].sort.each { |path| require_relative path }'
135
140
  gem build native-packages.gemspec
136
- ruby test/gem_install.rb native-packages-0.5.0.gem
141
+ ruby test/gem_install.rb native-packages-0.6.0.gem
137
142
  ```
138
143
 
139
144
  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.
140
145
 
141
146
  The [design document](docs/cli-design.md) records the agreed direction. [Gem release setup](docs/releasing.md) explains the RubyLLM-style token setup and GitHub release workflow.
147
+
148
+ For one configuration spanning native hosts, select the Linux target IDs in the
149
+ reusable workflow's `targets` input. Native release jobs use the same config
150
+ with `build --target macos-universal --defer-recipes` on the Mac. See
151
+ [one configuration across native hosts](docs/configuration.md#one-configuration-across-native-hosts).
@@ -31,8 +31,12 @@ native-packages --config native-packages.macos.yaml build \
31
31
  ```
32
32
 
33
33
  The gem imports the certificate and notary credentials into a fresh, temporary
34
- keychain. It never replaces the default keychain or its search list, and deletes
35
- its keychain after success or failure. Credential values are excluded from
34
+ keychain. Since 0.5.1, it adds that keychain to the user's search list when needed
35
+ on a clean runner, retaining existing entries and leaving the default keychain
36
+ unchanged. Signing calls under the same OS user share a lock, so parallel native
37
+ packaging hooks wait for the active signing call instead of racing search-list
38
+ updates. It deletes only its own keychain and search-list entry after success or
39
+ failure, preserving keychains added by other processes. Credential values are excluded from
36
40
  manifests and redacted from tool errors.
37
41
 
38
42
  The input is copied and checked before signing. All Mach-O code and nested
@@ -124,3 +124,39 @@ output. Existing destinations are never overwritten.
124
124
  Deferral does not enable stable downstream recipes in a prerelease configuration.
125
125
  The existing preview-format and stable-recipe restrictions still apply. Use a
126
126
  preview configuration without downstream templates when building previews.
127
+
128
+ ## One configuration across native hosts
129
+
130
+ Keep Linux, macOS and Windows targets in the same `native-packages.yaml`.
131
+ Build native inputs on their native host. Use `--defer-recipes` there when
132
+ stable distribution recipes need assets that have not yet been published:
133
+
134
+ ```sh
135
+ native-packages build --version 1.2.3 --target macos-universal --defer-recipes
136
+ ```
137
+
138
+ The application's release job may collect that native output directly. If it
139
+ uses the shared Linux packaging workflow afterward, set its `targets` input
140
+ to the complete Linux target IDs, for example `linux-amd64,linux-arm64`.
141
+ The workflow passes that exact selection to both build and publication.
142
+ An empty input retains the original all-target behavior. Native targets still
143
+ require their native host; selecting a Mac target does not turn an Ubuntu job
144
+ into a Mac runner.
145
+
146
+ The corresponding CLI publication is explicit:
147
+
148
+ ```sh
149
+ native-packages build --release v1.2.3 --target linux-amd64 --target linux-arm64
150
+ native-packages publish --from dist/packages/1.2.3 --to github \
151
+ --target linux-amd64 --target linux-arm64
152
+ ```
153
+
154
+ Every format for every selected target must be present, and no extra targets
155
+ may be in that build. Unknown IDs, missing formats, altered files, unfinished
156
+ recipes and configuration mismatches still fail before publication. Omitting
157
+ `--target` requires all targets from the configuration. This does not authorize
158
+ publishing an arbitrary partial build.
159
+
160
+ With `release.prereleases: true`, `--defer-recipes` also permits a native
161
+ prerelease to share its config with stable AUR/Homebrew templates. Those
162
+ recipes remain deferred and cannot be finalized or published for a prerelease.
@@ -10,7 +10,7 @@ See [Apple notarization](apple-notarization.md).
10
10
  ```yaml
11
11
  schema: 1
12
12
  tool:
13
- version: '0.5.0'
13
+ version: '0.6.0'
14
14
  nfpm: '2.47.0'
15
15
  nfpm:
16
16
  name: example-app
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.4.0 --target main --title 'native-packages 0.4.0' --notes-file release-notes.md
26
+ gh release create v0.6.0 --target main --title 'native-packages 0.6.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.
@@ -1,7 +1,7 @@
1
1
  # Supply the listed build outputs before running this example.
2
2
  schema: 1
3
3
  tool:
4
- version: '0.5.0'
4
+ version: '0.6.0'
5
5
  nfpm: '2.47.0'
6
6
  nfpm:
7
7
  name: example-app
@@ -1,6 +1,6 @@
1
1
  schema: 1
2
2
  tool:
3
- version: '0.5.0'
3
+ version: '0.6.0'
4
4
  nfpm: '2.47.0'
5
5
  nfpm:
6
6
  name: example-app
@@ -85,7 +85,7 @@ module NativePackages
85
85
  configuration.validate
86
86
  selected = configuration.select(ids: ids, formats: formats)
87
87
  number = version(value, release)
88
- configuration.check_prerelease(number, selected)
88
+ configuration.check_prerelease(number, selected, defer_recipes: defer_recipes)
89
89
  if release && selected.values.any? { |target| target["native"] }
90
90
  raise Error, "native recipes consume local prepared directories; use --version with native build artifacts"
91
91
  end
@@ -330,16 +330,16 @@ module NativePackages
330
330
  raise Error, "missing recipe outputs: #{missing.join(', ')}" unless missing.empty?
331
331
  end
332
332
 
333
- def verify(output, complete: true)
333
+ def verify(output, complete: true, ids: [])
334
334
  output = Pathname.new(output).expand_path(root)
335
335
  manifest = JSON.parse((output / "build.json").read)
336
336
  raise Error, "unsupported build manifest" unless manifest.fetch("schema") == 1
337
337
  raise Error, "build belongs to another project/configuration" unless manifest.fetch("name") == configuration.name && manifest.fetch("configuration") == configuration.digest
338
338
  number = configuration.package_version(manifest.fetch("version"))
339
339
  selected = configuration.select(ids: manifest.fetch("targets").keys)
340
- configuration.check_prerelease(number, selected)
341
- expected = configuration.targets.transform_values { |target| target.fetch("formats") }
342
- raise Error, "incomplete target set; aggregate all configured targets before publishing" if complete && manifest.fetch("targets") != expected
340
+ configuration.check_prerelease(number, selected, defer_recipes: manifest.dig("recipes", "state") == "deferred")
341
+ expected = configuration.select(ids: ids).transform_values { |target| target.fetch("formats") }
342
+ raise Error, "incomplete target set; supply every format for all requested targets before publishing" if complete && manifest.fetch("targets") != expected
343
343
  expected_pairs = manifest.fetch("targets").flat_map { |id, formats| formats.map { |format| [id, format] } }.sort
344
344
  actual_pairs = manifest.fetch("packages").map { |entry| entry.values_at("target", "format") }.sort
345
345
  raise Error, "manifest packages do not match its target set" unless actual_pairs == expected_pairs
@@ -357,10 +357,10 @@ module NativePackages
357
357
  manifest
358
358
  end
359
359
 
360
- def publish(output, destinations, body_file: nil)
360
+ def publish(output, destinations, body_file: nil, ids: [])
361
361
  configuration.validate
362
362
  output = Pathname.new(output).expand_path(root)
363
- manifest = verify(output)
363
+ manifest = verify(output, ids: ids)
364
364
  raise Error, "supply at least one --to destination" if destinations.empty?
365
365
  destinations.each { |name| project.repositories.select(name) unless name == "github" }
366
366
  if manifest.fetch("version").include?("-")
@@ -18,6 +18,7 @@ module NativePackages
18
18
  aggregate DIR... --output DIRECTORY Combine target builds before publishing
19
19
  [--finalize-recipes] Generate recipes from deferred builds
20
20
  publish --from DIRECTORY --to github,aur Publish a complete, verified build
21
+ [--target ID] Require exactly these complete targets
21
22
  migrate [--dry-run] Convert packaging/project.yml
22
23
  repositories List downstream destinations
23
24
  stage TARGET DIRECTORY Stage prepared recipes
@@ -49,8 +50,8 @@ module NativePackages
49
50
  flags.on("--help", "-h") { puts HELP; return }
50
51
  flags.on("--output DIRECTORY") { |value| options[:output] = Pathname.new(value).expand_path(root) } if %w[build aggregate artifacts prepare notarize-macos].include?(command)
51
52
  flags.on("--body-file FILE") { |value| options[:body_file] = Pathname.new(value).expand_path(root) } if command == "publish"
53
+ flags.on("--target ID") { |value| options[:ids] << value } if %w[build doctor publish].include?(command)
52
54
  if %w[build doctor].include?(command)
53
- flags.on("--target ID") { |value| options[:ids] << value }
54
55
  flags.on("--format FORMAT") { |value| options[:formats] << value }
55
56
  flags.on("--release TAG") { |value| options[:release] = value }
56
57
  flags.on("--defer-recipes") { options[:defer_recipes] = true }
@@ -77,6 +78,7 @@ module NativePackages
77
78
  end
78
79
  end
79
80
  parser.parse!(arguments)
81
+ raise Error, "publish --target requires --from" if command == "publish" && !options[:from] && !options[:ids].empty?
80
82
  arity = { "init" => 0..0, "migrate" => 0..0, "build" => 0..0, "doctor" => 0..0, "aggregate" => 1..,
81
83
  "prepare" => 1..1, "check" => 1..1, "artifacts" => 1..1, "publish-release" => 2..2,
82
84
  "notarize-macos" => 1..1, "repositories" => 0..0, "stage" => 2..2, "diff" => 1..1, "publish" => options[:from] ? 0..0 : 1..1,
@@ -104,7 +106,7 @@ module NativePackages
104
106
  when "aggregate"
105
107
  raise Error, "aggregate requires --output" unless options[:output]
106
108
  return builder.aggregate(arguments, output: options.fetch(:output), finalize_recipes: options.fetch(:finalize_recipes, false))
107
- when "publish" then return builder.publish(options.fetch(:from), options.fetch(:destinations), body_file: options[:body_file])
109
+ when "publish" then return builder.publish(options.fetch(:from), options.fetch(:destinations), body_file: options[:body_file], ids: options[:ids])
108
110
  end
109
111
  end
110
112
  if configuration
@@ -56,10 +56,10 @@ module NativePackages
56
56
  def package(target) = merge(data.fetch("nfpm"), target.fetch("nfpm", {}))
57
57
  def package_version(value) = version_arg(value, prerelease: data.fetch("release").fetch("prereleases", false))
58
58
 
59
- def check_prerelease(version, selected)
59
+ def check_prerelease(version, selected, defer_recipes: false)
60
60
  return unless version.include?("-")
61
61
  raise Error, "prerelease builds require release.prereleases: true" unless data.fetch("release")["prereleases"] == true
62
- unless selected.values.all? { |target| (target.fetch("formats") - %w[deb rpm dmg inno]).empty? } && data.fetch("templates").empty?
62
+ unless selected.values.all? { |target| (target.fetch("formats") - %w[deb rpm dmg inno]).empty? } && (defer_recipes || data.fetch("templates").empty?)
63
63
  raise Error, "prereleases support deb, rpm, dmg and inno only, without downstream recipes"
64
64
  end
65
65
  end
@@ -41,6 +41,21 @@ module NativePackages
41
41
  def with_identity
42
42
  doctor
43
43
  return yield(nil) unless enabled?
44
+ # The search list belongs to the OS user, not this project. Coordinate
45
+ # our signing calls across checkouts, including parallel packaging hooks.
46
+ lock_path = signing_lock_path
47
+ FileUtils.mkdir_p(lock_path.dirname, mode: 0o700)
48
+ File.open(lock_path, File::RDWR | File::CREAT, 0o600) do |lock|
49
+ lock.flock(File::LOCK_EX)
50
+ with_temporary_identity { yield self }
51
+ end
52
+ end
53
+
54
+ private def signing_lock_path
55
+ Pathname.new(Dir.home) / "Library/Caches/native-packages/macos-signing.lock"
56
+ end
57
+
58
+ private def with_temporary_identity
44
59
  Dir.mktmpdir("native-packages-signing-") do |directory|
45
60
  @keychain = Pathname.new(directory) / "signing.keychain-db"
46
61
  certificate = Pathname.new(directory) / "certificate.p12"
@@ -49,6 +64,12 @@ module NativePackages
49
64
  @redactions << password
50
65
  begin
51
66
  execute "security", "create-keychain", "-p", password, @keychain
67
+ # Clean runners may not add new keychains to the user search list.
68
+ # codesign needs that entry even when given an explicit --keychain.
69
+ search_list = Shellwords.shellsplit(execute("security", "list-keychains", "-d", "user"))
70
+ unless search_list.any? { |path| File.identical?(path, @keychain) }
71
+ execute "security", "list-keychains", "-d", "user", "-s", *search_list, @keychain
72
+ end
52
73
  execute "security", "set-keychain-settings", "-lut", "21600", @keychain
53
74
  execute "security", "unlock-keychain", "-p", password, @keychain
54
75
  execute "security", "import", certificate, "-k", @keychain,
@@ -63,8 +84,8 @@ module NativePackages
63
84
  "--password", @credentials.fetch("APPLE_APP_PASSWORD")
64
85
  yield self
65
86
  ensure
66
- # Never replace the user's search list or default keychain. Deleting
67
- # just this keychain also removes its own entry from the search list.
87
+ # delete-keychain removes only this keychain and its search-list entry.
88
+ # Never restore an old list over keychains added by another process.
68
89
  execute "security", "delete-keychain", @keychain if @keychain.exist?
69
90
  @keychain = nil
70
91
  end
@@ -12,7 +12,7 @@ require "tmpdir"
12
12
  require "yaml"
13
13
 
14
14
  module NativePackages
15
- VERSION = "0.5.0"
15
+ VERSION = "0.6.0"
16
16
  class Error < StandardError; end
17
17
 
18
18
  module Support
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: native-packages
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino