native-packages 0.8.1 → 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/README.md +5 -3
- data/docs/_guides/getting-started.md +2 -2
- data/docs/_guides/github-actions.md +5 -5
- data/docs/_guides/native-recipes.md +53 -9
- data/docs/_guides/publishing.md +1 -1
- data/docs/_guides/release-signing.md +3 -3
- data/docs/_reference/commands.md +1 -1
- data/docs/_reference/configuration.md +7 -6
- data/docs/_reference/platforms.md +40 -5
- data/docs/_reference/troubleshooting.md +3 -3
- data/docs/cli-design.md +1 -1
- data/docs/index.md +45 -1
- 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 +21 -8
- data/lib/native_packages/native_recipe.rb +3 -0
- 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/`.
|
data/README.md
CHANGED
|
@@ -13,6 +13,8 @@ Homebrew casks.
|
|
|
13
13
|
|
|
14
14
|
You bring the compiled app. native-packages handles packaging and release updates.
|
|
15
15
|
|
|
16
|
+
https://github.com/user-attachments/assets/e3157514-570d-47b3-a2e9-aca70042a138
|
|
17
|
+
|
|
16
18
|
[Documentation](https://native-packages.dev/)
|
|
17
19
|
|
|
18
20
|
## Get started
|
|
@@ -50,10 +52,10 @@ record. Local builds work without a GitHub release or credentials.
|
|
|
50
52
|
| --- | --- |
|
|
51
53
|
| Linux | DEB, RPM, AppImage, Arch Linux packages, Alpine APK, IPK, and source RPM |
|
|
52
54
|
| macOS | DMG, using your packaging script; optional Apple signing and notarization |
|
|
53
|
-
| Windows | MSIX, or Inno Setup installers using your packaging script |
|
|
55
|
+
| Windows | MSIX, or MSI and Inno Setup installers using your packaging script |
|
|
54
56
|
|
|
55
|
-
Each target needs files built for that platform. DMG
|
|
56
|
-
|
|
57
|
+
Each target needs files built for that platform. DMG builds run on macOS;
|
|
58
|
+
MSI and Inno builds run on Windows. See [supported platforms](docs/_reference/platforms.md)
|
|
57
59
|
for prerequisites and distribution recipes.
|
|
58
60
|
|
|
59
61
|
## Guides
|
|
@@ -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
|
|
@@ -101,7 +101,7 @@ The workflow passes the same target selection to build and publication.
|
|
|
101
101
|
It requires every format declared for those targets. An empty selection
|
|
102
102
|
means all configured targets.
|
|
103
103
|
|
|
104
|
-
Build DMG and Inno targets in your own macOS and Windows jobs with the CLI.
|
|
104
|
+
Build DMG, MSI and Inno targets in your own macOS and Windows jobs with the CLI.
|
|
105
105
|
See [Building across platforms](multi-platform.md) for combining their outputs
|
|
106
106
|
and generating recipes after all packages are available.
|
|
107
107
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: macOS and Windows installers
|
|
3
|
-
description: Connect your DMG or Inno Setup script to the same build and publication process as your Linux packages.
|
|
3
|
+
description: Connect your DMG, MSI or Inno Setup script to the same build and publication process as your Linux packages.
|
|
4
4
|
nav_order: 5
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# macOS and Windows installers
|
|
8
8
|
|
|
9
|
-
This guide shows how to connect an existing DMG or Inno Setup script to
|
|
9
|
+
This guide shows how to connect an existing DMG, MSI or Inno Setup script to
|
|
10
10
|
native-packages. You supply a built app and a packaging script. The build
|
|
11
11
|
command checks the app, runs your script, and records the finished installer's
|
|
12
12
|
checksum.
|
|
13
13
|
|
|
14
|
-
Build **DMGs on macOS** and **Inno installers on Windows**. These targets use
|
|
14
|
+
Build **DMGs on macOS** and **MSI or Inno installers on Windows**. These targets use
|
|
15
15
|
your native tools and do not need an nFPM executable.
|
|
16
16
|
|
|
17
17
|
## Prepare your app
|
|
@@ -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.
|
|
34
|
+
version: '0.10.0'
|
|
35
35
|
nfpm: '2.47.0'
|
|
36
36
|
nfpm:
|
|
37
37
|
name: hello
|
|
@@ -54,15 +54,18 @@ targets:
|
|
|
54
54
|
windows-amd64:
|
|
55
55
|
platform: windows
|
|
56
56
|
arch: amd64
|
|
57
|
-
formats: [
|
|
57
|
+
formats: [msi]
|
|
58
58
|
input:
|
|
59
59
|
kind: directory
|
|
60
60
|
local: dist/windows
|
|
61
61
|
native:
|
|
62
|
-
command: [ruby, packaging/
|
|
63
|
-
output: 'hello-@TAG@-windows-
|
|
62
|
+
command: [ruby, packaging/msi.rb, '@PAYLOAD@', '@PACKAGE@', '@VERSION@']
|
|
63
|
+
output: 'hello-@TAG@-windows-amd64.msi'
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
For an Inno Setup installer instead, use `formats: [inno]` and an `.exe`
|
|
67
|
+
output. A Windows target builds one installer format.
|
|
68
|
+
|
|
66
69
|
The `nfpm` section still holds shared package metadata, and `tool.nfpm` keeps
|
|
67
70
|
mixed-platform configurations consistent. Native targets use `native.command`
|
|
68
71
|
to create their output.
|
|
@@ -110,6 +113,47 @@ abort "DMG verification failed" unless system("hdiutil", "verify", output)
|
|
|
110
113
|
This places the app and an Applications shortcut inside a compressed disk
|
|
111
114
|
image. Adapt the volume name and app name to your project.
|
|
112
115
|
|
|
116
|
+
### A Windows Installer (MSI) project
|
|
117
|
+
|
|
118
|
+
Keep your installer's WiX source in the repository. This minimal
|
|
119
|
+
`packaging/hello.wxs` (WiX Toolset 5) installs everything in the payload
|
|
120
|
+
for all users and replaces earlier versions on upgrade:
|
|
121
|
+
|
|
122
|
+
```xml
|
|
123
|
+
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
|
124
|
+
<Package Name="Hello" Manufacturer="Your Name" Version="$(Version)"
|
|
125
|
+
UpgradeCode="{00000000-0000-0000-0000-000000000000}" Scope="perMachine" Compressed="yes">
|
|
126
|
+
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
|
|
127
|
+
<MediaTemplate EmbedCab="yes" />
|
|
128
|
+
<StandardDirectory Id="ProgramFiles64Folder">
|
|
129
|
+
<Directory Id="INSTALLFOLDER" Name="Hello" />
|
|
130
|
+
</StandardDirectory>
|
|
131
|
+
<Files Include="$(SourceDir)\**" Directory="INSTALLFOLDER" />
|
|
132
|
+
</Package>
|
|
133
|
+
</Wix>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Generate your own `UpgradeCode` once and never change it: it is how Windows
|
|
137
|
+
recognizes an upgrade. Then have `packaging/msi.rb` build it:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
payload, output, version = ARGV
|
|
141
|
+
# Windows Installer versions are numeric: 1.2.3-beta.1 installs as 1.2.3.
|
|
142
|
+
numeric = version.split("-").first
|
|
143
|
+
success = system("wix", "build", File.join(__dir__, "hello.wxs"), "-arch", "x64",
|
|
144
|
+
"-d", "Version=#{numeric}", "-d", "SourceDir=#{payload}", "-pdbtype", "none", "-o", output)
|
|
145
|
+
abort "MSI build failed" unless success
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`-pdbtype none` stops WiX writing a `.wixpdb` debug file next to the MSI;
|
|
149
|
+
the recipe may create only the declared output.
|
|
150
|
+
|
|
151
|
+
Install WiX with `dotnet tool install --global wix --version 5.0.2`. Windows
|
|
152
|
+
compares only the first three version fields, each at most 255.255.65535, so
|
|
153
|
+
release candidates of one version upgrade each other in place. Per-user
|
|
154
|
+
installs, shortcuts, file associations, and closing a running app are part of
|
|
155
|
+
your WiX project; see the [WiX documentation](https://docs.firegiant.com/wix/).
|
|
156
|
+
|
|
113
157
|
### An Inno Setup script
|
|
114
158
|
|
|
115
159
|
Keep your app's `.iss` recipe in the repository. Have `packaging/setup.rb`
|
|
@@ -143,11 +187,11 @@ native-packages build --version 1.2.3 --target windows-amd64 --output dist/windo
|
|
|
143
187
|
```
|
|
144
188
|
|
|
145
189
|
`doctor` checks the host and the command's executable. Your script checks its
|
|
146
|
-
additional tools, such as the Inno compiler. `validate` and `build --dry-run`
|
|
190
|
+
additional tools, such as WiX or the Inno compiler. `validate` and `build --dry-run`
|
|
147
191
|
do not execute it.
|
|
148
192
|
|
|
149
193
|
The build checks Mach-O or PE architecture, runs the script, and checks that
|
|
150
|
-
it produced exactly the declared DMG or EXE. Test installation, startup,
|
|
194
|
+
it produced exactly the declared DMG, MSI or EXE. Test installation, startup,
|
|
151
195
|
upgrades, and removal separately for your app.
|
|
152
196
|
|
|
153
197
|
## Preserve the prepared input
|
data/docs/_guides/publishing.md
CHANGED
|
@@ -71,7 +71,7 @@ The command downloads the inputs, verifies their published hashes, and builds
|
|
|
71
71
|
packages in `dist/packages/1.2.3/`. It skips `before_build` hooks. Every binary
|
|
72
72
|
release input needs a matching checksum.
|
|
73
73
|
|
|
74
|
-
DMG and Inno targets use prepared local directories. Download and unpack their
|
|
74
|
+
DMG, MSI and Inno targets use prepared local directories. Download and unpack their
|
|
75
75
|
inputs in the native build job, then use `--version`. See
|
|
76
76
|
[macOS and Windows installers](native-recipes.md).
|
|
77
77
|
|
|
@@ -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. |
|
|
@@ -48,7 +48,7 @@ command needed to install and select the configured gem.
|
|
|
48
48
|
|
|
49
49
|
`nfpm` is named after the helper that writes Linux packages and MSIX files.
|
|
50
50
|
It holds the package's metadata, files, dependencies, and package-manager
|
|
51
|
-
settings. Native DMG and Inno targets also read the shared metadata.
|
|
51
|
+
settings. Native DMG, MSI and Inno targets also read the shared metadata.
|
|
52
52
|
|
|
53
53
|
Every target needs nonempty `maintainer`, `description`, and `license` values.
|
|
54
54
|
Non-native targets need a `contents` list. The shared package name may contain
|
|
@@ -95,13 +95,14 @@ letters, digits, and hyphens, starting with a letter or digit.
|
|
|
95
95
|
| `compiler_target` | Optional value for `@TARGET@`; otherwise it is the target ID. |
|
|
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
|
-
| `native` | Required for DMG
|
|
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
|
-
`msix`, `dmg`, and `inno`. See [Supported platforms](platforms.md) for their requirements.
|
|
102
|
+
`msix`, `dmg`, `msi`, and `inno`. See [Supported platforms](platforms.md) for their requirements.
|
|
102
103
|
|
|
103
|
-
Windows targets select only `[msix]` or `[inno]`; macOS targets select `[dmg]`.
|
|
104
|
-
DMG and Inno each require a separate binary target. SRPM requires a separate
|
|
104
|
+
Windows targets select only `[msix]`, `[msi]` or `[inno]`; macOS targets select `[dmg]`.
|
|
105
|
+
DMG, MSI and Inno each require a separate binary target. SRPM requires a separate
|
|
105
106
|
`kind: source` target with only `[srpm]`.
|
|
106
107
|
|
|
107
108
|
### Inputs
|
|
@@ -19,14 +19,15 @@ 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`. |
|
|
26
|
+
| `msi` | Windows | Complete app and a Windows Installer recipe/wrapper, such as a WiX project. | Windows and your installed MSI toolset, such as WiX. |
|
|
26
27
|
| `inno` | Windows | Complete app and an Inno Setup recipe/wrapper. | Windows and your installed Inno compiler. |
|
|
27
28
|
|
|
28
29
|
The supported nFPM version is **2.47.0**. It writes package files from your
|
|
29
|
-
prepared contents. Native DMG and Inno targets call your scripts instead.
|
|
30
|
+
prepared contents. Native DMG, MSI and Inno targets call your scripts instead.
|
|
30
31
|
Archive inputs also require `bsdtar`; release downloads require `curl`.
|
|
31
32
|
|
|
32
33
|
## Linux compatibility
|
|
@@ -83,7 +84,41 @@ DEB and RPM do, and `build.json` lists what the binary links under
|
|
|
83
84
|
`required_libraries` with `"libraries": "host"`. It therefore runs where your
|
|
84
85
|
binary's glibc floor and those libraries are met, not on every Linux system.
|
|
85
86
|
Build on the oldest distribution you support, or link statically, to widen
|
|
86
|
-
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.
|
|
87
122
|
|
|
88
123
|
Running an AppImage needs FUSE on the user's system, or
|
|
89
124
|
`./App.AppImage --appimage-extract-and-run`.
|
|
@@ -105,7 +140,7 @@ project's Windows acceptance tests do. Those tests found that Windows rejected
|
|
|
105
140
|
nFPM 2.47.0's built-in signature with `0x80096010`. An MSIX file built
|
|
106
141
|
successfully is not yet evidence that Windows will accept and install it.
|
|
107
142
|
|
|
108
|
-
## DMG and Inno Setup
|
|
143
|
+
## DMG, MSI and Inno Setup
|
|
109
144
|
|
|
110
145
|
These formats run on their native host and each require a separate binary
|
|
111
146
|
target with a local directory input. macOS inspection supports `amd64`,
|
|
@@ -127,7 +162,7 @@ An Arch package file and an AUR recipe are separate outputs: the former can
|
|
|
127
162
|
be installed directly; the latter tells Arch tooling how to obtain and package
|
|
128
163
|
your app.
|
|
129
164
|
|
|
130
|
-
There is no Flatpak or Snap build/publication adapter, no NSIS
|
|
165
|
+
There is no Flatpak or Snap build/publication adapter, no NSIS adapter, and no
|
|
131
166
|
hosted APT/DNF index service. Keep those workflows in your app's release
|
|
132
167
|
process if you need them.
|
|
133
168
|
|
|
@@ -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
|
|
@@ -36,7 +36,7 @@ Linux and MSIX packaging require nFPM 2.47.0. Linux binary inspection needs
|
|
|
36
36
|
`readelf`; archive extraction needs `bsdtar`. Install these using the
|
|
37
37
|
[setup instructions](../_guides/getting-started.md#1-install-the-tools).
|
|
38
38
|
|
|
39
|
-
DMG and Inno targets do not require an nFPM executable. If you only intend to
|
|
39
|
+
DMG, MSI and Inno targets do not require an nFPM executable. If you only intend to
|
|
40
40
|
build a native target, select it explicitly so `doctor` does not check Linux
|
|
41
41
|
targets from the same configuration.
|
|
42
42
|
|
data/docs/cli-design.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The owner approved compatible shared support for RekordFlash's native alpha
|
|
4
4
|
packaging on 2026-09-13. Version 0.3's [native recipe and prerelease policy](native-recipes.md)
|
|
5
5
|
extends the original stable-only policy explicitly; published 0.2 consumers stay
|
|
6
|
-
pinned. DMG/Inno adapters coordinate existing app scripts and retain their
|
|
6
|
+
pinned. DMG/MSI/Inno adapters coordinate existing app scripts and retain their
|
|
7
7
|
signing/version decisions, without adding a compiler or plugin framework.
|
|
8
8
|
|
|
9
9
|
Status: accepted, 2026-09-13. Publishing was subsequently aligned with RubyLLM: GitHub release publication triggers the workflow, using `RUBYGEMS_AUTH_TOKEN`. The original Trusted Publishing proposal below is superseded by that decision. The v0.2 implementation follows this design. See the current [configuration reference](configuration.md) and [release setup](releasing.md) for the implemented interface and publication requirements. This document records the original design; it does not describe v0.1.0.
|
data/docs/index.md
CHANGED
|
@@ -28,7 +28,7 @@ features:
|
|
|
28
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
|
-
details: Connect your macOS DMG and Windows Inno Setup
|
|
31
|
+
details: Connect your macOS DMG script and your Windows MSI or Inno Setup script, or package Windows apps as MSIX.
|
|
32
32
|
- icon: 🚀
|
|
33
33
|
title: Release automation
|
|
34
34
|
details: Build from local files or release assets. Add packaging to GitHub Actions and upload the finished packages.
|
|
@@ -39,3 +39,47 @@ features:
|
|
|
39
39
|
title: Verified builds
|
|
40
40
|
details: Check binary architecture and library requirements. Record file hashes and verify them before publishing.
|
|
41
41
|
---
|
|
42
|
+
|
|
43
|
+
<video class="hero-film" controls muted loop playsinline preload="metadata" poster="{{ '/assets/images/launch-film-poster.jpg' | relative_url }}" aria-label="native-packages in under a minute: ZapFast, then seventeen packaging files become one configuration" hidden>
|
|
44
|
+
<source src="{{ '/assets/videos/launch-film.mp4' | relative_url }}" type="video/mp4">
|
|
45
|
+
</video>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
/* The theme sizes its hero for a square logo; the film is 16:9. */
|
|
49
|
+
.VPHero .hero-film {
|
|
50
|
+
display: block;
|
|
51
|
+
width: 100%;
|
|
52
|
+
max-width: 560px;
|
|
53
|
+
height: auto;
|
|
54
|
+
aspect-ratio: 16 / 9;
|
|
55
|
+
margin: 0 auto;
|
|
56
|
+
border-radius: 12px;
|
|
57
|
+
background: #0d0e1a;
|
|
58
|
+
}
|
|
59
|
+
.VPHero .image-container:has(.hero-film) {
|
|
60
|
+
width: 100%;
|
|
61
|
+
height: auto;
|
|
62
|
+
padding: 0 24px;
|
|
63
|
+
transform: none;
|
|
64
|
+
}
|
|
65
|
+
@media (max-width: 959px) {
|
|
66
|
+
.VPHero .image:has(.hero-film) {
|
|
67
|
+
margin: 0 0 32px;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
</style>
|
|
71
|
+
|
|
72
|
+
<script>
|
|
73
|
+
// The theme's hero takes a picture; the film takes its place where
|
|
74
|
+
// scripts run, and the logo stays where they do not.
|
|
75
|
+
(function () {
|
|
76
|
+
var film = document.querySelector(".hero-film");
|
|
77
|
+
var slot = document.querySelector(".VPHero .image-container");
|
|
78
|
+
if (!film || !slot) return;
|
|
79
|
+
slot.replaceChildren(film);
|
|
80
|
+
film.hidden = false;
|
|
81
|
+
if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
82
|
+
film.play().catch(function () {});
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
85
|
+
</script>
|
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
|
|
@@ -6,7 +6,7 @@ require_relative "appimage"
|
|
|
6
6
|
module NativePackages
|
|
7
7
|
class Configuration
|
|
8
8
|
include Support
|
|
9
|
-
NATIVE_FORMATS = %w[dmg inno].freeze
|
|
9
|
+
NATIVE_FORMATS = %w[dmg inno msi].freeze
|
|
10
10
|
FORMATS = (%w[deb rpm archlinux apk ipk msix srpm appimage] + NATIVE_FORMATS).freeze
|
|
11
11
|
NFPM_VERSION = "2.47.0"
|
|
12
12
|
NAMES = %w[native-packages.yaml native-packages.yml].freeze
|
|
@@ -60,8 +60,8 @@ module NativePackages
|
|
|
60
60
|
def check_prerelease(version, selected, defer_recipes: false)
|
|
61
61
|
return unless version.include?("-")
|
|
62
62
|
raise Error, "prerelease builds require release.prereleases: true" unless data.fetch("release")["prereleases"] == true
|
|
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
|
|
63
|
+
unless selected.values.all? { |target| (target.fetch("formats") - %w[deb rpm appimage dmg inno msi]).empty? } && (defer_recipes || data.fetch("templates").empty?)
|
|
64
|
+
raise Error, "prereleases support deb, rpm, appimage, dmg, inno and msi only, without downstream recipes"
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
|
|
@@ -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?
|
|
@@ -97,11 +97,11 @@ module NativePackages
|
|
|
97
97
|
kind = target.fetch("kind", "binary")
|
|
98
98
|
raise Error, "targets.#{id}.kind: use binary, data or source" unless %w[binary data source].include?(kind)
|
|
99
99
|
raise Error, "#{id}: MSIX requires a Windows target with only msix format" if formats.include?("msix") && (target["platform"] != "windows" || formats != ["msix"])
|
|
100
|
-
raise Error, "#{id}: Windows targets require msix or
|
|
100
|
+
raise Error, "#{id}: Windows targets require msix, inno or msi" if target["platform"] == "windows" && ![["msix"], ["inno"], ["msi"]].include?(formats)
|
|
101
101
|
raise Error, "#{id}: macOS targets require dmg" if target["platform"] == "macos" && formats != ["dmg"]
|
|
102
102
|
native = !(formats & NATIVE_FORMATS).empty?
|
|
103
103
|
if native
|
|
104
|
-
expected = { "dmg" => "macos", "inno" => "windows" }[formats.first]
|
|
104
|
+
expected = { "dmg" => "macos", "inno" => "windows", "msi" => "windows" }[formats.first]
|
|
105
105
|
raise Error, "#{id}: native formats require a separate binary target on their native platform" unless formats.length == 1 && expected == target["platform"] && kind == "binary"
|
|
106
106
|
recipe = mapping(target.fetch("native"), "#{id}.native")
|
|
107
107
|
raise Error, "#{id}.native: expected command and output" unless recipe.keys.sort == %w[command output]
|
|
@@ -110,13 +110,13 @@ module NativePackages
|
|
|
110
110
|
raise Error, "#{id}.native.command: use command arguments including @PACKAGE@"
|
|
111
111
|
end
|
|
112
112
|
filename = render(recipe.fetch("output"), target_tokens(metadata, id, target, "/payload"))
|
|
113
|
-
extension =
|
|
113
|
+
extension = { "dmg" => ".dmg", "inno" => ".exe", "msi" => ".msi" }.fetch(formats.first)
|
|
114
114
|
unless /\A[a-zA-Z0-9][a-zA-Z0-9._+-]*\z/.match?(filename) && filename.end_with?(extension)
|
|
115
115
|
raise Error, "#{id}.native.output: expected a safe #{extension} filename"
|
|
116
116
|
end
|
|
117
117
|
render_tree(command, target_tokens(metadata, id, target, "/payload").merge("PACKAGE" => "/output/#{filename}", "FORMAT" => formats.first))
|
|
118
118
|
elsif target.key?("native")
|
|
119
|
-
raise Error, "#{id}: native commands require a dmg or
|
|
119
|
+
raise Error, "#{id}: native commands require a dmg, inno or msi target"
|
|
120
120
|
end
|
|
121
121
|
raise Error, "#{id}: SRPM requires a separate source target" if (formats.include?("srpm") && (kind != "source" || formats != ["srpm"])) || (kind == "source" && formats != ["srpm"])
|
|
122
122
|
if kind == "binary" && target["platform"] == "linux"
|
|
@@ -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,
|
|
@@ -76,6 +76,9 @@ module NativePackages
|
|
|
76
76
|
next false if file.size < 512
|
|
77
77
|
file.seek(-512, IO::SEEK_END)
|
|
78
78
|
file.read(4) == "koly"
|
|
79
|
+
elsif format == "msi"
|
|
80
|
+
# Windows Installer databases are OLE compound files.
|
|
81
|
+
file.size >= 512 && file.read(8) == "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1".b
|
|
79
82
|
else
|
|
80
83
|
next false unless file.size >= 64 && file.read(2) == "MZ"
|
|
81
84
|
file.seek(0x3c)
|