crosspack 0.2.0 → 0.4.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 +58 -0
- data/README.md +72 -43
- data/exe/crosspack +119 -31
- data/lib/crossbuild/build_manifest.rb +111 -2
- data/lib/crossbuild/builder.rb +32 -6
- data/lib/crossbuild/dep_installer.rb +126 -0
- data/lib/crossbuild/distributor.rb +15 -2
- data/lib/crossbuild/matrix.rb +28 -1
- data/lib/crossbuild/platform.rb +33 -0
- data/lib/crossbuild/version.rb +1 -1
- data/lib/crossbuild.rb +8 -5
- data/lib/crosspack/builders/wix.rb +15 -5
- data/lib/crosspack/builds.rb +12 -0
- data/lib/crosspack/manifest.rb +33 -5
- data/lib/crosspack/packer.rb +23 -5
- data/lib/crosspack/version.rb +1 -1
- metadata +9 -9
- data/exe/crossbuild +0 -76
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 527794ea0ef513791363042fcace5c4a973a859e74c4300dedeec931268b00fc
|
|
4
|
+
data.tar.gz: 5bbad1503be58d2190c7f042fdd29ca8271d1d107826d8653589ef17c8587a9d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 465b113f78d7e0dce817e0a65c122437998938dbb66cd438c63762289dcc1a9c4a3ccf4df9a7639f73ec983aa20f31a94c34d357118efbdfdfb1415b9828fad1
|
|
7
|
+
data.tar.gz: 021f427e66d2a9c58eb02dde0907cfb7bf5f8c89a081018b2839df819a7c478dc4952f7fc9ba7246c4b2030488065b644401edfd32282ca987809019675c0670
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,64 @@ 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.4.0] - 2026-09-13
|
|
9
|
+
|
|
10
|
+
### Breaking
|
|
11
|
+
- The separate `crossbuild` executable is gone: one command, one staged
|
|
12
|
+
pipeline — `crosspack deps <target>` → `crosspack build <target>` →
|
|
13
|
+
`crosspack pack <target>`. The former crossbuild commands moved under
|
|
14
|
+
`crosspack` (validate/matrix/version gained build.yaml coverage; the gem
|
|
15
|
+
now installs only the `crosspack` binary).
|
|
16
|
+
- build.yaml `deps:` schema: host rules are now mappings with per-system
|
|
17
|
+
`verify` and `install` commands instead of a global `check:` plus a
|
|
18
|
+
bare command string. The 0.3.0 schema was never released outside the
|
|
19
|
+
local gem build, so no migration path is provided.
|
|
20
|
+
- `crosspack pack <target>` takes the target as a positional argument
|
|
21
|
+
(the old `--target` flag is gone) and `--version` is now optional: it
|
|
22
|
+
defaults to the version stamped by the build stage.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- Stage pipeline with gating: `crosspack build <target>` resolves the
|
|
26
|
+
matrix entry distributing to that target, runs the deps stage first,
|
|
27
|
+
and fans artifacts out into that target's builds/ directory only;
|
|
28
|
+
`crosspack pack <target>` refuses to run before the build stage
|
|
29
|
+
("Build stage not done for target ... — Run: crosspack build <target>").
|
|
30
|
+
- The build stage stamps `.crosspack-build` (version) into every
|
|
31
|
+
`builds/<target>/` directory; `crosspack pack` reads it as the default
|
|
32
|
+
package version.
|
|
33
|
+
- deps.yaml schema versioning: an optional top-level `version:` (default
|
|
34
|
+
1). A deps.yaml declaring a newer schema than this crosspack is
|
|
35
|
+
rejected at load time with an upgrade hint; the key is reserved and no
|
|
36
|
+
longer parsed as a canonical dependency name.
|
|
37
|
+
- deps: rules may be verify-only (no install command) — presence is
|
|
38
|
+
checked, and a missing dependency reports which `install:` to add.
|
|
39
|
+
|
|
40
|
+
## [0.3.0] - 2026-09-13
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
- build.yaml `deps:` section: build dependencies with per-host install
|
|
44
|
+
commands (e.g. `ubuntu: sudo apt-get install -y imagemagick`, `windows:
|
|
45
|
+
winget install ...`, or any project script). Host selectors resolve in
|
|
46
|
+
order: distro id from `/etc/os-release`, `ID_LIKE` tokens, os, `"*"`.
|
|
47
|
+
- `crossbuild deps` command: doctor + install of the deps: dependencies;
|
|
48
|
+
`--check` reports presence only and exits 1 when something is missing.
|
|
49
|
+
- `crossbuild build` now verifies/installs deps: dependencies before the
|
|
50
|
+
matrix entries; `--no-deps` skips the pass.
|
|
51
|
+
- `crossbuild matrix` lists how each deps: entry resolves on this host.
|
|
52
|
+
|
|
53
|
+
## [0.2.1] - 2026-09-11
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
- WiX builder: `wix build` is now invoked with `-arch <arch>` as separate
|
|
57
|
+
argv entries — the previous single `"-arch x64"` argument could never parse.
|
|
58
|
+
- WiX builder: a bare `dotnet` on PATH is no longer mistaken for the WiX
|
|
59
|
+
toolset (`dotnet build` cannot build an MSI); only a real `wix` command
|
|
60
|
+
triggers the real MSI build, otherwise the `.wxs` + `BUILD-MSI.txt`
|
|
61
|
+
fallback is emitted.
|
|
62
|
+
- WiX builder: manifest values (name, maintainer, summary) are XML-escaped
|
|
63
|
+
in the generated `.wxs`; a maintainer like `Name <email>` previously
|
|
64
|
+
produced invalid XML no WiX build could parse.
|
|
65
|
+
|
|
8
66
|
## [0.2.0] - 2026-09-08
|
|
9
67
|
|
|
10
68
|
### Added
|
data/README.md
CHANGED
|
@@ -1,32 +1,49 @@
|
|
|
1
1
|
# crosspack
|
|
2
2
|
|
|
3
3
|
Cross-platform build **and** pack toolchain for Wails-style desktop apps,
|
|
4
|
-
shipped as one gem with
|
|
4
|
+
shipped as one gem with one command and a staged pipeline:
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
distro × version × arch, driven by two small manifests.
|
|
6
|
+
```
|
|
7
|
+
crosspack deps <target> # verify/install the build host's dependencies
|
|
8
|
+
crosspack build <target> # compile the target's matrix entry into builds/
|
|
9
|
+
crosspack pack <target> # package builds/<target> as a native package
|
|
10
|
+
```
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
Every stage is keyed by the same target vocabulary — `debian-12`,
|
|
13
|
+
`ubuntu-24.04`, `fedora-41`, `arch`, `macos`, `windows-11.0` — and gated by
|
|
14
|
+
the previous one: `build` refuses to start before the deps stage passes, and
|
|
15
|
+
`pack` refuses to run before `build` filled `builds/<target>/`. The build
|
|
16
|
+
stage also stamps the version into the tree, so `pack` normally needs no
|
|
17
|
+
`--version`:
|
|
17
18
|
|
|
18
19
|
```
|
|
19
|
-
|
|
20
|
-
crosspack
|
|
20
|
+
crosspack deps ubuntu-24.04
|
|
21
|
+
crosspack build ubuntu-24.04
|
|
22
|
+
crosspack pack ubuntu-24.04
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
##
|
|
25
|
+
## build.yaml — what and where to build
|
|
24
26
|
|
|
25
27
|
```yaml
|
|
26
28
|
name: myapp
|
|
27
29
|
version: calver # calver | git-tag | env:VAR | any literal string
|
|
28
30
|
output: builds # default builds; mirrors package.yaml `sources`
|
|
29
31
|
|
|
32
|
+
deps: # build dependencies, per-host verify/install
|
|
33
|
+
imagemagick:
|
|
34
|
+
hosts: # first selector matching this host wins
|
|
35
|
+
ubuntu:
|
|
36
|
+
verify: dpkg -s imagemagick
|
|
37
|
+
install: sudo apt-get install -y imagemagick
|
|
38
|
+
darwin:
|
|
39
|
+
verify: magick -version
|
|
40
|
+
install: brew install imagemagick
|
|
41
|
+
windows:
|
|
42
|
+
verify: where magick
|
|
43
|
+
install: winget install -e --id ImageMagick.ImageMagick
|
|
44
|
+
"*": # any command — package manager or a project script
|
|
45
|
+
install: ./scripts/install-imagemagick.sh
|
|
46
|
+
|
|
30
47
|
matrix:
|
|
31
48
|
- build: linux/amd64 # os/arch this entry builds on (arch may be "any")
|
|
32
49
|
env: # extra env for the steps of this entry
|
|
@@ -63,6 +80,19 @@ builds/ubuntu/24.04/amd64/myapp -> build/bin/myapp (symlink)
|
|
|
63
80
|
builds/windows/11.0/x86_64/myapp.exe
|
|
64
81
|
```
|
|
65
82
|
|
|
83
|
+
### Build dependencies (deps:)
|
|
84
|
+
|
|
85
|
+
`deps:` is the host axis to `deps.yaml`'s target axis: deps.yaml declares
|
|
86
|
+
what a *package* needs at runtime; `deps:` declares what the *build host*
|
|
87
|
+
must have. Checks differ per system, so every host rule carries its own
|
|
88
|
+
commands: `verify` (presence probe; exit 0 = present; without it the name is
|
|
89
|
+
looked up in PATH) and `install` (how to install it; may be omitted for
|
|
90
|
+
verify-only rules). Selectors are tried in order: distro id from
|
|
91
|
+
`/etc/os-release` (`ubuntu`), then `ID_LIKE` tokens (`debian`), then the os
|
|
92
|
+
(`linux`, `darwin`, `windows`), then `"*"`. The deps stage runs verify →
|
|
93
|
+
install → re-verify for every dependency; a dependency that is still missing
|
|
94
|
+
fails the stage. Commands support the `{{name}}`/`{{os}}`/... placeholders.
|
|
95
|
+
|
|
66
96
|
### Version schemes
|
|
67
97
|
|
|
68
98
|
| scheme | value |
|
|
@@ -72,22 +102,9 @@ builds/windows/11.0/x86_64/myapp.exe
|
|
|
72
102
|
| `env:VAR` | taken from `VAR`; build fails loudly when unset |
|
|
73
103
|
| other string | used verbatim, e.g. `version: 1.2.3` |
|
|
74
104
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
crossbuild validate # build.yaml against the schema
|
|
79
|
-
crossbuild matrix # entries × host buildability table
|
|
80
|
-
crossbuild version # computed version
|
|
81
|
-
crossbuild build [--target linux/amd64] # all host-buildable entries, or one
|
|
82
|
-
crossbuild targets # what is in builds/ ready to pack
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
`build` refuses invalid manifests with path-pointing errors, runs only the
|
|
86
|
-
entries whose `build:` platform matches the host, then distributes.
|
|
105
|
+
## package.yaml + deps.yaml — what and how to pack
|
|
87
106
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
**`package.yaml`** — what and how to pack:
|
|
107
|
+
**`package.yaml`**:
|
|
91
108
|
|
|
92
109
|
```yaml
|
|
93
110
|
name: myapp
|
|
@@ -99,9 +116,9 @@ description: |-
|
|
|
99
116
|
license: Proprietary # optional, default Proprietary
|
|
100
117
|
section: utils # optional, deb only
|
|
101
118
|
|
|
102
|
-
sources: builds # compiled artifacts tree written by
|
|
103
|
-
# builds/<family>/<version>/<arch>/ —
|
|
104
|
-
# a matching build
|
|
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
|
|
105
122
|
prefix: usr/local # install prefix inside the package
|
|
106
123
|
lib_dir: lib/myapp # optional, default lib/<name>
|
|
107
124
|
|
|
@@ -124,9 +141,13 @@ icon: build/appicon.png # optional, -> <prefix>/share/pixmaps/<name>.png
|
|
|
124
141
|
```
|
|
125
142
|
|
|
126
143
|
**`deps.yaml`** — canonical dependency names resolved to concrete packages
|
|
127
|
-
per distro family/version
|
|
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:
|
|
128
147
|
|
|
129
148
|
```yaml
|
|
149
|
+
version: 1 # optional, currently 1
|
|
150
|
+
|
|
130
151
|
webkit2gtk:
|
|
131
152
|
targets:
|
|
132
153
|
debian:
|
|
@@ -136,17 +157,24 @@ webkit2gtk:
|
|
|
136
157
|
windows: system # preinstalled (WebView2)
|
|
137
158
|
```
|
|
138
159
|
|
|
139
|
-
|
|
140
|
-
path-pointing error messages; every
|
|
160
|
+
All three files are validated against embedded schemas with actionable,
|
|
161
|
+
path-pointing error messages; every stage refuses to run on an invalid
|
|
141
162
|
manifest.
|
|
142
163
|
|
|
143
164
|
## CLI
|
|
144
165
|
|
|
145
166
|
```
|
|
146
|
-
|
|
147
|
-
crosspack
|
|
148
|
-
crosspack
|
|
149
|
-
crosspack pack
|
|
167
|
+
# staged pipeline
|
|
168
|
+
crosspack deps <target> [--check] # verify/install build deps (--check: report only)
|
|
169
|
+
crosspack build <target>|--all [--no-deps] [--version X]
|
|
170
|
+
crosspack pack <target> [--version X] # version defaults to the build stamp
|
|
171
|
+
|
|
172
|
+
# inspection
|
|
173
|
+
crosspack validate # package.yaml, deps.yaml and build.yaml
|
|
174
|
+
crosspack resolve <target> # Depends line for the target
|
|
175
|
+
crosspack matrix # deps x target + build matrix tables
|
|
176
|
+
crosspack version # computed build version
|
|
177
|
+
crosspack targets # what is in builds/ ready to pack
|
|
150
178
|
```
|
|
151
179
|
|
|
152
180
|
`pack` picks the builder from the target and writes to
|
|
@@ -164,20 +192,21 @@ crosspacks/arch/x86_64/PKGBUILD
|
|
|
164
192
|
require 'crosspack' # pulls in Crossbuild too
|
|
165
193
|
|
|
166
194
|
Crossbuild.build('build.yaml', root: Dir.pwd) # all host entries
|
|
167
|
-
Crossbuild.build('build.yaml',
|
|
195
|
+
Crossbuild.build('build.yaml', target: 'ubuntu-24.04') # the stages' build, library-level
|
|
168
196
|
|
|
169
197
|
Crosspack.pack(
|
|
170
198
|
manifest: 'package.yaml', deps: 'deps.yaml',
|
|
171
199
|
target: Crosspack::Target.parse('debian-12', arch: 'amd64'),
|
|
172
|
-
version:
|
|
200
|
+
version: nil, # nil -> the version stamped by the build stage
|
|
173
201
|
root: Dir.pwd, output_base: 'crosspacks'
|
|
174
202
|
)
|
|
175
203
|
```
|
|
176
204
|
|
|
177
205
|
Lower-level pieces are public too: on the build side `Crossbuild::
|
|
178
206
|
BuildManifest`, `VersionScheme`, `Runner`, `Distributor`, `Matrix`,
|
|
179
|
-
`Builder`, `Platform`; on the pack side `Crosspack::
|
|
180
|
-
`Manifest`, `Resolver`, `Matrix`, `Target`, `
|
|
207
|
+
`Builder`, `DepInstaller`, `Platform`; on the pack side `Crosspack::
|
|
208
|
+
PackageManifest`, `Manifest`, `Resolver`, `Matrix`, `Target`, `Builds`,
|
|
209
|
+
`Builders::*`.
|
|
181
210
|
|
|
182
211
|
Deb needs `dpkg-deb` (present on any Debian/Ubuntu), rpm needs
|
|
183
212
|
`rpmbuild` (`sudo apt install rpm`), PKGBUILD generation needs nothing.
|
data/exe/crosspack
CHANGED
|
@@ -5,8 +5,10 @@ require 'optparse'
|
|
|
5
5
|
|
|
6
6
|
begin
|
|
7
7
|
require 'crosspack'
|
|
8
|
+
require 'crossbuild'
|
|
8
9
|
rescue LoadError
|
|
9
10
|
require_relative '../lib/crosspack'
|
|
11
|
+
require_relative '../lib/crossbuild'
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
def abort_with(message)
|
|
@@ -14,21 +16,28 @@ def abort_with(message)
|
|
|
14
16
|
exit 1
|
|
15
17
|
end
|
|
16
18
|
|
|
17
|
-
options = { file: 'deps.yaml', package: 'package.yaml',
|
|
18
|
-
version: nil, root: Dir.pwd, output_base:
|
|
19
|
+
options = { file: 'deps.yaml', package: 'package.yaml', build: 'build.yaml', arch: 'amd64',
|
|
20
|
+
format: nil, version: nil, root: Dir.pwd, output_base: nil,
|
|
21
|
+
check_only: false, no_deps: false, all: false }
|
|
19
22
|
parser = OptionParser.new do |opts|
|
|
20
|
-
opts.banner = "crosspack #{Crosspack::VERSION} —
|
|
23
|
+
opts.banner = "crosspack #{Crosspack::VERSION} — staged pipeline: deps → build → pack\n" \
|
|
21
24
|
"Usage: crosspack <command> [options]\n" \
|
|
22
|
-
|
|
25
|
+
"Stages: deps <target> [--check] verify/install build deps for the target's host\n" \
|
|
26
|
+
" build <target>|--all compile the target's matrix entry into builds/\n" \
|
|
27
|
+
" pack <target> [--version X] package builds/<target> (version from the build stamp)\n" \
|
|
28
|
+
'Commands: validate, resolve, matrix, version, targets'
|
|
23
29
|
|
|
24
30
|
opts.on('-f', '--file PATH', 'path to deps.yaml (default: ./deps.yaml)') { |v| options[:file] = v }
|
|
25
31
|
opts.on('--package PATH', 'path to package.yaml (default: ./package.yaml)') { |v| options[:package] = v }
|
|
26
|
-
opts.on('--
|
|
32
|
+
opts.on('--build PATH', 'path to build.yaml (default: ./build.yaml)') { |v| options[:build] = v }
|
|
27
33
|
opts.on('--arch ARCH', 'architecture: amd64/x86_64, arm64/aarch64 (default: amd64)') { |v| options[:arch] = v }
|
|
28
34
|
opts.on('--format FORMAT', 'output format for resolve: deb, rpm, pkgbuild (default: derived from the target)') { |v| options[:format] = v.to_sym }
|
|
29
|
-
opts.on('--version VERSION', '
|
|
35
|
+
opts.on('--version VERSION', 'override the version for build/pack') { |v| options[:version] = v }
|
|
30
36
|
opts.on('--root PATH', 'project root for relative paths (default: .)') { |v| options[:root] = v }
|
|
31
|
-
opts.on('--output-base DIR', 'output directory (default: ./crosspacks)') { |v| options[:output_base] = v }
|
|
37
|
+
opts.on('--output-base DIR', 'output directory (build stage: the manifest output, default builds; pack: default ./crosspacks)') { |v| options[:output_base] = v }
|
|
38
|
+
opts.on('--check', 'with `deps`: only report presence, never install (exit 1 if missing)') { options[:check_only] = true }
|
|
39
|
+
opts.on('--no-deps', 'with `build`: skip the deps stage') { options[:no_deps] = true }
|
|
40
|
+
opts.on('--all', 'with `build`: every host-buildable matrix entry (default target selection off)') { options[:all] = true }
|
|
32
41
|
opts.on('-h', '--help', 'show help') do
|
|
33
42
|
puts opts
|
|
34
43
|
exit
|
|
@@ -43,63 +52,142 @@ end
|
|
|
43
52
|
command = ARGV.shift
|
|
44
53
|
abort_with 'No command given. See: crosspack --help' if command.nil?
|
|
45
54
|
|
|
46
|
-
# Positional
|
|
47
|
-
|
|
55
|
+
# Positional argument: a target for the stage commands (deps/build/pack,
|
|
56
|
+
# resolve), a deps.yaml path for validate.
|
|
57
|
+
positional = ARGV.shift if ARGV.first && !ARGV.first.start_with?('-')
|
|
48
58
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
build_path = File.expand_path(options[:build], options[:root])
|
|
60
|
+
build_manifest = if File.file?(build_path)
|
|
61
|
+
begin
|
|
62
|
+
Crossbuild::BuildManifest.load(build_path)
|
|
63
|
+
rescue Crossbuild::InvalidManifestError => e
|
|
64
|
+
abort_with e.message
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The matrix entry that builds `target` — the deps/build stages operate on
|
|
69
|
+
# its host platform.
|
|
70
|
+
def entry_for_target!(manifest, target, host_os, host_arch)
|
|
71
|
+
entry = manifest.entry_for_target(target)
|
|
72
|
+
host = "#{host_os}/#{Crossbuild::Platform.display_arch(host_arch)}"
|
|
73
|
+
unless entry.buildable_on?(host_os, host_arch)
|
|
74
|
+
abort_with "The entry for #{target.inspect} (#{entry.id}) builds on #{entry.platform}, " \
|
|
75
|
+
"this host is #{host} — cross-compilation is not supported"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
entry
|
|
53
79
|
end
|
|
54
80
|
|
|
55
81
|
case command
|
|
56
82
|
when 'validate'
|
|
83
|
+
if positional
|
|
84
|
+
options[:file] = positional
|
|
85
|
+
elsif build_manifest.nil?
|
|
86
|
+
warn "note: #{options[:build]} not found — the deps/build stages need it (validate covered deps/package only)"
|
|
87
|
+
end
|
|
57
88
|
begin
|
|
58
|
-
pkg = Crosspack::PackageManifest.load(options[:package])
|
|
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]))
|
|
59
95
|
rescue Crosspack::InvalidManifestError => e
|
|
60
96
|
abort_with e.message
|
|
61
97
|
end
|
|
62
98
|
puts manifest.error_report
|
|
63
99
|
puts pkg.error_report
|
|
64
|
-
|
|
100
|
+
puts build_manifest.error_report if build_manifest
|
|
101
|
+
ok = manifest.valid? && pkg.valid? && (build_manifest.nil? || build_manifest.valid?)
|
|
102
|
+
exit(ok ? 0 : 1)
|
|
65
103
|
when 'matrix'
|
|
66
|
-
abort_with 'Schema is invalid — the matrix cannot be built. Run: crosspack validate' unless manifest.valid?
|
|
67
|
-
puts Crosspack::Matrix.new(manifest).render
|
|
68
|
-
when 'resolve'
|
|
69
|
-
abort_with 'Specify a target: --target debian-12 (see crosspack --help)' unless options[:target]
|
|
70
104
|
begin
|
|
71
|
-
|
|
72
|
-
rescue Crosspack::
|
|
73
|
-
abort_with
|
|
105
|
+
manifest = Crosspack::Manifest.load(File.expand_path(options[:file], options[:root]))
|
|
106
|
+
rescue Crosspack::InvalidManifestError => e
|
|
107
|
+
abort_with e.message
|
|
108
|
+
end
|
|
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
|
|
112
|
+
puts "\n#{Crossbuild::Matrix.new(build_manifest).render}"
|
|
113
|
+
else
|
|
114
|
+
warn "\nnote: #{options[:build]} not found — build matrix not shown"
|
|
74
115
|
end
|
|
116
|
+
when 'resolve'
|
|
117
|
+
target_str = positional
|
|
118
|
+
abort_with 'Specify a target: crosspack resolve debian-12 (see crosspack --help)' unless target_str
|
|
75
119
|
begin
|
|
120
|
+
manifest = Crosspack::Manifest.load(File.expand_path(options[:file], options[:root]))
|
|
121
|
+
target = Crosspack::Target.parse(target_str, arch: options[:arch])
|
|
76
122
|
resolver = Crosspack::Resolver.new(manifest)
|
|
77
123
|
format = options[:format] || target.format
|
|
78
124
|
result = resolver.depends(target, format: format)
|
|
125
|
+
puts "# #{target} (#{format})"
|
|
79
126
|
if result.is_a?(Array)
|
|
80
|
-
puts "# #{target} (#{format})"
|
|
81
127
|
result.each { |d| puts d }
|
|
82
128
|
else
|
|
83
|
-
puts "# #{target} (#{format})"
|
|
84
129
|
puts "Depends: #{result}"
|
|
85
130
|
end
|
|
86
|
-
rescue Crosspack::
|
|
87
|
-
abort_with "
|
|
131
|
+
rescue Crosspack::Target::Error => e
|
|
132
|
+
abort_with "Invalid target #{target_str.inspect}: #{e.message}"
|
|
133
|
+
rescue Crosspack::InvalidManifestError, Crosspack::ResolveError => e
|
|
134
|
+
abort_with e.message
|
|
135
|
+
end
|
|
136
|
+
when 'version'
|
|
137
|
+
abort_with "#{options[:build]} not found — crosspack version needs a build manifest" unless build_manifest
|
|
138
|
+
abort_with 'Schema is invalid. Run: crosspack validate' unless build_manifest.valid?
|
|
139
|
+
begin
|
|
140
|
+
puts Crossbuild::VersionScheme.new(build_manifest.version).compute(root: options[:root])
|
|
141
|
+
rescue Crossbuild::VersionScheme::Error => e
|
|
142
|
+
abort_with e.message
|
|
143
|
+
end
|
|
144
|
+
when 'deps'
|
|
145
|
+
abort_with 'Specify a target: crosspack deps debian-12 (see crosspack --help)' unless positional
|
|
146
|
+
abort_with "#{options[:build]} not found — crosspack deps needs a build manifest" unless build_manifest
|
|
147
|
+
entry_for_target!(build_manifest, positional, Crossbuild::Platform.os, Crossbuild::Platform.arch)
|
|
148
|
+
installer = Crossbuild::DepInstaller.new(build_manifest, root: options[:root])
|
|
149
|
+
begin
|
|
150
|
+
if options[:check_only]
|
|
151
|
+
installer.check_all
|
|
152
|
+
puts "deps on this host:\n#{installer.report}"
|
|
153
|
+
exit(installer.statuses.any? { |s| !s.installed } ? 1 : 0)
|
|
154
|
+
end
|
|
155
|
+
installer.ensure_all
|
|
156
|
+
rescue Crossbuild::Error => e
|
|
157
|
+
abort_with e.message
|
|
158
|
+
end
|
|
159
|
+
puts "deps ready:\n#{installer.report}"
|
|
160
|
+
when 'build'
|
|
161
|
+
abort_with "#{options[:build]} not found — crosspack build needs a build manifest" unless build_manifest
|
|
162
|
+
if options[:all]
|
|
163
|
+
target = nil
|
|
164
|
+
else
|
|
165
|
+
abort_with 'Specify a target: crosspack build ubuntu-24.04 (or --all for every host-buildable entry)' unless positional
|
|
166
|
+
target = positional
|
|
167
|
+
end
|
|
168
|
+
begin
|
|
169
|
+
result = Crossbuild::Builder.new(build_manifest, root: options[:root],
|
|
170
|
+
output_base: options[:output_base], version: options[:version],
|
|
171
|
+
deps: !options[:no_deps]).run(target: target)
|
|
172
|
+
rescue Crossbuild::Error, Crosspack::InvalidManifestError => e
|
|
173
|
+
abort_with e.message
|
|
88
174
|
end
|
|
175
|
+
puts "\n📦 #{result.entries.size} entr#{result.entries.size == 1 ? 'y' : 'ies'} built, version #{result.version}"
|
|
176
|
+
puts 'Pack with: crosspack pack <target>'
|
|
89
177
|
when 'pack'
|
|
90
|
-
abort_with 'Specify a target:
|
|
178
|
+
abort_with 'Specify a target: crosspack pack debian-12' unless positional
|
|
91
179
|
begin
|
|
92
|
-
target = Crosspack::Target.parse(
|
|
180
|
+
target = Crosspack::Target.parse(positional, arch: options[:arch])
|
|
93
181
|
path = Crosspack.pack(
|
|
94
182
|
manifest: options[:package],
|
|
95
183
|
deps: options[:file],
|
|
96
184
|
target: target,
|
|
97
185
|
version: options[:version],
|
|
98
186
|
root: options[:root],
|
|
99
|
-
output_base: options[:output_base]
|
|
187
|
+
output_base: options[:output_base] || 'crosspacks'
|
|
100
188
|
)
|
|
101
189
|
rescue Crosspack::Target::Error => e
|
|
102
|
-
abort_with "Invalid target #{
|
|
190
|
+
abort_with "Invalid target #{positional.inspect}: #{e.message}"
|
|
103
191
|
rescue Crosspack::InvalidManifestError, Crosspack::ResolveError, Crosspack::BuildError => e
|
|
104
192
|
abort_with e.message
|
|
105
193
|
end
|
|
@@ -115,5 +203,5 @@ when 'targets'
|
|
|
115
203
|
root = sources ? File.expand_path(sources, options[:root]) : File.join(options[:root], 'builds')
|
|
116
204
|
puts Crosspack::Builds.report(root)
|
|
117
205
|
else
|
|
118
|
-
abort_with "Unknown command #{command.inspect}. Available: validate, resolve, matrix,
|
|
206
|
+
abort_with "Unknown command #{command.inspect}. Available: deps, build, pack, validate, resolve, matrix, version, targets"
|
|
119
207
|
end
|
|
@@ -24,9 +24,10 @@ module Crossbuild
|
|
|
24
24
|
ARCH_LIST = %w[amd64 x86_64 arm64 aarch64 any].freeze
|
|
25
25
|
ARTIFACT_MODES = %w[symlink copy].freeze
|
|
26
26
|
VERSION_SCHEMES = %w[calver git-tag].freeze
|
|
27
|
-
TOP_LEVEL_KEYS = %w[name version output matrix].freeze
|
|
27
|
+
TOP_LEVEL_KEYS = %w[name version output deps matrix].freeze
|
|
28
28
|
ENTRY_KEYS = %w[id build env steps artifacts].freeze
|
|
29
29
|
ARTIFACTS_KEYS = %w[from include to mode].freeze
|
|
30
|
+
DEP_KEYS = %w[hosts].freeze
|
|
30
31
|
BUILD_RE = /\A(linux|darwin|windows)\/(#{ARCH_LIST.join('|')})\z/.freeze
|
|
31
32
|
NAME_RE = /\A[a-z0-9][a-z0-9+._-]*\z/i.freeze
|
|
32
33
|
|
|
@@ -54,6 +55,22 @@ module Crossbuild
|
|
|
54
55
|
end
|
|
55
56
|
end
|
|
56
57
|
|
|
58
|
+
# A build dependency from the top-level deps: section. `hosts` maps a
|
|
59
|
+
# host selector (distro id from os-release, ID_LIKE token, os name or
|
|
60
|
+
# "*") to a rule with per-system commands:
|
|
61
|
+
# verify: presence probe (default: PATH lookup of the name)
|
|
62
|
+
# install: how to install it (a verify-only rule may omit this)
|
|
63
|
+
class Dependency
|
|
64
|
+
RULE_KEYS = %w[verify install].freeze
|
|
65
|
+
|
|
66
|
+
attr_reader :name, :hosts
|
|
67
|
+
|
|
68
|
+
def initialize(name, hosts)
|
|
69
|
+
@name = name
|
|
70
|
+
@hosts = hosts
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
57
74
|
# Where artifacts come from and which builds/ directories they fan out to.
|
|
58
75
|
class Artifacts
|
|
59
76
|
attr_reader :from, :include, :to, :mode
|
|
@@ -66,7 +83,7 @@ module Crossbuild
|
|
|
66
83
|
end
|
|
67
84
|
end
|
|
68
85
|
|
|
69
|
-
attr_reader :path, :name, :version, :output, :entries, :errors, :warnings
|
|
86
|
+
attr_reader :path, :name, :version, :output, :deps, :entries, :errors, :warnings
|
|
70
87
|
|
|
71
88
|
def self.load(path)
|
|
72
89
|
unless File.file?(path)
|
|
@@ -89,6 +106,7 @@ module Crossbuild
|
|
|
89
106
|
@name = nil
|
|
90
107
|
@version = nil
|
|
91
108
|
@output = 'builds'
|
|
109
|
+
@deps = []
|
|
92
110
|
@entries = []
|
|
93
111
|
@errors = []
|
|
94
112
|
@warnings = []
|
|
@@ -132,6 +150,26 @@ module Crossbuild
|
|
|
132
150
|
@entries.find { |e| e.id == id }
|
|
133
151
|
end
|
|
134
152
|
|
|
153
|
+
# The single matrix entry that distributes artifacts to the given target
|
|
154
|
+
# string (e.g. "ubuntu-24.04"). Raises Crossbuild::Error when no entry
|
|
155
|
+
# claims it or when several do (ambiguous).
|
|
156
|
+
def entry_for_target(raw)
|
|
157
|
+
matches = @entries.select { |e| e.artifacts&.to&.include?(raw) }
|
|
158
|
+
declared = @entries.filter_map { |e| e.artifacts&.to }.flatten.uniq
|
|
159
|
+
if matches.empty?
|
|
160
|
+
raise Error,
|
|
161
|
+
"no matrix entry distributes to target #{raw.inspect}; " \
|
|
162
|
+
"declared targets: #{declared.join(', ')}"
|
|
163
|
+
end
|
|
164
|
+
if matches.size > 1
|
|
165
|
+
raise Error,
|
|
166
|
+
"target #{raw.inspect} is distributed by several entries " \
|
|
167
|
+
"(#{matches.map(&:id).join(', ')}) — crosspack build <target> needs exactly one"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
matches.first
|
|
171
|
+
end
|
|
172
|
+
|
|
135
173
|
private
|
|
136
174
|
|
|
137
175
|
def validate
|
|
@@ -144,6 +182,7 @@ module Crossbuild
|
|
|
144
182
|
validate_name
|
|
145
183
|
validate_version
|
|
146
184
|
validate_output
|
|
185
|
+
validate_deps
|
|
147
186
|
validate_matrix
|
|
148
187
|
end
|
|
149
188
|
|
|
@@ -184,6 +223,76 @@ module Crossbuild
|
|
|
184
223
|
end
|
|
185
224
|
end
|
|
186
225
|
|
|
226
|
+
def validate_deps
|
|
227
|
+
deps = @raw['deps']
|
|
228
|
+
return if deps.nil?
|
|
229
|
+
|
|
230
|
+
unless deps.is_a?(Hash)
|
|
231
|
+
@errors << Issue.new(
|
|
232
|
+
'deps',
|
|
233
|
+
"must be a mapping of dependency name -> {check?, hosts}; expected:\n" \
|
|
234
|
+
" deps:\n" \
|
|
235
|
+
" imagemagick:\n" \
|
|
236
|
+
" check: magick -version\n" \
|
|
237
|
+
" hosts:\n" \
|
|
238
|
+
" ubuntu: sudo apt-get install -y imagemagick"
|
|
239
|
+
)
|
|
240
|
+
return
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
deps.each { |name, body| validate_dep(name, body) }
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def validate_dep(name, body)
|
|
247
|
+
path = "deps.#{name}"
|
|
248
|
+
if name !~ NAME_RE
|
|
249
|
+
@errors << Issue.new(path, "invalid dependency name #{name.inspect} (letters, digits, \"+\", \"_\", \"-\", \".\")")
|
|
250
|
+
return
|
|
251
|
+
end
|
|
252
|
+
unless body.is_a?(Hash)
|
|
253
|
+
@errors << Issue.new(path, 'must be a mapping with the key: hosts')
|
|
254
|
+
return
|
|
255
|
+
end
|
|
256
|
+
check_unknown_keys(path, body.keys, DEP_KEYS)
|
|
257
|
+
|
|
258
|
+
hosts = body['hosts']
|
|
259
|
+
unless hosts.is_a?(Hash) && !hosts.empty?
|
|
260
|
+
@errors << Issue.new(
|
|
261
|
+
"#{path}.hosts",
|
|
262
|
+
"must be a non-empty mapping of host selector -> {verify?, install?}; expected:\n" \
|
|
263
|
+
" deps:\n" \
|
|
264
|
+
" #{name}:\n" \
|
|
265
|
+
" hosts:\n" \
|
|
266
|
+
" ubuntu:\n" \
|
|
267
|
+
" verify: dpkg -s #{name}\n" \
|
|
268
|
+
" install: sudo apt-get install -y #{name}"
|
|
269
|
+
)
|
|
270
|
+
return
|
|
271
|
+
end
|
|
272
|
+
hosts.each { |selector, rule| validate_host_rule(name, selector, rule) }
|
|
273
|
+
return unless valid_dep?(name, body)
|
|
274
|
+
|
|
275
|
+
@deps << Dependency.new(name, hosts)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def validate_host_rule(name, selector, rule)
|
|
279
|
+
path = "deps.#{name}.hosts.#{selector}"
|
|
280
|
+
unless rule.is_a?(Hash) && !rule.empty?
|
|
281
|
+
@errors << Issue.new(path, "must be a mapping with verify and/or install commands, e.g. { install: sudo apt-get install -y #{name} }")
|
|
282
|
+
return
|
|
283
|
+
end
|
|
284
|
+
check_unknown_keys(path, rule.keys, Dependency::RULE_KEYS)
|
|
285
|
+
rule.each do |key, command|
|
|
286
|
+
unless command.is_a?(String) && !command.strip.empty?
|
|
287
|
+
@errors << Issue.new("#{path}.#{key}", 'must be a non-empty shell command')
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def valid_dep?(name, body)
|
|
293
|
+
@errors.none? { |e| e.path.start_with?("deps.#{name}") }
|
|
294
|
+
end
|
|
295
|
+
|
|
187
296
|
def validate_matrix
|
|
188
297
|
matrix = @raw['matrix']
|
|
189
298
|
unless matrix.is_a?(Array) && !matrix.empty?
|