native-packages 0.9.0 → 0.10.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: d89f67e56b034b1cc13d55891e7a88d32e4a607c2c79d44df51c7115f65a7a8d
4
- data.tar.gz: b1d29fef3871480632f7557f3af5de763bbd7fa67e1ef04b792eb598e3352c49
3
+ metadata.gz: 7991d9fd47977d5eda80c98ee460ef7c401b4db6eae41a74d285c58576bbb089
4
+ data.tar.gz: 943a2ff5686edd0cfaa7f09fd9a213d797f4a628cf5eff9331127f84840c06c8
5
5
  SHA512:
6
- metadata.gz: 1d2b77dd672a81d231398884f02b4077c123f7cc05d6dff7b6fbed62417f2a7854ad7557245dc1fc291f0cd78884f799f6c97b0c96de003bc1ceaba16539550e
7
- data.tar.gz: e77790947545f9086ec1ec16caf3c6226ae8bc398d860c40cced2939d1d4b65262c0d66067e9d545272ef14df3b92a7648111303e1cc8efc6a7e93439d4e8aa8
6
+ metadata.gz: 2ac16d8c7b639da77db270af088839ab3e6ecbb6b913086208f26dc95db69b843df11c887ad556c744f493b5fe8ea85660095b9a5eff8425821daabb7b850330
7
+ data.tar.gz: '094cba93dda2a8dbb0442d3b88d0efe5039ec28f715fdd85763e6dcdd56fde1b2a013813fbcf514930240dc874b31fcc562381c304c8e710a303e3bbe1d1e255'
data/CONTRIBUTING.md CHANGED
@@ -18,7 +18,7 @@ To check the packaged gem and standalone command:
18
18
 
19
19
  ```sh
20
20
  gem build native-packages.gemspec
21
- ruby test/gem_install.rb native-packages-0.9.0.gem
21
+ ruby test/gem_install.rb native-packages-0.10.0.gem
22
22
  ```
23
23
 
24
24
  See [Releasing the gem](docs/releasing.md) for publication and
@@ -49,6 +49,10 @@ path dependency. To test changes from a local theme checkout, use Bundler's
49
49
  temporary path configuration or a separate Gemfile, and keep that local setup
50
50
  out of the committed lockfile.
51
51
 
52
+ Social previews use the launch-film poster, configured under
53
+ `jekyll_vitepress.seo.image` in `docs/_config.yml`. Keep it a JPEG or PNG with
54
+ matching width, height, and alt text; the SVG logo is for the site itself.
55
+
52
56
  ### Writing a guide
53
57
 
54
58
  Put tutorials in `docs/_guides/` and reference pages in `docs/_reference/`.
@@ -17,7 +17,7 @@ own compiled executable, or build the tiny example below.
17
17
  You need Ruby 3.2 or later. Install the version used by these guides:
18
18
 
19
19
  ```sh
20
- gem install native-packages --version 0.9.0
20
+ gem install native-packages --version 0.10.0
21
21
  ```
22
22
 
23
23
  For Linux packages, install **nFPM 2.47.0**, the helper native-packages uses to
@@ -79,7 +79,7 @@ Open the generated `native-packages.yaml` and replace its contents with:
79
79
  ```yaml
80
80
  schema: 1
81
81
  tool:
82
- version: '0.9.0'
82
+ version: '0.10.0'
83
83
  nfpm: '2.47.0'
84
84
 
85
85
  nfpm:
@@ -24,7 +24,7 @@ packaging:
24
24
  needs: release
25
25
  permissions:
26
26
  contents: write
27
- uses: crmne/native-packages/.github/workflows/package.yml@v0.9.0
27
+ uses: crmne/native-packages/.github/workflows/package.yml@v0.10.0
28
28
  with:
29
29
  version: ${{ github.ref_name }}
30
30
  publish: true
@@ -39,7 +39,7 @@ The configuration must include `release.repository` and a `release_asset`
39
39
  for each selected target. See [Publishing a release](publishing.md).
40
40
 
41
41
  Use the same native-packages release for the workflow and `tool.version`.
42
- For an immutable workflow reference, replace `v0.9.0` with that release's
42
+ For an immutable workflow reference, replace `v0.10.0` with that release's
43
43
  full commit SHA.
44
44
 
45
45
  ## Build without publishing
@@ -57,7 +57,7 @@ permissions:
57
57
  contents: read
58
58
  jobs:
59
59
  packaging:
60
- uses: crmne/native-packages/.github/workflows/package.yml@v0.9.0
60
+ uses: crmne/native-packages/.github/workflows/package.yml@v0.10.0
61
61
  ```
