crosspack 0.4.0 → 0.5.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/CHANGELOG.md +24 -0
- data/README.md +121 -119
- data/exe/crosspack +39 -53
- data/lib/crossbuild/build_manifest.rb +11 -25
- data/lib/crossbuild/dep_installer.rb +4 -3
- data/lib/crossbuild/matrix.rb +1 -1
- data/lib/crossbuild/platform.rb +3 -3
- data/lib/crossbuild/runner.rb +1 -1
- data/lib/crossbuild/version.rb +1 -1
- data/lib/crossbuild/version_scheme.rb +3 -2
- data/lib/crossbuild.rb +8 -3
- data/lib/crosspack/builders/pkgbuild.rb +1 -1
- data/lib/crosspack/builders/rpm.rb +2 -1
- data/lib/crosspack/config.rb +164 -0
- data/lib/crosspack/manifest.rb +16 -30
- data/lib/crosspack/matrix.rb +2 -2
- data/lib/crosspack/package_manifest.rb +16 -26
- data/lib/crosspack/packer.rb +12 -11
- data/lib/crosspack/resolver.rb +2 -2
- data/lib/crosspack/version.rb +1 -1
- data/lib/crosspack.rb +3 -2
- 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: 4f1b1bcc611377577384101685763d88198c0b27dc9323a8370fda526b7440d8
|
|
4
|
+
data.tar.gz: b3c121e02cfebbcfe8112bd4597f2009b35aecee53019a0c51aa2bb791f51007
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2f69bb73a73517c21764f8e9bd7c31436f2d6a8615b437ea7912c06e4790a353c8952b7d4834c5a883352773fedb00ee73786689a3864a51e95f6835760aa003
|
|
7
|
+
data.tar.gz: df5e860abc414316410211b72fdf22d82f18a95cc211fa37d9316503625ae5fbffee86fa6a66f959a15f3a3a4cc44661d6dac05b002c2c1b6e438923dbc71d41
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.5.0] - 2026-09-13
|
|
9
|
+
|
|
10
|
+
### Breaking
|
|
11
|
+
- One config file: `build.yaml`, `package.yaml` and `deps.yaml` are merged
|
|
12
|
+
into a single `crosspack.yml` with `build:` / `package:` / `deps:`
|
|
13
|
+
sections and a shared top-level `name:` (plus optional `version:`, the
|
|
14
|
+
build version scheme). The three old files are no longer read — move each
|
|
15
|
+
file's body under its section, hoist `name`/`version` to the top. No
|
|
16
|
+
migration path is provided (0.4.0 was never published beyond local gem
|
|
17
|
+
builds).
|
|
18
|
+
- CLI: `-f/--file` now points at crosspack.yml (default `./crosspack.yml`);
|
|
19
|
+
the `--package` and `--build` flags are gone.
|
|
20
|
+
- Library API: `Crosspack.pack` takes `config:` (a Config or a path to
|
|
21
|
+
crosspack.yml); `Crossbuild.build` accepts a Config, a path or a ready
|
|
22
|
+
BuildManifest; the manifest classes no longer read files —
|
|
23
|
+
`Manifest.load` / `PackageManifest.load` / `BuildManifest.load` are gone,
|
|
24
|
+
`Crosspack::Config.load` is the single loader.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- `Crosspack::Config`: top-level validation (name required once, version
|
|
28
|
+
scheme, unknown keys) with the shared name/version injected into the
|
|
29
|
+
build section and the name into the package section; each section keeps
|
|
30
|
+
its own schema and path-pointing error messages.
|
|
31
|
+
|
|
8
32
|
## [0.4.0] - 2026-09-13
|
|
9
33
|
|
|
10
34
|
### Breaking
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# crosspack
|
|
2
2
|
|
|
3
3
|
Cross-platform build **and** pack toolchain for Wails-style desktop apps,
|
|
4
|
-
shipped as one gem
|
|
4
|
+
shipped as one gem, one command, one config file and a staged pipeline:
|
|
5
5
|
|
|
6
6
|
```
|
|
7
7
|
crosspack deps <target> # verify/install the build host's dependencies
|
|
@@ -22,49 +22,101 @@ crosspack build ubuntu-24.04
|
|
|
22
22
|
crosspack pack ubuntu-24.04
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
##
|
|
25
|
+
## crosspack.yml — one file, three sections
|
|
26
|
+
|
|
27
|
+
The top level carries the shared facts — `name` (required once) and the
|
|
28
|
+
optional `version` scheme — and three sections: `build:` (what and where to
|
|
29
|
+
build), `package:` (what and how to pack) and `deps:` (runtime dependencies
|
|
30
|
+
resolved to concrete packages per distro). The shared name/version feed the
|
|
31
|
+
build section, the name feeds the package section; each section keeps its
|
|
32
|
+
own schema.
|
|
26
33
|
|
|
27
34
|
```yaml
|
|
28
35
|
name: myapp
|
|
29
36
|
version: calver # calver | git-tag | env:VAR | any literal string
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
37
|
+
|
|
38
|
+
build:
|
|
39
|
+
output: builds # default builds; mirrors package `sources`
|
|
40
|
+
|
|
41
|
+
deps: # build dependencies, per-host verify/install
|
|
42
|
+
imagemagick:
|
|
43
|
+
hosts: # first selector matching this host wins
|
|
44
|
+
ubuntu:
|
|
45
|
+
verify: dpkg -s imagemagick
|
|
46
|
+
install: sudo apt-get install -y imagemagick
|
|
47
|
+
darwin:
|
|
48
|
+
verify: magick -version
|
|
49
|
+
install: brew install imagemagick
|
|
50
|
+
windows:
|
|
51
|
+
verify: where magick
|
|
52
|
+
install: winget install -e --id ImageMagick.ImageMagick
|
|
53
|
+
"*": # any command — package manager or a project script
|
|
54
|
+
install: ./scripts/install-imagemagick.sh
|
|
55
|
+
|
|
56
|
+
matrix:
|
|
57
|
+
- build: linux/amd64 # os/arch this entry builds on (arch may be "any")
|
|
58
|
+
env: # extra env for the steps of this entry
|
|
59
|
+
CGO_ENABLED: "1"
|
|
60
|
+
steps: # shell commands, run from the project root
|
|
61
|
+
- wails build -platform linux/amd64 -ldflags "-X myapp/internal/app.appVersion={{version}}"
|
|
62
|
+
- cargo build --release -p helper --locked
|
|
63
|
+
artifacts:
|
|
64
|
+
from: build/bin # what got built
|
|
65
|
+
include: [myapp, helper, "*.onnx", "*.so*"] # default: ["*"]
|
|
66
|
+
to: [ubuntu-22.04, ubuntu-24.04, debian-12, debian-13]
|
|
67
|
+
mode: symlink # symlink | copy, default symlink
|
|
68
|
+
|
|
69
|
+
- id: windows # defaults to the build platform string
|
|
70
|
+
build: windows/amd64
|
|
71
|
+
steps:
|
|
72
|
+
- wails build -platform windows/amd64 -ldflags "-X ...={{version}}"
|
|
73
|
+
artifacts:
|
|
74
|
+
from: build/bin
|
|
75
|
+
include: [myapp.exe]
|
|
76
|
+
to: [windows-11.0]
|
|
77
|
+
|
|
78
|
+
package:
|
|
79
|
+
maintainer: Your Name <you@example.com>
|
|
80
|
+
summary: MyApp - cross-platform desktop application # optional
|
|
81
|
+
description: |-
|
|
82
|
+
MyApp - cross-platform desktop application.
|
|
83
|
+
Bundles helper binaries and model assets alongside the GUI...
|
|
84
|
+
license: Proprietary # optional, default Proprietary
|
|
85
|
+
section: utils # optional, deb only
|
|
86
|
+
|
|
87
|
+
sources: builds # compiled artifacts tree written by the build
|
|
88
|
+
# stage: builds/<family>/<version>/<arch>/ —
|
|
89
|
+
# pack without a matching build dir fails honestly
|
|
90
|
+
prefix: usr/local # install prefix inside the package
|
|
91
|
+
lib_dir: lib/myapp # optional, default lib/<name>
|
|
92
|
+
|
|
93
|
+
payload: # files from sources -> <prefix>/<lib_dir>/
|
|
94
|
+
- myapp
|
|
95
|
+
- helper
|
|
96
|
+
- model.onnx
|
|
97
|
+
|
|
98
|
+
executables: [myapp, helper] # chmod 755, must be in payload
|
|
99
|
+
|
|
100
|
+
links: # symlinks relative to prefix
|
|
101
|
+
bin/myapp: ../lib/myapp/myapp
|
|
102
|
+
|
|
103
|
+
desktop: # optional, .desktop generated
|
|
104
|
+
name: MyApp
|
|
105
|
+
comment: Cross-platform desktop application
|
|
106
|
+
categories: Utility;Audio
|
|
107
|
+
|
|
108
|
+
icon: build/appicon.png # optional, -> <prefix>/share/pixmaps/<name>.png
|
|
109
|
+
|
|
110
|
+
deps:
|
|
111
|
+
# canonical dependency names resolved to concrete packages per distro
|
|
112
|
+
# family/version; an optional `version:` declares the deps schema version
|
|
113
|
+
webkit2gtk:
|
|
114
|
+
targets:
|
|
115
|
+
debian:
|
|
116
|
+
"12": [libwebkit2gtk-4.1-0, libwebkit2gtk-4.0-37] # deb: a | b
|
|
117
|
+
fedora: { "*": [webkit2gtk4.1] }
|
|
118
|
+
macos: system # OS component
|
|
119
|
+
windows: system # preinstalled (WebView2)
|
|
68
120
|
```
|
|
69
121
|
|
|
70
122
|
Placeholders `{{version}}`, `{{name}}`, `{{platform}}`, `{{os}}`, `{{arch}}`
|
|
@@ -80,18 +132,19 @@ builds/ubuntu/24.04/amd64/myapp -> build/bin/myapp (symlink)
|
|
|
80
132
|
builds/windows/11.0/x86_64/myapp.exe
|
|
81
133
|
```
|
|
82
134
|
|
|
83
|
-
### Build dependencies (deps
|
|
135
|
+
### Build dependencies (build.deps)
|
|
84
136
|
|
|
85
|
-
`deps
|
|
86
|
-
what a *package* needs at runtime;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
`/etc/os-release` (`ubuntu`),
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
137
|
+
`build.deps` is the host axis to the top-level `deps:` section's target
|
|
138
|
+
axis: the `deps:` section declares what a *package* needs at runtime;
|
|
139
|
+
`build.deps` declares what the *build host* must have. Checks differ per
|
|
140
|
+
system, so every host rule carries its own commands: `verify` (presence
|
|
141
|
+
probe; exit 0 = present; without it the name is looked up in PATH) and
|
|
142
|
+
`install` (how to install it; may be omitted for verify-only rules).
|
|
143
|
+
Selectors are tried in order: distro id from `/etc/os-release` (`ubuntu`),
|
|
144
|
+
then `ID_LIKE` tokens (`debian`), then the os (`linux`, `darwin`,
|
|
145
|
+
`windows`), then `"*"`. The deps stage runs verify → install → re-verify
|
|
146
|
+
for every dependency; a dependency that is still missing fails the stage.
|
|
147
|
+
Commands support the `{{name}}`/`{{os}}`/... placeholders.
|
|
95
148
|
|
|
96
149
|
### Version schemes
|
|
97
150
|
|
|
@@ -102,64 +155,9 @@ fails the stage. Commands support the `{{name}}`/`{{os}}`/... placeholders.
|
|
|
102
155
|
| `env:VAR` | taken from `VAR`; build fails loudly when unset |
|
|
103
156
|
| other string | used verbatim, e.g. `version: 1.2.3` |
|
|
104
157
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
**`package.yaml`**:
|
|
108
|
-
|
|
109
|
-
```yaml
|
|
110
|
-
name: myapp
|
|
111
|
-
maintainer: Your Name <you@example.com>
|
|
112
|
-
summary: MyApp - cross-platform desktop application # optional
|
|
113
|
-
description: |-
|
|
114
|
-
MyApp - cross-platform desktop application.
|
|
115
|
-
Bundles helper binaries and model assets alongside the GUI...
|
|
116
|
-
license: Proprietary # optional, default Proprietary
|
|
117
|
-
section: utils # optional, deb only
|
|
118
|
-
|
|
119
|
-
sources: builds # compiled artifacts tree written by the build
|
|
120
|
-
# stage: builds/<family>/<version>/<arch>/ —
|
|
121
|
-
# pack without a matching build dir fails honestly
|
|
122
|
-
prefix: usr/local # install prefix inside the package
|
|
123
|
-
lib_dir: lib/myapp # optional, default lib/<name>
|
|
124
|
-
|
|
125
|
-
payload: # files from sources -> <prefix>/<lib_dir>/
|
|
126
|
-
- myapp
|
|
127
|
-
- helper
|
|
128
|
-
- model.onnx
|
|
129
|
-
|
|
130
|
-
executables: [myapp, helper] # chmod 755, must be in payload
|
|
131
|
-
|
|
132
|
-
links: # symlinks relative to prefix
|
|
133
|
-
bin/myapp: ../lib/myapp/myapp
|
|
134
|
-
|
|
135
|
-
desktop: # optional, .desktop generated
|
|
136
|
-
name: MyApp
|
|
137
|
-
comment: Cross-platform desktop application
|
|
138
|
-
categories: Utility;Audio
|
|
139
|
-
|
|
140
|
-
icon: build/appicon.png # optional, -> <prefix>/share/pixmaps/<name>.png
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
**`deps.yaml`** — canonical dependency names resolved to concrete packages
|
|
144
|
-
per distro family/version. An optional top-level `version:` declares the
|
|
145
|
-
schema version; a deps.yaml written for a newer crosspack is rejected with
|
|
146
|
-
an upgrade hint:
|
|
147
|
-
|
|
148
|
-
```yaml
|
|
149
|
-
version: 1 # optional, currently 1
|
|
150
|
-
|
|
151
|
-
webkit2gtk:
|
|
152
|
-
targets:
|
|
153
|
-
debian:
|
|
154
|
-
"12": [libwebkit2gtk-4.1-0, libwebkit2gtk-4.0-37] # deb: a | b
|
|
155
|
-
fedora: { "*": [webkit2gtk4.1] }
|
|
156
|
-
macos: system # OS component
|
|
157
|
-
windows: system # preinstalled (WebView2)
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
All three files are validated against embedded schemas with actionable,
|
|
158
|
+
The whole file is validated against embedded schemas with actionable,
|
|
161
159
|
path-pointing error messages; every stage refuses to run on an invalid
|
|
162
|
-
|
|
160
|
+
config.
|
|
163
161
|
|
|
164
162
|
## CLI
|
|
165
163
|
|
|
@@ -170,11 +168,14 @@ crosspack build <target>|--all [--no-deps] [--version X]
|
|
|
170
168
|
crosspack pack <target> [--version X] # version defaults to the build stamp
|
|
171
169
|
|
|
172
170
|
# inspection
|
|
173
|
-
crosspack validate #
|
|
171
|
+
crosspack validate # crosspack.yml: top level + all sections
|
|
174
172
|
crosspack resolve <target> # Depends line for the target
|
|
175
173
|
crosspack matrix # deps x target + build matrix tables
|
|
176
174
|
crosspack version # computed build version
|
|
177
175
|
crosspack targets # what is in builds/ ready to pack
|
|
176
|
+
|
|
177
|
+
# options
|
|
178
|
+
-f, --file PATH # path to crosspack.yml (default: ./crosspack.yml)
|
|
178
179
|
```
|
|
179
180
|
|
|
180
181
|
`pack` picks the builder from the target and writes to
|
|
@@ -191,28 +192,29 @@ crosspacks/arch/x86_64/PKGBUILD
|
|
|
191
192
|
```ruby
|
|
192
193
|
require 'crosspack' # pulls in Crossbuild too
|
|
193
194
|
|
|
194
|
-
Crossbuild.build('
|
|
195
|
-
Crossbuild.build('
|
|
195
|
+
Crossbuild.build('crosspack.yml', root: Dir.pwd) # all host entries
|
|
196
|
+
Crossbuild.build('crosspack.yml', target: 'ubuntu-24.04') # the stages' build, library-level
|
|
196
197
|
|
|
197
198
|
Crosspack.pack(
|
|
198
|
-
|
|
199
|
+
config: 'crosspack.yml',
|
|
199
200
|
target: Crosspack::Target.parse('debian-12', arch: 'amd64'),
|
|
200
201
|
version: nil, # nil -> the version stamped by the build stage
|
|
201
202
|
root: Dir.pwd, output_base: 'crosspacks'
|
|
202
203
|
)
|
|
203
204
|
```
|
|
204
205
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
`
|
|
206
|
+
`Crosspack::Config.load('crosspack.yml')` parses and validates the file
|
|
207
|
+
once and exposes the sections as manifests. Lower-level pieces are public
|
|
208
|
+
too: on the build side `Crossbuild::BuildManifest`, `VersionScheme`,
|
|
209
|
+
`Runner`, `Distributor`, `Matrix`, `Builder`, `DepInstaller`, `Platform`;
|
|
210
|
+
on the pack side `Crosspack::PackageManifest`, `Manifest`, `Resolver`,
|
|
211
|
+
`Matrix`, `Target`, `Builds`, `Builders::*`.
|
|
210
212
|
|
|
211
213
|
Deb needs `dpkg-deb` (present on any Debian/Ubuntu), rpm needs
|
|
212
214
|
`rpmbuild` (`sudo apt install rpm`), PKGBUILD generation needs nothing.
|
|
213
215
|
The WiX builder generates `.wxs` sources (run the WiX Toolset yourself to
|
|
214
|
-
get an MSI), the macOS builder stages the `.app` bundle (signing,
|
|
215
|
-
and .dmg creation are out of scope). Requires Ruby >= 3.2.
|
|
216
|
+
get an MSI), the macOS builder stages the `.app` bundle (signing,
|
|
217
|
+
notarization and .dmg creation are out of scope). Requires Ruby >= 3.2.
|
|
216
218
|
|
|
217
219
|
## Tests
|
|
218
220
|
|
data/exe/crosspack
CHANGED
|
@@ -16,7 +16,7 @@ def abort_with(message)
|
|
|
16
16
|
exit 1
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
options = { file:
|
|
19
|
+
options = { file: Crosspack::Config::DEFAULT_PATH, arch: 'amd64',
|
|
20
20
|
format: nil, version: nil, root: Dir.pwd, output_base: nil,
|
|
21
21
|
check_only: false, no_deps: false, all: false }
|
|
22
22
|
parser = OptionParser.new do |opts|
|
|
@@ -27,9 +27,7 @@ parser = OptionParser.new do |opts|
|
|
|
27
27
|
" pack <target> [--version X] package builds/<target> (version from the build stamp)\n" \
|
|
28
28
|
'Commands: validate, resolve, matrix, version, targets'
|
|
29
29
|
|
|
30
|
-
opts.on('-f', '--file PATH', 'path to
|
|
31
|
-
opts.on('--package PATH', 'path to package.yaml (default: ./package.yaml)') { |v| options[:package] = v }
|
|
32
|
-
opts.on('--build PATH', 'path to build.yaml (default: ./build.yaml)') { |v| options[:build] = v }
|
|
30
|
+
opts.on('-f', '--file PATH', 'path to crosspack.yml (default: ./crosspack.yml)') { |v| options[:file] = v }
|
|
33
31
|
opts.on('--arch ARCH', 'architecture: amd64/x86_64, arm64/aarch64 (default: amd64)') { |v| options[:arch] = v }
|
|
34
32
|
opts.on('--format FORMAT', 'output format for resolve: deb, rpm, pkgbuild (default: derived from the target)') { |v| options[:format] = v.to_sym }
|
|
35
33
|
opts.on('--version VERSION', 'override the version for build/pack') { |v| options[:version] = v }
|
|
@@ -53,17 +51,22 @@ command = ARGV.shift
|
|
|
53
51
|
abort_with 'No command given. See: crosspack --help' if command.nil?
|
|
54
52
|
|
|
55
53
|
# Positional argument: a target for the stage commands (deps/build/pack,
|
|
56
|
-
# resolve), a
|
|
54
|
+
# resolve), a crosspack.yml path for validate.
|
|
57
55
|
positional = ARGV.shift if ARGV.first && !ARGV.first.start_with?('-')
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
options[:file] = positional if command == 'validate' && positional
|
|
58
|
+
|
|
59
|
+
begin
|
|
60
|
+
config = Crosspack::Config.load(File.expand_path(options[:file], options[:root]))
|
|
61
|
+
rescue Crosspack::InvalidManifestError => e
|
|
62
|
+
abort_with e.message
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# validate prints the full report itself and exits by status; everything
|
|
66
|
+
# else stops on a broken top level first.
|
|
67
|
+
if command != 'validate'
|
|
68
|
+
abort_with config.error_report unless config.valid?
|
|
69
|
+
end
|
|
67
70
|
|
|
68
71
|
# The matrix entry that builds `target` — the deps/build stages operate on
|
|
69
72
|
# its host platform.
|
|
@@ -80,44 +83,30 @@ end
|
|
|
80
83
|
|
|
81
84
|
case command
|
|
82
85
|
when 'validate'
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
begin
|
|
89
|
-
pkg = Crosspack::PackageManifest.load(File.expand_path(options[:package], options[:root]))
|
|
90
|
-
rescue Crosspack::InvalidManifestError => e
|
|
91
|
-
abort_with e.message
|
|
92
|
-
end
|
|
93
|
-
begin
|
|
94
|
-
manifest = Crosspack::Manifest.load(File.expand_path(options[:file], options[:root]))
|
|
95
|
-
rescue Crosspack::InvalidManifestError => e
|
|
96
|
-
abort_with e.message
|
|
97
|
-
end
|
|
98
|
-
puts manifest.error_report
|
|
86
|
+
deps_manifest = config.deps_manifest
|
|
87
|
+
pkg = config.package_manifest
|
|
88
|
+
build_manifest = config.build_manifest
|
|
89
|
+
puts config.error_report
|
|
90
|
+
puts deps_manifest.error_report
|
|
99
91
|
puts pkg.error_report
|
|
100
|
-
puts build_manifest.error_report
|
|
101
|
-
ok =
|
|
92
|
+
puts build_manifest.error_report
|
|
93
|
+
ok = config.valid? && deps_manifest.valid? && pkg.valid? && build_manifest.valid?
|
|
102
94
|
exit(ok ? 0 : 1)
|
|
103
95
|
when 'matrix'
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
abort_with 'deps.yaml schema is invalid — run: crosspack validate' unless manifest.valid?
|
|
110
|
-
puts Crosspack::Matrix.new(manifest).render
|
|
111
|
-
if build_manifest
|
|
96
|
+
deps_manifest = config.deps_manifest
|
|
97
|
+
abort_with 'the deps: section is invalid — run: crosspack validate' unless deps_manifest.valid?
|
|
98
|
+
puts Crosspack::Matrix.new(deps_manifest).render
|
|
99
|
+
build_manifest = config.build_manifest
|
|
100
|
+
if build_manifest.valid?
|
|
112
101
|
puts "\n#{Crossbuild::Matrix.new(build_manifest).render}"
|
|
113
102
|
else
|
|
114
|
-
warn "\nnote:
|
|
103
|
+
warn "\nnote: no valid build: section — build matrix not shown (crosspack validate)"
|
|
115
104
|
end
|
|
116
105
|
when 'resolve'
|
|
117
106
|
target_str = positional
|
|
118
107
|
abort_with 'Specify a target: crosspack resolve debian-12 (see crosspack --help)' unless target_str
|
|
119
108
|
begin
|
|
120
|
-
manifest =
|
|
109
|
+
manifest = config.deps_manifest
|
|
121
110
|
target = Crosspack::Target.parse(target_str, arch: options[:arch])
|
|
122
111
|
resolver = Crosspack::Resolver.new(manifest)
|
|
123
112
|
format = options[:format] || target.format
|
|
@@ -134,8 +123,8 @@ when 'resolve'
|
|
|
134
123
|
abort_with e.message
|
|
135
124
|
end
|
|
136
125
|
when 'version'
|
|
137
|
-
|
|
138
|
-
abort_with '
|
|
126
|
+
build_manifest = config.build_manifest
|
|
127
|
+
abort_with 'the build: section is invalid — run: crosspack validate' unless build_manifest.valid?
|
|
139
128
|
begin
|
|
140
129
|
puts Crossbuild::VersionScheme.new(build_manifest.version).compute(root: options[:root])
|
|
141
130
|
rescue Crossbuild::VersionScheme::Error => e
|
|
@@ -143,7 +132,8 @@ when 'version'
|
|
|
143
132
|
end
|
|
144
133
|
when 'deps'
|
|
145
134
|
abort_with 'Specify a target: crosspack deps debian-12 (see crosspack --help)' unless positional
|
|
146
|
-
|
|
135
|
+
build_manifest = config.build_manifest
|
|
136
|
+
abort_with 'the build: section is invalid — run: crosspack validate' unless build_manifest.valid?
|
|
147
137
|
entry_for_target!(build_manifest, positional, Crossbuild::Platform.os, Crossbuild::Platform.arch)
|
|
148
138
|
installer = Crossbuild::DepInstaller.new(build_manifest, root: options[:root])
|
|
149
139
|
begin
|
|
@@ -158,7 +148,8 @@ when 'deps'
|
|
|
158
148
|
end
|
|
159
149
|
puts "deps ready:\n#{installer.report}"
|
|
160
150
|
when 'build'
|
|
161
|
-
|
|
151
|
+
build_manifest = config.build_manifest
|
|
152
|
+
abort_with 'the build: section is invalid — run: crosspack validate' unless build_manifest.valid?
|
|
162
153
|
if options[:all]
|
|
163
154
|
target = nil
|
|
164
155
|
else
|
|
@@ -179,8 +170,7 @@ when 'pack'
|
|
|
179
170
|
begin
|
|
180
171
|
target = Crosspack::Target.parse(positional, arch: options[:arch])
|
|
181
172
|
path = Crosspack.pack(
|
|
182
|
-
|
|
183
|
-
deps: options[:file],
|
|
173
|
+
config: config,
|
|
184
174
|
target: target,
|
|
185
175
|
version: options[:version],
|
|
186
176
|
root: options[:root],
|
|
@@ -194,12 +184,8 @@ when 'pack'
|
|
|
194
184
|
puts "📦 #{path}"
|
|
195
185
|
when 'targets'
|
|
196
186
|
# What can actually be packed: builds tree + resolvable deps per target.
|
|
197
|
-
|
|
198
|
-
sources =
|
|
199
|
-
Crosspack::PackageManifest.load(pkg_path).sources
|
|
200
|
-
rescue Crosspack::InvalidManifestError
|
|
201
|
-
nil
|
|
202
|
-
end
|
|
187
|
+
pkg = config.package_manifest
|
|
188
|
+
sources = pkg.valid? ? pkg.sources : nil
|
|
203
189
|
root = sources ? File.expand_path(sources, options[:root]) : File.join(options[:root], 'builds')
|
|
204
190
|
puts Crosspack::Builds.report(root)
|
|
205
191
|
else
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'yaml'
|
|
4
|
-
|
|
5
3
|
module Crossbuild
|
|
6
|
-
# A single schema problem: full path into build
|
|
4
|
+
# A single schema problem: full path into the build: section + human
|
|
5
|
+
# explanation.
|
|
7
6
|
class Issue
|
|
8
7
|
attr_reader :path, :message
|
|
9
8
|
|
|
@@ -17,7 +16,7 @@ module Crossbuild
|
|
|
17
16
|
end
|
|
18
17
|
end
|
|
19
18
|
|
|
20
|
-
class InvalidManifestError <
|
|
19
|
+
class InvalidManifestError < Crosspack::InvalidManifestError; end
|
|
21
20
|
|
|
22
21
|
class BuildManifest
|
|
23
22
|
OS_LIST = %w[linux darwin windows].freeze
|
|
@@ -85,21 +84,6 @@ module Crossbuild
|
|
|
85
84
|
|
|
86
85
|
attr_reader :path, :name, :version, :output, :deps, :entries, :errors, :warnings
|
|
87
86
|
|
|
88
|
-
def self.load(path)
|
|
89
|
-
unless File.file?(path)
|
|
90
|
-
raise InvalidManifestError,
|
|
91
|
-
"Build manifest not found: #{path}\n" \
|
|
92
|
-
'Create build.yaml next to the Rakefile (see crossbuild/README.md).'
|
|
93
|
-
end
|
|
94
|
-
begin
|
|
95
|
-
raw = YAML.safe_load(File.read(path), permitted_classes: [], aliases: false)
|
|
96
|
-
rescue Psych::SyntaxError => e
|
|
97
|
-
raise InvalidManifestError,
|
|
98
|
-
"#{path}: YAML syntax error on line #{e.line}: #{e.problem}"
|
|
99
|
-
end
|
|
100
|
-
new(path, raw)
|
|
101
|
-
end
|
|
102
|
-
|
|
103
87
|
def initialize(path, raw)
|
|
104
88
|
@path = path
|
|
105
89
|
@raw = raw
|
|
@@ -121,7 +105,7 @@ module Crossbuild
|
|
|
121
105
|
def validate!
|
|
122
106
|
return self if valid?
|
|
123
107
|
|
|
124
|
-
lines = ["#{path}: build
|
|
108
|
+
lines = ["#{path}: the build: section is invalid (#{@errors.size} errors):"]
|
|
125
109
|
@errors.each { |e| lines << " ✗ #{e}" }
|
|
126
110
|
lines << 'Build aborted: fix the listed nodes and retry.'
|
|
127
111
|
raise InvalidManifestError, lines.join("\n")
|
|
@@ -132,9 +116,9 @@ module Crossbuild
|
|
|
132
116
|
lines = []
|
|
133
117
|
if valid?
|
|
134
118
|
noun = @entries.size == 1 ? 'entry' : 'entries'
|
|
135
|
-
lines << "#{path}:
|
|
119
|
+
lines << "#{path}: the build: section is valid (#{@entries.size} #{noun}: #{@entries.map(&:id).join(', ')})."
|
|
136
120
|
else
|
|
137
|
-
lines << "#{path}: build
|
|
121
|
+
lines << "#{path}: the build: section is invalid (#{@errors.size} errors):"
|
|
138
122
|
@errors.each { |e| lines << " ✗ #{e}" }
|
|
139
123
|
end
|
|
140
124
|
@warnings.each { |w| lines << " ⚠ #{w}" }
|
|
@@ -174,7 +158,9 @@ module Crossbuild
|
|
|
174
158
|
|
|
175
159
|
def validate
|
|
176
160
|
unless @raw.is_a?(Hash) && !@raw.empty?
|
|
177
|
-
@errors << Issue.new('(root)',
|
|
161
|
+
@errors << Issue.new('(root)',
|
|
162
|
+
'the build: section of crosspack.yml is missing or empty — ' \
|
|
163
|
+
'the deps and build stages need it (matrix, steps, artifacts)')
|
|
178
164
|
return
|
|
179
165
|
end
|
|
180
166
|
|
|
@@ -189,7 +175,7 @@ module Crossbuild
|
|
|
189
175
|
def validate_name
|
|
190
176
|
@name = @raw['name'].to_s
|
|
191
177
|
if @raw['name'].nil? || @name.empty?
|
|
192
|
-
@errors << Issue.new('name', 'is required
|
|
178
|
+
@errors << Issue.new('name', 'is required at the top level of crosspack.yml, e.g. name: myapp')
|
|
193
179
|
elsif @name !~ NAME_RE
|
|
194
180
|
@errors << Issue.new('name', "invalid name #{@name.inspect} (letters, digits, \"+\", \"_\", \"-\", \".\")")
|
|
195
181
|
end
|
|
@@ -198,7 +184,7 @@ module Crossbuild
|
|
|
198
184
|
def validate_version
|
|
199
185
|
spec = @raw['version']
|
|
200
186
|
if spec.nil?
|
|
201
|
-
@warnings << Issue.new('version', 'not set — defaulting to calver (YYYY.MM.DD-<secs>)')
|
|
187
|
+
@warnings << Issue.new('version', 'not set at the top level of crosspack.yml — defaulting to calver (YYYY.MM.DD-<secs>)')
|
|
202
188
|
@version = 'calver'
|
|
203
189
|
return
|
|
204
190
|
end
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Crossbuild
|
|
4
4
|
# Verifies and installs the build dependencies declared in the deps:
|
|
5
|
-
# section of
|
|
6
|
-
# "*" — see Platform.selectors) are matched
|
|
7
|
-
# hosts: rules; the first hit wins. Every rule
|
|
5
|
+
# rules of the build: section of crosspack.yml. Host selectors (distro
|
|
6
|
+
# id, ID_LIKE tokens, os, "*" — see Platform.selectors) are matched
|
|
7
|
+
# against each dependency's hosts: rules; the first hit wins. Every rule
|
|
8
|
+
# carries its own commands:
|
|
8
9
|
# `verify` decides presence (exit 0 = installed; default: PATH lookup of
|
|
9
10
|
# the dependency name), `install` says how to install it on that host.
|
|
10
11
|
class DepInstaller
|
data/lib/crossbuild/matrix.rb
CHANGED
|
@@ -14,7 +14,7 @@ module Crossbuild
|
|
|
14
14
|
|
|
15
15
|
def render
|
|
16
16
|
host = "#{@host_os}/#{Platform.display_arch(@host_arch)}"
|
|
17
|
-
lines = ["build
|
|
17
|
+
lines = ["build matrix — host: #{host}"]
|
|
18
18
|
@manifest.entries.each { |e| lines << render_entry(e) }
|
|
19
19
|
buildable = @manifest.buildable_entries(@host_os, @host_arch)
|
|
20
20
|
lines << if buildable.empty?
|
data/lib/crossbuild/platform.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Crossbuild
|
|
4
|
-
# Host platform detection, normalized to the os/arch vocabulary
|
|
5
|
-
# uses: linux/amd64, darwin/arm64, windows/amd64.
|
|
4
|
+
# Host platform detection, normalized to the os/arch vocabulary the
|
|
5
|
+
# build: section uses: linux/amd64, darwin/arm64, windows/amd64.
|
|
6
6
|
module Platform
|
|
7
7
|
OS_BY_RUBY = {
|
|
8
8
|
linux: /linux/,
|
|
@@ -45,7 +45,7 @@ module Crossbuild
|
|
|
45
45
|
facts['ID']
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
# Ordered host selectors for the deps:
|
|
48
|
+
# Ordered host selectors for the deps: rules of the build: section — distro id,
|
|
49
49
|
# then ID_LIKE tokens (Linux only), then the os, then "*" as the final
|
|
50
50
|
# wildcard. The first selector with a matching rule wins.
|
|
51
51
|
def self.selectors
|
data/lib/crossbuild/runner.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Crossbuild
|
|
|
4
4
|
# Runs the shell steps of a matrix entry from the project root. Commands may
|
|
5
5
|
# reference build facts via {{version}}, {{name}}, {{platform}}, {{os}},
|
|
6
6
|
# {{arch}} and {{id}} placeholders — expanded by the gem itself so the same
|
|
7
|
-
#
|
|
7
|
+
# config works under POSIX shells and cmd.exe. The same facts are also
|
|
8
8
|
# exported as CROSSBUILD_* environment variables.
|
|
9
9
|
class Runner
|
|
10
10
|
class BuildError < Error; end
|
data/lib/crossbuild/version.rb
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Crossbuild
|
|
4
|
-
# Computes the build version from the `version:`
|
|
4
|
+
# Computes the build version from the top-level `version:` of
|
|
5
|
+
# crosspack.yml.
|
|
5
6
|
# Supported specs:
|
|
6
7
|
# calver — YYYY.MM.DD-<seconds since local midnight>, e.g. 2026.08.31-33837
|
|
7
8
|
# git-tag — latest `git describe --tags --abbrev=0`, fallback 0.0.0-dev
|
|
@@ -43,7 +44,7 @@ module Crossbuild
|
|
|
43
44
|
|
|
44
45
|
def env_version(var)
|
|
45
46
|
value = ENV[var].to_s.strip
|
|
46
|
-
raise Error, "version env var #{var} is not set (version: env:#{var}
|
|
47
|
+
raise Error, "version env var #{var} is not set (version: env:#{var} at the top level of crosspack.yml)" if value.empty?
|
|
47
48
|
|
|
48
49
|
value
|
|
49
50
|
end
|
data/lib/crossbuild.rb
CHANGED
|
@@ -17,13 +17,18 @@ require_relative 'crossbuild/matrix'
|
|
|
17
17
|
require_relative 'crossbuild/builder'
|
|
18
18
|
|
|
19
19
|
module Crossbuild
|
|
20
|
-
# Convenience wrapper: Crossbuild.build('
|
|
20
|
+
# Convenience wrapper: Crossbuild.build('crosspack.yml') runs every
|
|
21
21
|
# host-buildable matrix entry and returns Builder::Result. target: builds
|
|
22
22
|
# the entry distributing to that target only; deps: false skips the
|
|
23
|
-
# pre-build dependency check/install pass.
|
|
23
|
+
# pre-build dependency check/install pass. The manifest argument may be a
|
|
24
|
+
# Config, a path to crosspack.yml or a ready BuildManifest.
|
|
24
25
|
def self.build(manifest, entry_id: nil, target: nil, root: Dir.pwd, output_base: nil, version: nil,
|
|
25
26
|
deps: true, host_os: Platform.os, host_arch: Platform.arch)
|
|
26
|
-
m =
|
|
27
|
+
m = case manifest
|
|
28
|
+
when BuildManifest then manifest
|
|
29
|
+
when Crosspack::Config then manifest.build_manifest
|
|
30
|
+
else Crosspack::Config.coerce(manifest, root: root).build_manifest
|
|
31
|
+
end
|
|
27
32
|
Builder.new(m, root: root, output_base: output_base, version: version, deps: deps,
|
|
28
33
|
host_os: host_os, host_arch: host_arch).run(entry_id: entry_id, target: target)
|
|
29
34
|
end
|
|
@@ -42,7 +42,7 @@ module Crosspack
|
|
|
42
42
|
|
|
43
43
|
<<~PKGBUILD
|
|
44
44
|
# Maintainer: #{maintainer}
|
|
45
|
-
# Generated by crosspack — edit deps.
|
|
45
|
+
# Generated by crosspack — edit the deps: section of crosspack.yml, not this file.
|
|
46
46
|
pkgname=#{name}
|
|
47
47
|
pkgver=#{version}
|
|
48
48
|
pkgrel=#{release}
|
|
@@ -8,7 +8,8 @@ module Crosspack
|
|
|
8
8
|
module Builders
|
|
9
9
|
# Stages a buildroot and packs it with rpmbuild. The spec is generated
|
|
10
10
|
# with AutoReqProv disabled so dependency names come exclusively from
|
|
11
|
-
# deps.
|
|
11
|
+
# the deps: section of crosspack.yml (resolved per target), never from
|
|
12
|
+
# ELF soname scanning.
|
|
12
13
|
class Rpm
|
|
13
14
|
def self.build(name:, version:, release:, summary:, license:, requires:,
|
|
14
15
|
files:, symlinks: {}, executables: [], description: nil,
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module Crosspack
|
|
6
|
+
# crosspack.yml — the single configuration file. The top level carries the
|
|
7
|
+
# shared facts (name, version scheme) plus three sections:
|
|
8
|
+
# build: what and where to build (the former build.yaml)
|
|
9
|
+
# package: what and how to pack (the former package.yaml)
|
|
10
|
+
# deps: runtime dependencies (the former deps.yaml)
|
|
11
|
+
# The shared name/version are injected into the build section and the name
|
|
12
|
+
# into the package section; each section is then validated by its manifest
|
|
13
|
+
# class (Crossbuild::BuildManifest, PackageManifest, Manifest). A missing
|
|
14
|
+
# section yields a manifest whose root error names the section — gating
|
|
15
|
+
# happens per section, not per file.
|
|
16
|
+
class Config
|
|
17
|
+
DEFAULT_PATH = 'crosspack.yml'
|
|
18
|
+
TOP_LEVEL_KEYS = %w[name version build package deps].freeze
|
|
19
|
+
NAME_RE = /\A[a-z0-9][a-z0-9+._-]*\z/i.freeze
|
|
20
|
+
|
|
21
|
+
attr_reader :path, :errors, :warnings
|
|
22
|
+
|
|
23
|
+
def self.load(path = DEFAULT_PATH)
|
|
24
|
+
unless File.file?(path)
|
|
25
|
+
raise InvalidManifestError,
|
|
26
|
+
"crosspack.yml not found: #{path}\n" \
|
|
27
|
+
'Create crosspack.yml in the project root (see crosspack/README.md).'
|
|
28
|
+
end
|
|
29
|
+
begin
|
|
30
|
+
raw = YAML.safe_load(File.read(path), permitted_classes: [], aliases: false)
|
|
31
|
+
rescue Psych::SyntaxError => e
|
|
32
|
+
raise InvalidManifestError,
|
|
33
|
+
"#{path}: YAML syntax error on line #{e.line}: #{e.problem}"
|
|
34
|
+
end
|
|
35
|
+
new(path, raw)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# A Config, or a path to crosspack.yml, -> Config. Relative paths are
|
|
39
|
+
# resolved against root.
|
|
40
|
+
def self.coerce(config, root: Dir.pwd)
|
|
41
|
+
return config if config.is_a?(self)
|
|
42
|
+
|
|
43
|
+
load(File.expand_path(config.to_s, root))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def initialize(path, raw)
|
|
47
|
+
@path = path
|
|
48
|
+
@raw = raw
|
|
49
|
+
@errors = []
|
|
50
|
+
@warnings = []
|
|
51
|
+
validate
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def valid?
|
|
55
|
+
@errors.empty?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def error_report
|
|
59
|
+
lines = []
|
|
60
|
+
if valid?
|
|
61
|
+
lines << "#{@path}: config is valid."
|
|
62
|
+
else
|
|
63
|
+
lines << "#{@path}: crosspack.yml is invalid (#{@errors.size} errors):"
|
|
64
|
+
@errors.each { |e| lines << " ✗ #{e}" }
|
|
65
|
+
end
|
|
66
|
+
@warnings.each { |w| lines << " ⚠ #{w}" }
|
|
67
|
+
lines.join("\n")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def build_manifest
|
|
71
|
+
require_relative '../crossbuild' unless defined?(::Crossbuild::BuildManifest)
|
|
72
|
+
|
|
73
|
+
@build_manifest ||= Crossbuild::BuildManifest.new(@path, build_section)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def package_manifest
|
|
77
|
+
@package_manifest ||= PackageManifest.new(@path, package_section)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def deps_manifest
|
|
81
|
+
@deps_manifest ||= Manifest.new(@path, deps_section)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def validate
|
|
87
|
+
unless @raw.is_a?(Hash) && !@raw.empty?
|
|
88
|
+
@errors << Issue.new('(root)',
|
|
89
|
+
'crosspack.yml must be a non-empty mapping: name, version?, build:, package:, deps:')
|
|
90
|
+
return
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
check_unknown_keys
|
|
94
|
+
validate_name
|
|
95
|
+
validate_version
|
|
96
|
+
check_section_types
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def check_unknown_keys
|
|
100
|
+
@raw.keys.each do |key|
|
|
101
|
+
next if TOP_LEVEL_KEYS.include?(key.to_s)
|
|
102
|
+
|
|
103
|
+
@errors << Issue.new("(root).#{key}", "unknown key; allowed: #{TOP_LEVEL_KEYS.join(', ')}")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def validate_name
|
|
108
|
+
name = @raw['name']
|
|
109
|
+
if name.nil?
|
|
110
|
+
@errors << Issue.new('name', 'is required once at the top level, e.g. name: myapp')
|
|
111
|
+
elsif !name.is_a?(String) || name.strip.empty? || name !~ NAME_RE
|
|
112
|
+
@errors << Issue.new('name', "invalid name #{name.inspect} (letters, digits, \"+\", \"_\", \"-\", \".\")")
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def validate_version
|
|
117
|
+
v = @raw['version']
|
|
118
|
+
return if v.nil?
|
|
119
|
+
|
|
120
|
+
unless v.is_a?(String) && !v.strip.empty?
|
|
121
|
+
@errors << Issue.new('version',
|
|
122
|
+
"must be a string scheme: calver / git-tag / env:VAR or a literal, got #{v.inspect}")
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def check_section_types
|
|
127
|
+
%w[build package deps].each do |key|
|
|
128
|
+
next unless @raw.key?(key) && !@raw[key].is_a?(Hash)
|
|
129
|
+
|
|
130
|
+
@errors << Issue.new(key, 'must be a mapping')
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def build_section
|
|
135
|
+
base = section('build') or return nil
|
|
136
|
+
|
|
137
|
+
{ 'name' => top_name, 'version' => top_version }.merge(base)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def package_section
|
|
141
|
+
base = section('package') or return nil
|
|
142
|
+
|
|
143
|
+
{ 'name' => top_name }.merge(base)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def deps_section
|
|
147
|
+
section('deps')
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def section(key)
|
|
151
|
+
s = @raw.is_a?(Hash) ? @raw[key] : nil
|
|
152
|
+
s.is_a?(Hash) && !s.empty? ? s : nil
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def top_name
|
|
156
|
+
@raw.is_a?(Hash) ? @raw['name'] : nil
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def top_version
|
|
160
|
+
v = @raw.is_a?(Hash) ? @raw['version'] : nil
|
|
161
|
+
v.is_a?(String) && !v.strip.empty? ? v.strip : nil
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
data/lib/crosspack/manifest.rb
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'yaml'
|
|
4
|
-
|
|
5
3
|
module Crosspack
|
|
6
|
-
# Canonical dependency names crosspack knows about. Anything else in
|
|
7
|
-
# deps
|
|
4
|
+
# Canonical dependency names crosspack knows about. Anything else in the
|
|
5
|
+
# deps: section still resolves, but produces a warning (possible typo).
|
|
8
6
|
KNOWN_CANONICAL = %w[
|
|
9
7
|
webkit2gtk gtk3 openmp ayatana-appindicator
|
|
10
8
|
glib2 cairo pango gdk-pixbuf alsa-lib pulseaudio dbus
|
|
@@ -15,7 +13,8 @@ module Crosspack
|
|
|
15
13
|
|
|
16
14
|
CANONICAL_NAME_RE = /\A[a-z0-9][a-z0-9+._-]*\z/.freeze
|
|
17
15
|
|
|
18
|
-
# A single schema problem: full path into deps
|
|
16
|
+
# A single schema problem: full path into the deps: section + human
|
|
17
|
+
# explanation.
|
|
19
18
|
class Issue
|
|
20
19
|
attr_reader :path, :message
|
|
21
20
|
|
|
@@ -31,33 +30,19 @@ module Crosspack
|
|
|
31
30
|
|
|
32
31
|
class InvalidManifestError < StandardError; end
|
|
33
32
|
|
|
34
|
-
# Parses and validates deps.
|
|
35
|
-
# names; each maps to
|
|
36
|
-
#
|
|
37
|
-
#
|
|
33
|
+
# Parses and validates the deps: section of crosspack.yml. Keys are
|
|
34
|
+
# canonical dependency names; each maps to
|
|
35
|
+
# { targets: { family: { "version": [pkgs] } } } or a scalar verdict for a
|
|
36
|
+
# whole family. A reserved `version:` key inside the section declares the
|
|
37
|
+
# schema version (see SCHEMA_VERSION).
|
|
38
38
|
class Manifest
|
|
39
|
-
# The deps
|
|
39
|
+
# The deps: section schema version this crosspack understands. Sections
|
|
40
40
|
# declaring a newer version are rejected at load time with an upgrade
|
|
41
41
|
# hint; older (or absent) versions are accepted.
|
|
42
42
|
SCHEMA_VERSION = 1
|
|
43
43
|
|
|
44
44
|
attr_reader :path, :deps, :schema_version, :errors, :warnings
|
|
45
45
|
|
|
46
|
-
def self.load(path)
|
|
47
|
-
unless File.file?(path)
|
|
48
|
-
raise InvalidManifestError,
|
|
49
|
-
"Dependencies file not found: #{path}\n" \
|
|
50
|
-
'Create deps.yaml next to the Rakefile (see crosspack/README.md).'
|
|
51
|
-
end
|
|
52
|
-
begin
|
|
53
|
-
raw = YAML.safe_load(File.read(path), permitted_classes: [], aliases: false)
|
|
54
|
-
rescue Psych::SyntaxError => e
|
|
55
|
-
raise InvalidManifestError,
|
|
56
|
-
"#{path}: YAML syntax error on line #{e.line}: #{e.problem}"
|
|
57
|
-
end
|
|
58
|
-
new(path, raw)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
46
|
def initialize(path, raw)
|
|
62
47
|
@path = path
|
|
63
48
|
@schema_version = raw.is_a?(Hash) ? raw['version'] : nil
|
|
@@ -77,7 +62,7 @@ module Crosspack
|
|
|
77
62
|
def validate!
|
|
78
63
|
return self if valid?
|
|
79
64
|
|
|
80
|
-
lines = ["#{path}: deps
|
|
65
|
+
lines = ["#{path}: the deps: section is invalid (#{@errors.size} errors):"]
|
|
81
66
|
@errors.each { |e| lines << " ✗ #{e}" }
|
|
82
67
|
lines << 'Build aborted: fix the listed nodes and retry.'
|
|
83
68
|
raise InvalidManifestError, lines.join("\n")
|
|
@@ -87,9 +72,9 @@ module Crosspack
|
|
|
87
72
|
def error_report
|
|
88
73
|
lines = []
|
|
89
74
|
if valid?
|
|
90
|
-
lines << "#{path}:
|
|
75
|
+
lines << "#{path}: the deps: section is valid."
|
|
91
76
|
else
|
|
92
|
-
lines << "#{path}: deps
|
|
77
|
+
lines << "#{path}: the deps: section is invalid (#{@errors.size} errors):"
|
|
93
78
|
@errors.each { |e| lines << " ✗ #{e}" }
|
|
94
79
|
end
|
|
95
80
|
@warnings.each { |w| lines << " ⚠ #{w}" }
|
|
@@ -113,7 +98,7 @@ module Crosspack
|
|
|
113
98
|
return if v <= SCHEMA_VERSION
|
|
114
99
|
|
|
115
100
|
raise InvalidManifestError,
|
|
116
|
-
"#{@path}: deps
|
|
101
|
+
"#{@path}: the deps: section declares schema version #{v}, " \
|
|
117
102
|
"but this crosspack (#{Crosspack::VERSION}) supports up to #{SCHEMA_VERSION}. " \
|
|
118
103
|
'Update the gem (gem update crosspack) or lower the declared version.'
|
|
119
104
|
end
|
|
@@ -122,7 +107,8 @@ module Crosspack
|
|
|
122
107
|
unless @raw.is_a?(Hash) && !@raw.empty?
|
|
123
108
|
@errors << Issue.new(
|
|
124
109
|
'(root)',
|
|
125
|
-
'
|
|
110
|
+
'the deps: section of crosspack.yml is missing or empty — ' \
|
|
111
|
+
'it must map canonical names to per-distro packages'
|
|
126
112
|
)
|
|
127
113
|
return
|
|
128
114
|
end
|
data/lib/crosspack/matrix.rb
CHANGED
|
@@ -4,7 +4,7 @@ require 'set'
|
|
|
4
4
|
|
|
5
5
|
module Crosspack
|
|
6
6
|
# Renders the dependency x target matrix straight from the manifest.
|
|
7
|
-
# Columns come from the targets declared in deps
|
|
7
|
+
# Columns come from the targets declared in the deps: section itself.
|
|
8
8
|
class Matrix
|
|
9
9
|
def initialize(manifest)
|
|
10
10
|
@manifest = manifest
|
|
@@ -12,7 +12,7 @@ module Crosspack
|
|
|
12
12
|
|
|
13
13
|
def render
|
|
14
14
|
columns = target_columns
|
|
15
|
-
return 'deps
|
|
15
|
+
return 'the deps: section is empty — nothing to show.' if columns.empty?
|
|
16
16
|
|
|
17
17
|
header = ['dependency'] + columns.map(&:to_s)
|
|
18
18
|
rows = @manifest.deps.map do |name, body|
|
|
@@ -1,32 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'yaml'
|
|
4
|
-
|
|
5
3
|
module Crosspack
|
|
6
|
-
# Packaging manifest (package.
|
|
7
|
-
# payload, executables, symlinks, desktop entry,
|
|
8
|
-
# crosspack's business; artifacts are picked from
|
|
4
|
+
# Packaging manifest (the package: section of crosspack.yml): declares
|
|
5
|
+
# WHAT and HOW to pack — payload, executables, symlinks, desktop entry,
|
|
6
|
+
# icon. Compilation is not crosspack's business; artifacts are picked from
|
|
7
|
+
# `sources` as-is.
|
|
9
8
|
class PackageManifest
|
|
10
|
-
REQUIRED = %w[
|
|
11
|
-
OPTIONAL = %w[summary license section lib_dir executables links desktop icon].freeze
|
|
9
|
+
REQUIRED = %w[maintainer description sources prefix payload].freeze
|
|
10
|
+
OPTIONAL = %w[name summary license section lib_dir executables links desktop icon].freeze
|
|
12
11
|
|
|
13
12
|
attr_reader :path, :data, :errors, :warnings
|
|
14
13
|
|
|
15
|
-
def self.load(path)
|
|
16
|
-
unless File.file?(path)
|
|
17
|
-
raise InvalidManifestError,
|
|
18
|
-
"Packaging manifest not found: #{path}\n" \
|
|
19
|
-
'Create package.yaml next to the Rakefile (see crosspack/README.md).'
|
|
20
|
-
end
|
|
21
|
-
begin
|
|
22
|
-
raw = YAML.safe_load(File.read(path), permitted_classes: [], aliases: false)
|
|
23
|
-
rescue Psych::SyntaxError => e
|
|
24
|
-
raise InvalidManifestError,
|
|
25
|
-
"#{path}: YAML syntax error on line #{e.line}: #{e.problem}"
|
|
26
|
-
end
|
|
27
|
-
new(path, raw)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
14
|
def initialize(path, raw)
|
|
31
15
|
@path = path
|
|
32
16
|
@data = raw.is_a?(Hash) ? raw : {}
|
|
@@ -43,7 +27,7 @@ module Crosspack
|
|
|
43
27
|
def validate!
|
|
44
28
|
return self if valid?
|
|
45
29
|
|
|
46
|
-
lines = ["#{path}: package
|
|
30
|
+
lines = ["#{path}: the package: section is invalid (#{@errors.size} errors):"]
|
|
47
31
|
@errors.each { |e| lines << " ✗ #{e}" }
|
|
48
32
|
lines << 'Packing aborted: fix the listed nodes and retry.'
|
|
49
33
|
raise InvalidManifestError, lines.join("\n")
|
|
@@ -51,9 +35,9 @@ module Crosspack
|
|
|
51
35
|
|
|
52
36
|
def error_report
|
|
53
37
|
if valid?
|
|
54
|
-
"#{path}:
|
|
38
|
+
"#{path}: the package: section is valid."
|
|
55
39
|
else
|
|
56
|
-
["#{path}: package
|
|
40
|
+
["#{path}: the package: section is invalid (#{@errors.size} errors):",
|
|
57
41
|
*@errors.map { |e| " ✗ #{e}" }].join("\n")
|
|
58
42
|
end
|
|
59
43
|
end
|
|
@@ -120,10 +104,16 @@ module Crosspack
|
|
|
120
104
|
|
|
121
105
|
def validate
|
|
122
106
|
unless @raw.is_a?(Hash) && !@raw.empty?
|
|
123
|
-
@errors << Issue.new('(root)',
|
|
107
|
+
@errors << Issue.new('(root)',
|
|
108
|
+
'the package: section of crosspack.yml is missing or empty — ' \
|
|
109
|
+
'the pack stage needs it (maintainer, description, payload, ...)')
|
|
124
110
|
return
|
|
125
111
|
end
|
|
126
112
|
|
|
113
|
+
if @data['name'].nil?
|
|
114
|
+
@errors << Issue.new('name', 'is required at the top level of crosspack.yml, e.g. name: myapp')
|
|
115
|
+
end
|
|
116
|
+
|
|
127
117
|
REQUIRED.each do |key|
|
|
128
118
|
next unless @data[key].nil?
|
|
129
119
|
|
data/lib/crosspack/packer.rb
CHANGED
|
@@ -4,22 +4,21 @@ require 'fileutils'
|
|
|
4
4
|
require 'tmpdir'
|
|
5
5
|
|
|
6
6
|
module Crosspack
|
|
7
|
-
# Single entry point: takes a
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
7
|
+
# Single entry point: takes a Config (the package: and deps: sections of
|
|
8
|
+
# crosspack.yml), resolves dependencies for the target and produces a
|
|
9
|
+
# native package under output_base/<family>/<version>/<arch>/. The builder
|
|
10
|
+
# is chosen by the target format — deb, rpm or PKGBUILD.
|
|
11
11
|
# version: when omitted, the version stamped by `crosspack build <target>`
|
|
12
12
|
# (.crosspack-build in the target's builds/ directory) is used.
|
|
13
13
|
class Packer
|
|
14
|
-
def self.pack(
|
|
14
|
+
def self.pack(config:, target:, version: nil, root: Dir.pwd,
|
|
15
15
|
output_base: 'crosspacks')
|
|
16
|
-
new(
|
|
16
|
+
new(config: config, target: target, version: version,
|
|
17
17
|
root: root, output_base: output_base).pack
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def initialize(
|
|
21
|
-
@
|
|
22
|
-
@deps_path = deps
|
|
20
|
+
def initialize(config:, target:, version:, root:, output_base:)
|
|
21
|
+
@config = config
|
|
23
22
|
@target = target
|
|
24
23
|
@version = version && version.to_s
|
|
25
24
|
@root = root
|
|
@@ -27,8 +26,9 @@ module Crosspack
|
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
def pack
|
|
30
|
-
@
|
|
31
|
-
@
|
|
29
|
+
@config = Config.coerce(@config, root: @root)
|
|
30
|
+
@pkg = @config.package_manifest
|
|
31
|
+
@deps = @config.deps_manifest
|
|
32
32
|
validate_both!
|
|
33
33
|
resolve_version!
|
|
34
34
|
@pkg_data = build_layout
|
|
@@ -69,6 +69,7 @@ module Crosspack
|
|
|
69
69
|
|
|
70
70
|
def validate_both!
|
|
71
71
|
errors = []
|
|
72
|
+
errors << @config.error_report unless @config.valid?
|
|
72
73
|
errors << @pkg.error_report unless @pkg.valid?
|
|
73
74
|
errors << @deps.error_report unless @deps.valid?
|
|
74
75
|
return if errors.empty?
|
data/lib/crosspack/resolver.rb
CHANGED
|
@@ -25,7 +25,7 @@ module Crosspack
|
|
|
25
25
|
class ResolveError < StandardError; end
|
|
26
26
|
|
|
27
27
|
# Turns (manifest, target) into concrete package dependencies. No network,
|
|
28
|
-
# no search: everything comes from deps.
|
|
28
|
+
# no search: everything comes from the deps: section of crosspack.yml.
|
|
29
29
|
class Resolver
|
|
30
30
|
def initialize(manifest)
|
|
31
31
|
@manifest = manifest
|
|
@@ -41,7 +41,7 @@ module Crosspack
|
|
|
41
41
|
if rule.nil?
|
|
42
42
|
available = rules.keys.map(&:to_s).join(', ')
|
|
43
43
|
raise ResolveError,
|
|
44
|
-
"#{name}: deps
|
|
44
|
+
"#{name}: the deps: section has no rules for family #{family} (target #{target}). " \
|
|
45
45
|
"Add the section #{name}: targets: #{family}: ... — currently described: #{available}"
|
|
46
46
|
end
|
|
47
47
|
|
data/lib/crosspack/version.rb
CHANGED
data/lib/crosspack.rb
CHANGED
|
@@ -4,6 +4,7 @@ require_relative 'crosspack/version'
|
|
|
4
4
|
require_relative 'crosspack/target'
|
|
5
5
|
require_relative 'crosspack/manifest'
|
|
6
6
|
require_relative 'crosspack/package_manifest'
|
|
7
|
+
require_relative 'crosspack/config'
|
|
7
8
|
require_relative 'crosspack/resolver'
|
|
8
9
|
require_relative 'crosspack/matrix'
|
|
9
10
|
require_relative 'crosspack/builds'
|
|
@@ -30,8 +31,8 @@ module Crosspack
|
|
|
30
31
|
# executables: [destination paths to chmod 0755]
|
|
31
32
|
class BuildError < StandardError; end
|
|
32
33
|
|
|
33
|
-
# Convenience wrapper: Crosspack.pack(
|
|
34
|
-
#
|
|
34
|
+
# Convenience wrapper: Crosspack.pack(config: 'crosspack.yml',
|
|
35
|
+
# target: Target.parse('debian-12'), version: '1.0-1')
|
|
35
36
|
def self.pack(**kwargs)
|
|
36
37
|
Packer.pack(**kwargs)
|
|
37
38
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: crosspack
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oleg Orlov
|
|
@@ -43,6 +43,7 @@ files:
|
|
|
43
43
|
- lib/crosspack/builders/rpm.rb
|
|
44
44
|
- lib/crosspack/builders/wix.rb
|
|
45
45
|
- lib/crosspack/builds.rb
|
|
46
|
+
- lib/crosspack/config.rb
|
|
46
47
|
- lib/crosspack/manifest.rb
|
|
47
48
|
- lib/crosspack/matrix.rb
|
|
48
49
|
- lib/crosspack/package_manifest.rb
|