native-packages 0.7.0 → 0.8.1
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 +1 -1
- data/README.md +1 -1
- data/docs/_guides/building-packages.md +4 -0
- data/docs/_guides/getting-started.md +2 -2
- data/docs/_guides/github-actions.md +12 -8
- data/docs/_guides/native-recipes.md +1 -1
- data/docs/_guides/prereleases.md +1 -1
- data/docs/_guides/release-signing.md +3 -3
- data/docs/_reference/commands.md +1 -1
- data/docs/_reference/configuration.md +3 -3
- data/docs/_reference/platforms.md +41 -1
- data/docs/_reference/troubleshooting.md +2 -2
- data/docs/index.md +1 -1
- data/docs/releasing.md +14 -1
- data/examples/native-packages-all-formats.yaml +1 -1
- data/examples/native-packages.yaml +1 -1
- data/lib/native_packages/appimage.rb +166 -0
- data/lib/native_packages/build.rb +12 -1
- data/lib/native_packages/configuration.rb +8 -3
- data/lib/native_packages/inspection.rb +2 -0
- data/lib/native_packages/support.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ef21c207476e3d19df85ebdb463eb0ccfb020ab8a4fd8ead551a22c4c2d4de62
|
|
4
|
+
data.tar.gz: 1648f4ffa1698dfdccea0d3693943e696b2810c69f595bf48fa01c386346808d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f3cea3ca8d3d79956a0cca4ca779955b901795b1000a4ef57cad97abebfc32721d678df435ba8a46c2e226a210fdcb92f6c7cac586f81cc416ed5e8028ee300f
|
|
7
|
+
data.tar.gz: 2e6c2eaffc5cdab5cb3ef8ad3969b7058880cabb155f56170955f309d7b2e11701a384ac291f889cc5717d565b7073f1ec75edd51984b25554bad00e2564ec20
|
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.8.1.gem
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
See [Releasing the gem](docs/releasing.md) for publication and
|
data/README.md
CHANGED
|
@@ -48,7 +48,7 @@ record. Local builds work without a GitHub release or credentials.
|
|
|
48
48
|
|
|
49
49
|
| Platform | Outputs |
|
|
50
50
|
| --- | --- |
|
|
51
|
-
| Linux | DEB, RPM, Arch Linux packages, Alpine APK, IPK, and source RPM |
|
|
51
|
+
| Linux | DEB, RPM, AppImage, Arch Linux packages, Alpine APK, IPK, and source RPM |
|
|
52
52
|
| macOS | DMG, using your packaging script; optional Apple signing and notarization |
|
|
53
53
|
| Windows | MSIX, or Inno Setup installers using your packaging script |
|
|
54
54
|
|
|
@@ -126,6 +126,10 @@ Both targets use the shared package metadata and file mappings. A target can
|
|
|
126
126
|
have its own `nfpm` section to override them. Maps merge; lists replace the
|
|
127
127
|
whole list, so a target-specific `contents` must list all of that target's files.
|
|
128
128
|
|
|
129
|
+
Add `appimage` to a Linux target's formats for a single file that runs without
|
|
130
|
+
installation. It needs one desktop entry and its icon among the contents; see
|
|
131
|
+
[AppImage](../_reference/platforms.md#appimage).
|
|
132
|
+
|
|
129
133
|
Use a separate musl or static build for Alpine. Changing `formats` alone
|
|
130
134
|
does not change which systems a binary can run on.
|
|
131
135
|
|
|
@@ -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.8.1
|
|
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.8.1'
|
|
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.8.1
|
|
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.8.1` 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.8.1
|
|
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.8.1
|
|
76
76
|
with:
|
|
77
77
|
version: '1.2.3'
|
|
78
78
|
source-artifact: linux-binaries
|
|
@@ -125,10 +125,14 @@ GitHub repository:
|
|
|
125
125
|
| Destination | Repository variable | Secrets |
|
|
126
126
|
| --- | --- | --- |
|
|
127
127
|
| AUR group `aur` | `PUBLISH_AUR=true` | `AUR_SSH_KEY`, `AUR_KNOWN_HOSTS` |
|
|
128
|
-
| Destination or group `homebrew` | `PUBLISH_HOMEBREW=true` | `HOMEBREW_TAP_GITHUB_TOKEN` |
|
|
129
|
-
|
|
130
|
-
These steps run only with `publish: true`.
|
|
131
|
-
access to
|
|
128
|
+
| Destination or group `homebrew` | `PUBLISH_HOMEBREW=true` | `HOMEBREW_TAP_SSH_KEY` or `HOMEBREW_TAP_GITHUB_TOKEN` |
|
|
129
|
+
|
|
130
|
+
These steps run only with `publish: true`. For Homebrew, prefer a deploy key
|
|
131
|
+
with write access to the tap alone (`gh repo deploy-key add KEY.pub --repo
|
|
132
|
+
OWNER/homebrew-tap --allow-write`) stored as `HOMEBREW_TAP_SSH_KEY`: GitHub URLs in that step then go over SSH,
|
|
133
|
+
checked against GitHub's published host keys. A token
|
|
134
|
+
in `HOMEBREW_TAP_GITHUB_TOKEN` also works and needs write access to the tap.
|
|
135
|
+
The AUR key needs access to the configured repositories.
|
|
132
136
|
Use `secrets: inherit` as in the release example, or pass the named secrets
|
|
133
137
|
explicitly. If another tool already publishes a destination, keep one
|
|
134
138
|
publisher responsible for it.
|
data/docs/_guides/prereleases.md
CHANGED
|
@@ -8,7 +8,7 @@ nav_order: 9
|
|
|
8
8
|
|
|
9
9
|
Use a prerelease to let people test an upcoming version before the stable
|
|
10
10
|
release. native-packages supports alpha, beta, and release candidate packages
|
|
11
|
-
for **DEB, RPM, DMG, and Inno Setup**.
|
|
11
|
+
for **DEB, RPM, AppImage, DMG, and Inno Setup**.
|
|
12
12
|
|
|
13
13
|
## Enable preview versions
|
|
14
14
|
|
|
@@ -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.8.1` 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.8.1
|
|
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.8.1
|
|
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.8.1_ 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.8.1'`. |
|
|
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. |
|
|
@@ -97,8 +97,8 @@ letters, digits, and hyphens, starting with a letter or digit.
|
|
|
97
97
|
| `after_package` | Command arguments run on each package before its hash is recorded. |
|
|
98
98
|
| `native` | Required for DMG/Inno. Contains `command` and `output`. |
|
|
99
99
|
|
|
100
|
-
Formats are `deb`, `rpm`, `archlinux`, `apk`, `ipk`, `
|
|
101
|
-
and `inno`. See [Supported platforms](platforms.md) for their requirements.
|
|
100
|
+
Formats are `deb`, `rpm`, `archlinux`, `apk`, `ipk`, `appimage`, `srpm`,
|
|
101
|
+
`msix`, `dmg`, and `inno`. See [Supported platforms](platforms.md) for their requirements.
|
|
102
102
|
|
|
103
103
|
Windows targets select only `[msix]` or `[inno]`; macOS targets select `[dmg]`.
|
|
104
104
|
DMG and Inno each require a separate binary target. SRPM requires a separate
|
|
@@ -19,6 +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
23
|
| `srpm` | Linux source target | RPM spec and source files. | nFPM; native RPM tooling for rebuild tests. |
|
|
23
24
|
| `msix` | Windows | Windows binaries, identity, application assets, and capabilities. | nFPM; Windows tools for signing and installation tests. |
|
|
24
25
|
| `dmg` | macOS | Complete app and a DMG script. | macOS, `lipo`, and your script's tools, such as `hdiutil`. |
|
|
@@ -48,6 +49,45 @@ For IPK, check which format your device's distribution release actually uses.
|
|
|
48
49
|
The repository's IPK fixture targets OpenWrt 24.10.8; its coverage should not
|
|
49
50
|
be treated as a claim about every OpenWrt version.
|
|
50
51
|
|
|
52
|
+
## AppImage
|
|
53
|
+
|
|
54
|
+
An AppImage is one executable file that runs without installation. Add
|
|
55
|
+
`appimage` to a Linux binary target's `formats`; it uses the same `contents`
|
|
56
|
+
as the other formats, so there is nothing else to describe:
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
linux-amd64:
|
|
60
|
+
platform: linux
|
|
61
|
+
arch: amd64
|
|
62
|
+
libc: glibc
|
|
63
|
+
formats: [deb, rpm, appimage]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The package must install exactly one desktop entry under
|
|
67
|
+
`/usr/share/applications`. Its `Exec` names the program the AppImage runs
|
|
68
|
+
(found in `/usr/bin`, or an absolute path inside the package) and its `Icon`
|
|
69
|
+
names an SVG or PNG under `/usr/share/icons` or `/usr/share/pixmaps`. A
|
|
70
|
+
contents entry with `packager: appimage` is included only in the AppImage, and
|
|
71
|
+
one with another packager is left out. Entries of type `ghost` are skipped.
|
|
72
|
+
|
|
73
|
+
The output is `NAME-VERSION-ARCH.AppImage`, with AppImage's architecture names
|
|
74
|
+
(`x86_64`, `aarch64`, `i686`, `armhf` for `amd64`, `arm64`, `386`, `arm7`).
|
|
75
|
+
The file is the pinned AppImage runtime (type2-runtime 20251108, downloaded
|
|
76
|
+
once into `.cache/native-packages` and checked against a built-in SHA-256)
|
|
77
|
+
followed by a zstd SquashFS image. No `appimagetool` or nFPM is involved, the
|
|
78
|
+
build does not run the binary, and an ARM64 AppImage can be built on an x86
|
|
79
|
+
host. The same inputs and `SOURCE_DATE_EPOCH` give the same bytes.
|
|
80
|
+
|
|
81
|
+
**Libraries are not bundled.** The AppImage uses the host's libraries, as the
|
|
82
|
+
DEB and RPM do, and `build.json` lists what the binary links under
|
|
83
|
+
`required_libraries` with `"libraries": "host"`. It therefore runs where your
|
|
84
|
+
binary's glibc floor and those libraries are met, not on every Linux system.
|
|
85
|
+
Build on the oldest distribution you support, or link statically, to widen
|
|
86
|
+
that. The AppImage carries no update information and is not signed.
|
|
87
|
+
|
|
88
|
+
Running an AppImage needs FUSE on the user's system, or
|
|
89
|
+
`./App.AppImage --appimage-extract-and-run`.
|
|
90
|
+
|
|
51
91
|
## MSIX
|
|
52
92
|
|
|
53
93
|
MSIX targets use `platform: windows`, `formats: [msix]`, and PE binaries with
|
|
@@ -87,7 +127,7 @@ An Arch package file and an AUR recipe are separate outputs: the former can
|
|
|
87
127
|
be installed directly; the latter tells Arch tooling how to obtain and package
|
|
88
128
|
your app.
|
|
89
129
|
|
|
90
|
-
There is no Flatpak build/publication adapter, no NSIS/WiX adapter, and no
|
|
130
|
+
There is no Flatpak or Snap build/publication adapter, no NSIS/WiX adapter, and no
|
|
91
131
|
hosted APT/DNF index service. Keep those workflows in your app's release
|
|
92
132
|
process if you need them.
|
|
93
133
|
|
|
@@ -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.8.1
|
|
27
|
+
native-packages _0.8.1_ doctor
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
`tool.version` is an exact match. In CI, use the matching released reusable
|
data/docs/index.md
CHANGED
|
@@ -25,7 +25,7 @@ features:
|
|
|
25
25
|
details: Describe your app's files, package metadata, and build targets in a single YAML file.
|
|
26
26
|
- icon: 🐧
|
|
27
27
|
title: Linux packages
|
|
28
|
-
details: Create DEB, RPM, Arch, Alpine, and IPK packages from your compatible Linux builds.
|
|
28
|
+
details: Create DEB, RPM, AppImage, Arch, Alpine, and IPK packages from your compatible Linux builds.
|
|
29
29
|
- icon: 🖥️
|
|
30
30
|
title: Native installers
|
|
31
31
|
details: Connect your macOS DMG and Windows Inno Setup scripts, or package Windows apps as MSIX.
|
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.8.1 --target main --title 'native-packages 0.8.1' --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.
|
|
@@ -31,3 +31,16 @@ Adding `--draft` permits review before publication and does not trigger the publ
|
|
|
31
31
|
The release workflow verifies the tag, tested commit and prerelease setting, then runs the test workflow, including native package acceptance. It downloads the tested gem from the Ruby 4.0 job, publishes it to RubyGems and GitHub Packages, and attaches it to the GitHub release. It does not rebuild a different artifact after the tests.
|
|
32
32
|
|
|
33
33
|
If authentication is missing, leave the tested gem and release draft available until the secret is configured. Failed release jobs can be rerun after configuration; already-published gem versions are not overwritten. Never move an existing release tag to retry publication.
|
|
34
|
+
|
|
35
|
+
If the workflow itself needs a correction, fix it on `main` and dispatch the
|
|
36
|
+
corrected release workflow against the existing published tag:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
gh workflow run release.yml --ref main -f tag=v0.8.1
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Validation resolves the original tag to an exact commit, requires it to be on
|
|
43
|
+
`main`, and checks the existing release's version and prerelease state. Every
|
|
44
|
+
test and publication checkout uses that commit, not the newer workflow commit.
|
|
45
|
+
The gem is taken from that retry's tested Ruby 4.0 artifact. This permits a
|
|
46
|
+
workflow repair without moving a tag or publishing untested source.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NativePackages
|
|
4
|
+
# Builds a type 2 AppImage from the same contents nFPM packages: the files
|
|
5
|
+
# are laid out in an AppDir, packed with mksquashfs and appended to a pinned
|
|
6
|
+
# AppImage runtime. Libraries are not bundled; the AppImage uses the host's,
|
|
7
|
+
# as the deb and rpm packages do.
|
|
8
|
+
class AppImage
|
|
9
|
+
include Support
|
|
10
|
+
RUNTIME = "20251108"
|
|
11
|
+
RUNTIME_URL = "https://github.com/AppImage/type2-runtime/releases/download/#{RUNTIME}"
|
|
12
|
+
# nFPM architecture => [AppImage architecture, runtime SHA-256].
|
|
13
|
+
RUNTIMES = {
|
|
14
|
+
"amd64" => ["x86_64", "2fca8b443c92510f1483a883f60061ad09b46b978b2631c807cd873a47ec260d"],
|
|
15
|
+
"arm64" => ["aarch64", "00cbdfcf917cc6c0ff6d3347d59e0ca1f7f45a6df1a428a0d6d8a78664d87444"],
|
|
16
|
+
"386" => ["i686", "e72ea0b140a0a16e680713238a6f30aad278b62c4ca17919c554864124515498"],
|
|
17
|
+
"arm7" => ["armhf", "e9060d37577b8a29914ec12d8740add24e19ff29012fb1fa0f60daf62db0688d"]
|
|
18
|
+
}.freeze
|
|
19
|
+
ICON_DIRECTORIES = %w[usr/share/icons usr/share/pixmaps].freeze
|
|
20
|
+
attr_reader :root
|
|
21
|
+
|
|
22
|
+
def initialize(root)
|
|
23
|
+
@root = root
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.architecture(target)
|
|
27
|
+
RUNTIMES.fetch(target.fetch("arch")) { raise Error, "appimage does not support #{target.fetch('arch')}; use #{RUNTIMES.keys.join(', ')}" }.first
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.filename(name, version, target) = "#{name}-#{version}-#{architecture(target)}.AppImage"
|
|
31
|
+
|
|
32
|
+
def doctor(target)
|
|
33
|
+
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?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def runtime(target)
|
|
39
|
+
architecture, digest = RUNTIMES.fetch(target.fetch("arch"))
|
|
40
|
+
path = root / ".cache/native-packages/appimage-runtime" / RUNTIME / "runtime-#{architecture}"
|
|
41
|
+
download("#{RUNTIME_URL}/runtime-#{architecture}", path)
|
|
42
|
+
unless sha256(path) == digest
|
|
43
|
+
path.delete
|
|
44
|
+
raise Error, "AppImage runtime checksum mismatch: runtime-#{architecture}"
|
|
45
|
+
end
|
|
46
|
+
path
|
|
47
|
+
end
|
|
48
|
+
|
|
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)
|
|
52
|
+
output = destination / self.class.filename(name, version, target)
|
|
53
|
+
Dir.mktmpdir("native-packages-appimage-") do |directory|
|
|
54
|
+
appdir = Pathname.new(directory) / "AppDir"
|
|
55
|
+
appdir.mkpath
|
|
56
|
+
stage(package, appdir)
|
|
57
|
+
entry = integrate(appdir)
|
|
58
|
+
normalize(appdir, epoch)
|
|
59
|
+
image = Pathname.new(directory) / "filesystem.squashfs"
|
|
60
|
+
run "mksquashfs", appdir, image, "-root-owned", "-noappend", "-no-xattrs", "-no-progress", "-quiet", "-comp", "zstd",
|
|
61
|
+
env: { "SOURCE_DATE_EPOCH" => epoch.to_s }
|
|
62
|
+
File.open(output, "wb") do |file|
|
|
63
|
+
file.write(runtime(target).binread)
|
|
64
|
+
IO.copy_stream(image.to_s, file)
|
|
65
|
+
end
|
|
66
|
+
output.chmod(0o755)
|
|
67
|
+
check(output)
|
|
68
|
+
[output, entry.merge("runtime" => RUNTIME, "libraries" => "host")]
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def check(path)
|
|
73
|
+
header = path.binread(11)
|
|
74
|
+
raise Error, "not a type 2 AppImage: #{path}" unless header&.start_with?("\x7fELF".b) && header.byteslice(8, 3) == "AI\x02".b
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# nFPM overrides replace the contents list when one is supplied.
|
|
78
|
+
def contents(package)
|
|
79
|
+
package.fetch("overrides", {}).fetch("appimage", {}).fetch("contents", package.fetch("contents"))
|
|
80
|
+
.reject { |item| item["packager"] && item["packager"] != "appimage" }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def inside(appdir, destination)
|
|
84
|
+
raise Error, "appimage: content destinations must be absolute: #{destination}" unless destination.to_s.start_with?("/")
|
|
85
|
+
appdir / relative_path(destination.to_s.delete_prefix("/"))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def stage(package, appdir)
|
|
89
|
+
contents(package).each do |item|
|
|
90
|
+
type = item.fetch("type", "file")
|
|
91
|
+
next if type == "ghost"
|
|
92
|
+
target = inside(appdir, item.fetch("dst"))
|
|
93
|
+
mode = item.dig("file_info", "mode")
|
|
94
|
+
case type
|
|
95
|
+
when "dir"
|
|
96
|
+
target.mkpath
|
|
97
|
+
when "symlink"
|
|
98
|
+
target.dirname.mkpath
|
|
99
|
+
File.symlink(item.fetch("src"), target)
|
|
100
|
+
when "file", "tree", "config", "config|noreplace", "config|missingok"
|
|
101
|
+
source = File.expand_path(item.fetch("src"), root)
|
|
102
|
+
paths = package["disable_globbing"] ? [source].select { |path| File.exist?(path) } : Dir.glob(source).sort
|
|
103
|
+
raise Error, "missing package content: #{source}" if paths.empty?
|
|
104
|
+
# As nFPM does: a glob source, or a destination ending in a slash,
|
|
105
|
+
# makes the destination a directory that takes each match's name.
|
|
106
|
+
pattern = !package["disable_globbing"] && item.fetch("src").match?(/[*?\[{]/)
|
|
107
|
+
single = paths.length == 1 && File.file?(paths.first) && type != "tree" && !pattern && !item.fetch("dst").end_with?("/")
|
|
108
|
+
paths.each do |path|
|
|
109
|
+
if File.directory?(path)
|
|
110
|
+
target.mkpath
|
|
111
|
+
FileUtils.cp_r(Dir.children(path).map { |child| File.join(path, child) }, target)
|
|
112
|
+
else
|
|
113
|
+
file = single ? target : target / File.basename(path)
|
|
114
|
+
file.dirname.mkpath
|
|
115
|
+
FileUtils.cp(path, file)
|
|
116
|
+
file.chmod(mode) if mode
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
else
|
|
120
|
+
raise Error, "appimage: unsupported content type #{type}"
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# AppImage's root needs the desktop entry, its icon and AppRun.
|
|
126
|
+
def integrate(appdir)
|
|
127
|
+
entries = Pathname.glob(appdir / "usr/share/applications/*.desktop")
|
|
128
|
+
raise Error, "appimage needs exactly one desktop entry in /usr/share/applications; found #{entries.length}" unless entries.length == 1
|
|
129
|
+
desktop = entries.first
|
|
130
|
+
fields = desktop.read.lines.each_with_object({}) do |line, result|
|
|
131
|
+
key, value = line.strip.split("=", 2)
|
|
132
|
+
result[key] ||= value if value && %w[Exec Icon].include?(key)
|
|
133
|
+
end
|
|
134
|
+
command = Shellwords.split(fields.fetch("Exec") { raise Error, "appimage: #{desktop.basename} has no Exec" }).first
|
|
135
|
+
executable = command.start_with?("/") ? inside(appdir, command) : appdir / "usr/bin" / relative_path(command)
|
|
136
|
+
raise Error, "appimage: #{desktop.basename} runs #{command}, which the package does not install" unless executable.file? && executable.executable?
|
|
137
|
+
name = fields.fetch("Icon") { raise Error, "appimage: #{desktop.basename} has no Icon" }
|
|
138
|
+
raise Error, "appimage: Icon must be a name, not a path" if name.include?("/")
|
|
139
|
+
icons = ICON_DIRECTORIES.flat_map { |path| Pathname.glob(appdir / path / "**" / "#{name}.{svg,png}") }
|
|
140
|
+
# A scalable icon first, else the largest bitmap.
|
|
141
|
+
icon = icons.find { |path| path.extname == ".svg" } || icons.max_by(&:size)
|
|
142
|
+
raise Error, "appimage: the package installs no #{name}.svg or #{name}.png icon" unless icon
|
|
143
|
+
FileUtils.cp(desktop, appdir / desktop.basename)
|
|
144
|
+
FileUtils.cp(icon, appdir / icon.basename)
|
|
145
|
+
File.symlink(icon.basename.to_s, appdir / ".DirIcon")
|
|
146
|
+
File.symlink(executable.relative_path_from(appdir).to_s, appdir / "AppRun")
|
|
147
|
+
{ "desktop" => desktop.basename.to_s, "icon" => icon.basename.to_s, "executable" => executable.relative_path_from(appdir).to_s }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# The same bytes for the same inputs: fixed modes and times.
|
|
151
|
+
def normalize(appdir, epoch)
|
|
152
|
+
time = Time.at(epoch)
|
|
153
|
+
Pathname.glob(appdir / "**/*", File::FNM_DOTMATCH).each do |path|
|
|
154
|
+
next if path.basename.to_s == "."
|
|
155
|
+
if path.symlink?
|
|
156
|
+
File.lutime(time, time, path)
|
|
157
|
+
else
|
|
158
|
+
path.chmod(path.directory? || path.executable? ? 0o755 : 0o644)
|
|
159
|
+
File.utime(time, time, path)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
appdir.chmod(0o755)
|
|
163
|
+
File.utime(time, time, appdir)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -4,6 +4,7 @@ require_relative "configuration"
|
|
|
4
4
|
require_relative "inspection"
|
|
5
5
|
require_relative "native_recipe"
|
|
6
6
|
require_relative "macos_signing"
|
|
7
|
+
require_relative "appimage"
|
|
7
8
|
|
|
8
9
|
module NativePackages
|
|
9
10
|
class Build
|
|
@@ -31,7 +32,8 @@ module NativePackages
|
|
|
31
32
|
def doctor(ids: [], formats: [], release: false, defer_recipes: false)
|
|
32
33
|
configuration.validate
|
|
33
34
|
selected = configuration.select(ids: ids, formats: formats)
|
|
34
|
-
nfpm_version if selected.values.any? { |target| (target.fetch("formats")
|
|
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") }
|
|
35
37
|
selected.each do |id, target|
|
|
36
38
|
next unless target["native"]
|
|
37
39
|
metadata = configuration.target_tokens(configuration.tokens("9.8.7"), id, target, "/payload").merge("PACKAGE" => "/output/package", "FORMAT" => target.fetch("formats").first)
|
|
@@ -155,6 +157,14 @@ module NativePackages
|
|
|
155
157
|
packages = files(destination)
|
|
156
158
|
raise Error, "native recipe must create only its declared output" unless packages == [package_path]
|
|
157
159
|
native.check_output(package_path, format)
|
|
160
|
+
elsif format == "appimage"
|
|
161
|
+
package = render_tree(configuration.package(target), configuration.target_tokens(info, id, target, payload))
|
|
162
|
+
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
|
+
inspection["appimage"] = details
|
|
166
|
+
packages = files(destination)
|
|
167
|
+
raise Error, "AppImage build must create only its package" unless packages == [package_path]
|
|
158
168
|
else
|
|
159
169
|
package = render_tree(configuration.package(target), configuration.target_tokens(info, id, target, payload))
|
|
160
170
|
if number.include?("-") && (package.fetch("version_schema", "semver") != "semver" || %w[prerelease version_metadata].any? { |key| package.key?(key) && !package[key].to_s.empty? })
|
|
@@ -174,6 +184,7 @@ module NativePackages
|
|
|
174
184
|
hook_tokens = configuration.target_tokens(info, id, target, payload).merge("PACKAGE" => packages.first.to_s, "FORMAT" => format)
|
|
175
185
|
run(*render_tree(target.fetch("after_package"), hook_tokens), env: { "NATIVE_PACKAGES_TARGET" => id, "NATIVE_PACKAGES_VERSION" => number })
|
|
176
186
|
raise Error, "after_package must preserve the package path and output set" unless files(destination) == packages
|
|
187
|
+
AppImage.new(root).check(packages.first) if format == "appimage"
|
|
177
188
|
end
|
|
178
189
|
if native
|
|
179
190
|
native.check_output(packages.first, format)
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "project"
|
|
4
|
+
require_relative "appimage"
|
|
4
5
|
|
|
5
6
|
module NativePackages
|
|
6
7
|
class Configuration
|
|
7
8
|
include Support
|
|
8
9
|
NATIVE_FORMATS = %w[dmg inno].freeze
|
|
9
|
-
FORMATS = (%w[deb rpm archlinux apk ipk msix srpm] + NATIVE_FORMATS).freeze
|
|
10
|
+
FORMATS = (%w[deb rpm archlinux apk ipk msix srpm appimage] + NATIVE_FORMATS).freeze
|
|
10
11
|
NFPM_VERSION = "2.47.0"
|
|
11
12
|
NAMES = %w[native-packages.yaml native-packages.yml].freeze
|
|
12
13
|
KEYS = %w[schema tool nfpm targets release assets templates repositories revisions libraries version_file version_section].freeze
|
|
@@ -59,8 +60,8 @@ module NativePackages
|
|
|
59
60
|
def check_prerelease(version, selected, defer_recipes: false)
|
|
60
61
|
return unless version.include?("-")
|
|
61
62
|
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? } && (defer_recipes || data.fetch("templates").empty?)
|
|
63
|
-
raise Error, "prereleases support deb, rpm, dmg and inno only, without downstream recipes"
|
|
63
|
+
unless selected.values.all? { |target| (target.fetch("formats") - %w[deb rpm appimage dmg inno]).empty? } && (defer_recipes || data.fetch("templates").empty?)
|
|
64
|
+
raise Error, "prereleases support deb, rpm, appimage, dmg and inno only, without downstream recipes"
|
|
64
65
|
end
|
|
65
66
|
end
|
|
66
67
|
|
|
@@ -121,6 +122,10 @@ module NativePackages
|
|
|
121
122
|
if kind == "binary" && target["platform"] == "linux"
|
|
122
123
|
raise Error, "#{id}.libc: declare glibc, musl or static" unless %w[glibc musl static].include?(target["libc"])
|
|
123
124
|
end
|
|
125
|
+
if formats.include?("appimage")
|
|
126
|
+
raise Error, "#{id}: AppImage requires a Linux binary target" unless target["platform"] == "linux" && kind == "binary"
|
|
127
|
+
AppImage.architecture(target)
|
|
128
|
+
end
|
|
124
129
|
raise Error, "#{id}: IPK needs an explicit abi (device/distribution baseline)" if formats.include?("ipk") && target.fetch("abi", "").empty?
|
|
125
130
|
input = mapping(target.fetch("input"), "#{id}.input")
|
|
126
131
|
raise Error, "#{id}.input: declare local or release_asset" unless input["local"] || input["release_asset"]
|
|
@@ -115,6 +115,8 @@ module NativePackages
|
|
|
115
115
|
depends << (format == "deb" ? "libc6 (>= #{floor})" : "glibc >= #{floor}")
|
|
116
116
|
end
|
|
117
117
|
override["depends"] = depends.uniq
|
|
118
|
+
elsif format == "appimage"
|
|
119
|
+
# Nothing is bundled: the host supplies these, as for deb and rpm.
|
|
118
120
|
elsif !external.empty? && package.fetch("overrides", {}).fetch(format, {}).fetch("depends", package.fetch("depends", [])).empty?
|
|
119
121
|
raise Error, "#{format}: declare dependencies for #{external.join(', ')}"
|
|
120
122
|
end
|
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.
|
|
4
|
+
version: 0.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Carmine Paolino
|
|
@@ -52,6 +52,7 @@ files:
|
|
|
52
52
|
- examples/packaging/repositories.yml
|
|
53
53
|
- exe/native-packages
|
|
54
54
|
- lib/native_packages.rb
|
|
55
|
+
- lib/native_packages/appimage.rb
|
|
55
56
|
- lib/native_packages/build.rb
|
|
56
57
|
- lib/native_packages/cli.rb
|
|
57
58
|
- lib/native_packages/configuration.rb
|