62
62
 
63
63
  Validation does not compile the app or check that future release inputs exist.
@@ -72,7 +72,7 @@ packaging:
72
72
  needs: build
73
73
  permissions:
74
74
  contents: write
75
- uses: crmne/native-packages/.github/workflows/package.yml@v0.9.0
75
+ uses: crmne/native-packages/.github/workflows/package.yml@v0.10.0
76
76
  with:
77
77
  version: '1.2.3'
78
78
  source-artifact: linux-binaries
@@ -31,7 +31,7 @@ Here is a complete configuration for an ARM64 Mac app and an x86-64 Windows app:
31
31
  ```yaml
32
32
  schema: 1
33
33
  tool:
34
- version: '0.9.0'
34
+ version: '0.10.0'
35
35
  nfpm: '2.47.0'
36
36
  nfpm:
37
37
  name: hello
@@ -67,7 +67,7 @@ Package publication's separate `packaging-checksums.txt` is also unchanged.
67
67
 
68
68
  ## Attest in the application's build job
69
69
 
70
- The examples use `v0.9.0` for readability. For release workflows, replace it
70
+ The examples use `v0.10.0` for readability. For release workflows, replace it
71
71
  with that release's reviewed full commit SHA, not a mutable branch.
72
72
  After producing the final package, before uploading it, add this step to the
73
73
  job that built it:
@@ -79,7 +79,7 @@ permissions:
79
79
  attestations: write
80
80
  steps:
81
81
  # Existing checkout, build, platform signing and packaging steps go here.
82
- - uses: crmne/native-packages/.github/actions/attest@v0.9.0
82
+ - uses: crmne/native-packages/.github/actions/attest@v0.10.0
83
83
  with:
84
84
  subject-path: dist/*.tar.gz
85
85
  ```
@@ -108,7 +108,7 @@ and install Ruby 3.2 or later with Ed25519-capable OpenSSL before the signing
108
108
  step. Then call:
109
109
 
110
110
  ```yaml
111
- - uses: crmne/native-packages/.github/actions/sign-release@v0.9.0
111
+ - uses: crmne/native-packages/.github/actions/sign-release@v0.10.0
112
112
  with:
113
113
  directory: dist/release
114
114
  public-key: assets/update-public-key.hex
@@ -193,7 +193,7 @@ If several gem versions are installed, RubyGems lets you select the one required
193
193
  by the configuration:
194
194
 
195
195
  ```sh
