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 +4 -4
- data/CONTRIBUTING.md +5 -1
- data/docs/_guides/getting-started.md +2 -2
- data/docs/_guides/github-actions.md +4 -4
- data/docs/_guides/native-recipes.md +1 -1
- data/docs/_guides/release-signing.md +3 -3
- data/docs/_reference/commands.md +1 -1
- data/docs/_reference/configuration.md +2 -1
- data/docs/_reference/platforms.md +36 -2
- data/docs/_reference/troubleshooting.md +2 -2
- data/docs/releasing.md +2 -2
- data/examples/native-packages-all-formats.yaml +1 -1
- data/examples/native-packages.yaml +1 -1
- data/lib/native_packages/appimage.rb +119 -8
- data/lib/native_packages/build.rb +15 -8
- data/lib/native_packages/configuration.rb +14 -1
- 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: 7991d9fd47977d5eda80c98ee460ef7c401b4db6eae41a74d285c58576bbb089
|
|
4
|
+
data.tar.gz: 943a2ff5686edd0cfaa7f09fd9a213d797f4a628cf5eff9331127f84840c06c8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
data/docs/_reference/commands.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
|
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.
|
|
27
|
-
native-packages _0.
|
|
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.
|
|
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.
|
|
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,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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
167
|
-
raise Error, "AppImage build must create only its package" unless
|
|
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) ==
|
|
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
|
-
|
|
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").
|
|
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,
|