196
- native-packages _0.9.0_ build --version 1.2.3
196
+ native-packages _0.10.0_ build --version 1.2.3
197
197
  ```
198
198
 
199
199
  Bundler also works if you prefer to manage the gem in a Gemfile.
@@ -28,7 +28,7 @@ expressions. Use the replacement values listed below.
28
28
  | Field | Purpose |
29
29
  | --- | --- |
30
30
  | `schema` | Required. Set to `1`. |
31
- | `tool.version` | Required. Exact native-packages version, such as `'0.9.0'`. |
31
+ | `tool.version` | Required. Exact native-packages version, such as `'0.10.0'`. |
32
32
  | `tool.nfpm` | Required. Set to `'2.47.0'`, including in native-only configurations. |
33
33
  | `nfpm` | Shared package metadata and installed-file mappings, or a relative YAML filename. Requires `name`. |
34
34
  | `targets` | Required. Named build targets; use `{}` for recipe-only projects. |
@@ -96,6 +96,7 @@ letters, digits, and hyphens, starting with a letter or digit.
96
96
  | `before_build` | Command arguments run before copying a local input. |
97
97
  | `after_package` | Command arguments run on each package before its hash is recorded. |
98
98
  | `native` | Required for DMG, MSI and Inno. Contains `command` and `output`. |
99
+ | `appimage` | Optional for AppImage targets: `update_information`, embedded with a `.zsync` file beside the AppImage. See [AppImage](platforms.md#update-information). |
99
100
 
100
101
  Formats are `deb`, `rpm`, `archlinux`, `apk`, `ipk`, `appimage`, `srpm`,
101
102
  `msix`, `dmg`, `msi`, and `inno`. See [Supported platforms](platforms.md) for their requirements.
@@ -19,7 +19,7 @@ libraries.
19
19
  | `archlinux` | Linux | Arch-compatible files and explicit dependencies. | nFPM, `readelf` for binaries. |
20
20
  | `apk` | Linux | Suitable Alpine files, usually musl or static binaries, and dependencies. | nFPM, `readelf` for binaries. |
21
21
  | `ipk` | Linux | Files for the device/distribution baseline, dependencies, and an `abi` label. | nFPM, `readelf` for binaries. |
22
- | `appimage` | Linux | The same files as the DEB/RPM, including one desktop entry and its icon. | `mksquashfs` (squashfs-tools), `curl`, `readelf`. |
22
+ | `appimage` | Linux | The same files as the DEB/RPM, including one desktop entry and its icon. | `mksquashfs` (squashfs-tools), `curl`, `readelf`; `zsyncmake` (zsync) with update information. |
23
23
  | `srpm` | Linux source target | RPM spec and source files. | nFPM; native RPM tooling for rebuild tests. |
24
24
  | `msix` | Windows | Windows binaries, identity, application assets, and capabilities. | nFPM; Windows tools for signing and installation tests. |
25
25
  | `dmg` | macOS | Complete app and a DMG script. | macOS, `lipo`, and your script's tools, such as `hdiutil`. |
@@ -84,7 +84,41 @@ DEB and RPM do, and `build.json` lists what the binary links under
84
84
  `required_libraries` with `"libraries": "host"`. It therefore runs where your
85
85
  binary's glibc floor and those libraries are met, not on every Linux system.
86
86
  Build on the oldest distribution you support, or link statically, to widen
87
- that. The AppImage carries no update information and is not signed.
87
+ that. The AppImage is not signed.
88
+
89
+ ### Update information
90
+
91
+ AppImageUpdate, AppImageLauncher, AM, Gear Lever and other updaters find a
92
+ newer AppImage through the update information embedded in it, and download
93
+ only the blocks that changed through a `.zsync` file published beside it.
94
+ Give the target an `appimage.update_information`:
95
+
96
+ ```yaml
97
+ linux-amd64:
98
+ platform: linux
99
+ arch: amd64
100
+ libc: glibc
101
+ formats: [appimage]
102
+ appimage:
103
+ update_information: gh-releases-zsync|owner|app|latest|app-*-x86_64.AppImage.zsync
104
+ ```
105
+
106
+ The string goes into the runtime's `.upd_info` section, and the build reads
107
+ it back from the finished AppImage. `zsyncmake` then writes
108
+ `NAME-VERSION-ARCH.AppImage.zsync` beside it, with the AppImage's file name
109
+ as its relative URL, and the build checks the file's name, length and SHA-1
110
+ against the AppImage. `build.json` records the `.zsync` file with its
111
+ package, and `publish --to github` attaches both. The same inputs give the
112
+ same bytes for both files.
113
+
114
+ Two forms are accepted: `gh-releases-zsync|OWNER|REPOSITORY|TAG|PATTERN`,
115
+ where `TAG` is usually `latest` (the newest stable release), and
116
+ `zsync|https://URL.zsync`. A GitHub string must name `release.repository`,
117
+ and its pattern must match the target's `.zsync` file name, so validation
118
+ fails before a release could ship an AppImage whose updaters look for a file
119
+ that is not there. Each architecture needs its own target with its own
120
+ pattern. An `after_package` hook must leave the AppImage as it is, or the
121
+ `.zsync` file no longer describes it and the build fails.
88
122
 
89
123
  Running an AppImage needs FUSE on the user's system, or
90
124
  `./App.AppImage --appimage-extract-and-run`.
@@ -23,8 +23,8 @@ working on with `doctor --target ID` and `build --target ID --dry-run`.
23
23
  Install and select the version named in the error:
24
24
 
25
25
  ```sh
26
- gem install native-packages --version 0.9.0
27
- native-packages _0.9.0_ doctor
26
+ gem install native-packages --version 0.10.0
27
+ native-packages _0.10.0_ doctor
28
28
  ```
29
29
 
30
30
  `tool.version` is an exact match. In CI, use the matching released reusable
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.9.0 --target main --title 'native-packages 0.9.0' --notes-file release-notes.md
26
+ gh release create v0.10.0 --target main --title 'native-packages 0.10.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.
@@ -36,7 +36,7 @@ If the workflow itself needs a correction, fix it on `main` and dispatch the
36
36
  corrected release workflow against the existing published tag:
37
37
 
38
38
  ```sh
39
- gh workflow run release.yml --ref main -f tag=v0.9.0
39
+ gh workflow run release.yml --ref main -f tag=v0.10.0
40
40
  ```
41
41
 
42
42
  Validation resolves the original tag to an exact commit, requires it to be on
@@ -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.9.0'
4
+ version: '0.10.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.9.0'
3
+ version: '0.10.0'
4
4
  nfpm: '2.47.0'
5
5
  nfpm:
6
6
  name: example-app
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "digest"
4
+
3
5
  module NativePackages
4
6
  # Builds a type 2 AppImage from the same contents nFPM packages: the files
5
7
  # are laid out in an AppDir, packed with mksquashfs and appended to a pinned
@@ -17,6 +19,13 @@ module NativePackages
17
19
  "arm7" => ["armhf", "e9060d37577b8a29914ec12d8740add24e19ff29012fb1fa0f60daf62db0688d"]
18
20
  }.freeze
19
21
  ICON_DIRECTORIES = %w[usr/share/icons usr/share/pixmaps].freeze
22
+ # The runtime's ELF section that AppImageUpdate and the other updaters
23
+ # read: a NUL-terminated string in a fixed 1024 bytes.
24
+ UPDATE_SECTION = ".upd_info"
25
+ # AppImage's update information formats, without the `bintray-zsync`
26
+ # and `pling-v1-zsync` services that no longer exist.
27
+ GITHUB_UPDATE = /\Agh-releases-zsync\|([A-Za-z0-9_.-]+)\|([A-Za-z0-9_.-]+)\|([^|\s]+)\|([^|\s]+\.zsync)\z/
28
+ URL_UPDATE = %r{\Azsync\|https://[^|\s]+\.zsync\z}
20
29
  attr_reader :root
21
30
 
22
31
  def initialize(root)
@@ -29,10 +38,81 @@ module NativePackages
29
38
 
30
39
  def self.filename(name, version, target) = "#{name}-#{version}-#{architecture(target)}.AppImage"
31
40
 
32
- def doctor(target)
41
+ # The target's update information, checked against the AppImage it
42
+ # describes: a GitHub string must name the release repository, and its
43
+ # file pattern must match this target's `.zsync` file, so a release
44
+ # never ships an AppImage whose updaters look for a file that is not
45
+ # there. Returns nil when the target has none.
46
+ def self.update_information(target, name:, version:, repository:)
47
+ settings = target["appimage"]
48
+ return nil if settings.nil?
49
+ raise Error, "appimage: expected a mapping" unless settings.is_a?(Hash)
50
+ unknown = settings.keys - %w[update_information]
51
+ raise Error, "appimage: unknown fields #{unknown.join(', ')}" unless unknown.empty?
52
+ value = settings["update_information"]
53
+ raise Error, "appimage.update_information: expected a string" unless value.is_a?(String) && !value.empty?
54
+ raise Error, "appimage.update_information: at most 1023 bytes" if value.bytesize > 1023
55
+ if (match = GITHUB_UPDATE.match(value))
56
+ owner, project, _tag, pattern = match.captures
57
+ unless "#{owner}/#{project}".casecmp?(repository.to_s)
58
+ raise Error, "appimage.update_information names #{owner}/#{project}, but release.repository is #{repository.inspect}"
59
+ end
60
+ zsync = "#{filename(name, version, target)}.zsync"
61
+ unless File.fnmatch?(pattern, zsync, File::FNM_PATHNAME)
62
+ raise Error, "appimage.update_information: #{pattern} does not match #{zsync}"
63
+ end
64
+ elsif !URL_UPDATE.match?(value)
65
+ raise Error, "appimage.update_information: use gh-releases-zsync|OWNER|REPOSITORY|TAG|FILE.zsync or zsync|https://URL.zsync"
66
+ end
67
+ value
68
+ end
69
+
70
+ # The file offset and size of the ELF section `name` in `bytes`, or nil.
71
+ # Every AppImage runtime is a little-endian ELF file, 32 or 64 bits.
72
+ def self.section(bytes, name)
73
+ raise Error, "AppImage runtime is not a little-endian ELF file" unless bytes.start_with?("\x7fELF".b) && bytes.getbyte(5) == 1
74
+ wide = { 1 => false, 2 => true }.fetch(bytes.getbyte(4)) { raise Error, "AppImage runtime has an unknown ELF class" }
75
+ offset = wide ? bytes.byteslice(0x28, 8).unpack1("Q<") : bytes.byteslice(0x20, 4).unpack1("V")
76
+ size, count, names = bytes.byteslice(wide ? 0x3A : 0x2E, 6).unpack("v3")
77
+ header = lambda do |index|
78
+ entry = bytes.byteslice(offset + (index * size), size)
79
+ raise Error, "AppImage runtime has a truncated section table" unless entry && entry.bytesize == size
80
+ wide ? [entry.unpack1("V"), *entry.byteslice(0x18, 16).unpack("Q<2")] : [entry.unpack1("V"), *entry.byteslice(0x10, 8).unpack("V2")]
81
+ end
82
+ _, strings, = header.call(names)
83
+ count.times do |index|
84
+ label, start, length = header.call(index)
85
+ found = bytes.byteslice(strings + label, name.bytesize + 1)
86
+ return [start, length] if found == "#{name}\0".b
87
+ end
88
+ nil
89
+ end
90
+
91
+ # `runtime` with `information` in its update section.
92
+ def self.embed(runtime, information)
93
+ start, length = section(runtime, UPDATE_SECTION) || raise(Error, "AppImage runtime has no #{UPDATE_SECTION} section")
94
+ raise Error, "update information exceeds the runtime's #{length}-byte #{UPDATE_SECTION} section" if information.bytesize >= length
95
+ raise Error, "AppImage runtime's #{UPDATE_SECTION} section is outside the file" if start + length > runtime.bytesize
96
+ runtime = runtime.b
97
+ runtime[start, length] = information.b + ("\0".b * (length - information.bytesize))
98
+ runtime
99
+ end
100
+
101
+ # The update information an AppImage carries, or nil when it has none.
102
+ def self.read_update_information(path)
103
+ bytes = File.binread(path)
104
+ start, length = section(bytes, UPDATE_SECTION)
105
+ return nil unless start
106
+ value = bytes.byteslice(start, length)[/\A[^\0]*/n]
107
+ value.empty? ? nil : value
108
+ end
109
+
110
+ def doctor(target, update: false)
33
111
  self.class.architecture(target)
34
- missing = %w[mksquashfs curl].reject { |tool| available?(tool) }
35
- raise Error, "install required AppImage tools: #{missing.join(', ')} (mksquashfs is in squashfs-tools)" unless missing.empty?
112
+ tools = %w[mksquashfs curl]
113
+ tools << "zsyncmake" if update
114
+ missing = tools.reject { |tool| available?(tool) }
115
+ raise Error, "install required AppImage tools: #{missing.join(', ')} (mksquashfs is in squashfs-tools, zsyncmake in zsync)" unless missing.empty?
36
116
  end
37
117
 
38
118
  def runtime(target)
@@ -46,9 +126,11 @@ module NativePackages
46
126
  path
47
127
  end
48
128
 
49
- # Returns the AppImage's path and what was put at the AppDir's root.
50
- def build(package, target, destination, name:, version:, epoch:)
51
- doctor(target)
129
+ # Returns the AppImage's path, what was put at the AppDir's root, and
130
+ # the `.zsync` file beside it when the AppImage carries update
131
+ # information (nil otherwise).
132
+ def build(package, target, destination, name:, version:, epoch:, update: nil)
133
+ doctor(target, update: !update.nil?)
52
134
  output = destination / self.class.filename(name, version, target)
53
135
  Dir.mktmpdir("native-packages-appimage-") do |directory|
54
136
  appdir = Pathname.new(directory) / "AppDir"
@@ -59,14 +141,43 @@ module NativePackages
59
141
  image = Pathname.new(directory) / "filesystem.squashfs"
60
142
  run "mksquashfs", appdir, image, "-root-owned", "-noappend", "-no-xattrs", "-no-progress", "-quiet", "-comp", "zstd",
61
143
  env: { "SOURCE_DATE_EPOCH" => epoch.to_s }
144
+ bytes = runtime(target).binread
145
+ bytes = self.class.embed(bytes, update) if update
62
146
  File.open(output, "wb") do |file|
63
- file.write(runtime(target).binread)
147
+ file.write(bytes)
64
148
  IO.copy_stream(image.to_s, file)
65
149
  end
66
150
  output.chmod(0o755)
67
151
  check(output)
68
- [output, entry.merge("runtime" => RUNTIME, "libraries" => "host")]
152
+ details = entry.merge("runtime" => RUNTIME, "libraries" => "host")
153
+ return [output, details, nil] unless update
154
+ raise Error, "AppImage lost its update information: #{output}" unless self.class.read_update_information(output) == update
155
+ [output, details.merge("update_information" => update), zsync(output, epoch)]
156
+ end
157
+ end
158
+
159
+ # Writes `<AppImage>.zsync` beside the AppImage, for updaters that fetch
160
+ # only the blocks that changed. Its URL is the AppImage's file name,
161
+ # relative to the `.zsync` file, so it works from wherever both are
162
+ # published. The AppImage's time is the build's, which zsyncmake records.
163
+ def zsync(path, epoch)
164
+ File.utime(epoch, epoch, path)
165
+ output = Pathname.new("#{path}.zsync")
166
+ run "zsyncmake", "-u", path.basename.to_s, "-o", output, path, chdir: path.dirname
167
+ check_zsync(output, path)
168
+ output
169
+ end
170
+
171
+ # Fails unless `zsync` describes the AppImage at `path` as it is now.
172
+ def check_zsync(zsync, path)
173
+ header = zsync.binread.split("\n\n".b, 2).first.to_s.force_encoding(Encoding::UTF_8).lines.to_h do |line|
174
+ key, value = line.chomp.split(": ", 2)
175
+ [key, value]
69
176
  end
177
+ expected = { "Filename" => path.basename.to_s, "URL" => path.basename.to_s, "Length" => path.size.to_s,
178
+ "SHA-1" => Digest::SHA1.file(path).hexdigest }
179
+ wrong = expected.reject { |key, value| header[key] == value }.keys
180
+ raise Error, "#{zsync.basename} does not describe #{path.basename}: #{wrong.join(', ')}" unless wrong.empty?
70
181
  end
71
182
 
72
183
  def check(path)
@@ -33,7 +33,9 @@ module NativePackages
33
33
  configuration.validate
34
34
  selected = configuration.select(ids: ids, formats: formats)
35
35
  nfpm_version if selected.values.any? { |target| !(target.fetch("formats") - Configuration::NATIVE_FORMATS - ["appimage"]).empty? }
36
- selected.each_value { |target| AppImage.new(root).doctor(target) if target.fetch("formats").include?("appimage") }
36
+ selected.each_value do |target|
37
+ AppImage.new(root).doctor(target, update: !target["appimage"].nil?) if target.fetch("formats").include?("appimage")
38
+ end
37
39
  selected.each do |id, target|
38
40
  next unless target["native"]
39
41
  metadata = configuration.target_tokens(configuration.tokens("9.8.7"), id, target, "/payload").merge("PACKAGE" => "/output/package", "FORMAT" => target.fetch("formats").first)
@@ -160,11 +162,12 @@ module NativePackages
160
162
  elsif format == "appimage"
161
163
  package = render_tree(configuration.package(target), configuration.target_tokens(info, id, target, payload))
162
164
  inspection = Inspection.new(root, configuration.data.fetch("libraries")).check(package, target, format)
163
- package_path, details = AppImage.new(root).build(package, target, destination, name: configuration.name, version: number,
164
- epoch: info.fetch("SOURCE_DATE_EPOCH"))
165
+ package_path, details, zsync = AppImage.new(root).build(package, target, destination, name: configuration.name, version: number,
166
+ epoch: info.fetch("SOURCE_DATE_EPOCH"), update: configuration.update_information(target, number))
165
167
  inspection["appimage"] = details
166
- packages = files(destination)
167
- raise Error, "AppImage build must create only its package" unless packages == [package_path]
168
+ outputs = [package_path, zsync].compact.sort
169
+ raise Error, "AppImage build must create only its package and .zsync file" unless files(destination) == outputs
170
+ packages = [package_path]
168
171
  else
169
172
  package = render_tree(configuration.package(target), configuration.target_tokens(info, id, target, payload))
170
173
  if number.include?("-") && (package.fetch("version_schema", "semver") != "semver" || %w[prerelease version_metadata].any? { |key| package.key?(key) && !package[key].to_s.empty? })
@@ -180,19 +183,23 @@ module NativePackages
180
183
  packages = files(destination)
181
184
  raise Error, "nFPM did not create exactly one #{format} package" unless packages.length == 1
182
185
  end
186
+ outputs ||= packages
183
187
  if target["after_package"]
184
188
  hook_tokens = configuration.target_tokens(info, id, target, payload).merge("PACKAGE" => packages.first.to_s, "FORMAT" => format)
185
189
  run(*render_tree(target.fetch("after_package"), hook_tokens), env: { "NATIVE_PACKAGES_TARGET" => id, "NATIVE_PACKAGES_VERSION" => number })
186
- raise Error, "after_package must preserve the package path and output set" unless files(destination) == packages
190
+ raise Error, "after_package must preserve the package path and output set" unless files(destination) == outputs
187
191
  AppImage.new(root).check(packages.first) if format == "appimage"
192
+ AppImage.new(root).check_zsync(zsync, packages.first) if format == "appimage" && zsync
188
193
  end
189
194
  if native
190
195
  native.check_output(packages.first, format)
191
196
  raise Error, "native recipes/signing must not change their input payload" unless native.digest(payload) == payload_digest
192
197
  end
193
198
  inspection["apple"] = signing.notarize(packages.first) if signing
194
- records << { "target" => id, "format" => format, "path" => packages.first.relative_path_from(staging).to_s,
199
+ record = { "target" => id, "format" => format, "path" => packages.first.relative_path_from(staging).to_s,
195
200
  "sha256" => sha256(packages.first), "validation" => inspection }
201
+ record["zsync"] = zsync.relative_path_from(staging).to_s if format == "appimage" && zsync
202
+ records << record
196
203
  end
197
204
  end
198
205
  end
@@ -381,7 +388,7 @@ module NativePackages
381
388
  end
382
389
  destinations.each do |destination|
383
390
  if destination == "github"
384
- paths = manifest.fetch("packages").map { |entry| output / entry.fetch("path") }
391
+ paths = manifest.fetch("packages").flat_map { |entry| [entry.fetch("path"), entry["zsync"]].compact.map { |path| output / path } }
385
392
  paths += manifest.fetch("files").keys.grep(/-packaging\.tar\.xz\z/).map { |path| output / path }
386
393
  names = paths.map { |path| path.basename.to_s }
387
394
  raise Error, "duplicate release asset names; give variants distinct nFPM names/releases" unless names.uniq == names
@@ -86,7 +86,7 @@ module NativePackages
86
86
  metadata = tokens("9.8.7", epoch: 1_767_225_600)
87
87
  targets.each do |id, target|
88
88
  raise Error, "invalid target name: #{id}" unless /\A[a-z0-9][a-z0-9-]*\z/.match?(id)
89
- unknown = target.keys - %w[platform arch libc abi kind formats input nfpm before_build after_package compiler_target native]
89
+ unknown = target.keys - %w[platform arch libc abi kind formats input nfpm before_build after_package compiler_target native appimage]
90
90
  raise Error, "targets.#{id}: unknown fields #{unknown.join(', ')}" unless unknown.empty?
91
91
  formats = target.fetch("formats")
92
92
  unless formats.is_a?(Array) && !formats.empty? && formats.uniq == formats && (formats - FORMATS).empty?
@@ -125,6 +125,13 @@ module NativePackages
125
125
  if formats.include?("appimage")
126
126
  raise Error, "#{id}: AppImage requires a Linux binary target" unless target["platform"] == "linux" && kind == "binary"
127
127
  AppImage.architecture(target)
128
+ elsif target.key?("appimage")
129
+ raise Error, "#{id}.appimage: only for targets that build an AppImage"
130
+ end
131
+ begin
132
+ update_information(target, "9.8.7")
133
+ rescue Error => error
134
+ raise Error, "#{id}.#{error.message}"
128
135
  end
129
136
  raise Error, "#{id}: IPK needs an explicit abi (device/distribution baseline)" if formats.include?("ipk") && target.fetch("abi", "").empty?
130
137
  input = mapping(target.fetch("input"), "#{id}.input")
@@ -162,6 +169,12 @@ module NativePackages
162
169
  true
163
170
  end
164
171
 
172
+ # The AppImage update information for `target`, checked against the
173
+ # AppImage `version` builds, or nil.
174
+ def update_information(target, version)
175
+ AppImage.update_information(target, name: name, version: version, repository: data.fetch("release")["repository"])
176
+ end
177
+
165
178
  def tokens(version, epoch: nil)
166
179
  epoch ||= Integer(ENV.fetch("SOURCE_DATE_EPOCH", Time.now.to_i.to_s))
167
180
  result = { "NAME" => name, "VERSION" => version, "TAG" => "v#{version}", "DATE" => Time.at(epoch).utc.iso8601,
@@ -12,7 +12,7 @@ require "tmpdir"
12
12
  require "yaml"
13
13
 
14
14
  module NativePackages
15
- VERSION = "0.9.0"
15
+ VERSION = "0.10.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.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino