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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 527794ea0ef513791363042fcace5c4a973a859e74c4300dedeec931268b00fc
4
- data.tar.gz: 5bbad1503be58d2190c7f042fdd29ca8271d1d107826d8653589ef17c8587a9d
3
+ metadata.gz: 4f1b1bcc611377577384101685763d88198c0b27dc9323a8370fda526b7440d8
4
+ data.tar.gz: b3c121e02cfebbcfe8112bd4597f2009b35aecee53019a0c51aa2bb791f51007
5
5
  SHA512:
6
- metadata.gz: 465b113f78d7e0dce817e0a65c122437998938dbb66cd438c63762289dcc1a9c4a3ccf4df9a7639f73ec983aa20f31a94c34d357118efbdfdfb1415b9828fad1
7
- data.tar.gz: 021f427e66d2a9c58eb02dde0907cfb7bf5f8c89a081018b2839df819a7c478dc4952f7fc9ba7246c4b2030488065b644401edfd32282ca987809019675c0670
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 with one command and a staged pipeline:
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
- ## build.yamlwhat and where to build
25
+ ## crosspack.ymlone 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
- output: builds # default builds; mirrors package.yaml `sources`
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
-
47
- matrix:
48
- - build: linux/amd64 # os/arch this entry builds on (arch may be "any")
49
- env: # extra env for the steps of this entry
50
- CGO_ENABLED: "1"
51
- steps: # shell commands, run from the project root
52
- - wails build -platform linux/amd64 -ldflags "-X myapp/internal/app.appVersion={{version}}"
53
- - cargo build --release -p helper --locked
54
- artifacts:
55
- from: build/bin # what got built
56
- include: [myapp, helper, "*.onnx", "*.so*"] # default: ["*"]
57
- to: [ubuntu-22.04, ubuntu-24.04, debian-12, debian-13]
58
- mode: symlink # symlink | copy, default symlink
59
-
60
- - id: windows # defaults to the build platform string
61
- build: windows/amd64
62
- steps:
63
- - wails build -platform windows/amd64 -ldflags "-X ...={{version}}"
64
- artifacts:
65
- from: build/bin
66
- include: [myapp.exe]
67
- to: [windows-11.0]
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:` 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.
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
- ## package.yaml + deps.yaml what and how to pack
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
- manifest.
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 # package.yaml, deps.yaml and build.yaml
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('build.yaml', root: Dir.pwd) # all host entries
195
- Crossbuild.build('build.yaml', target: 'ubuntu-24.04') # the stages' build, library-level
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
- manifest: 'package.yaml', deps: 'deps.yaml',
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
- Lower-level pieces are public too: on the build side `Crossbuild::
206
- BuildManifest`, `VersionScheme`, `Runner`, `Distributor`, `Matrix`,
207
- `Builder`, `DepInstaller`, `Platform`; on the pack side `Crosspack::
208
- PackageManifest`, `Manifest`, `Resolver`, `Matrix`, `Target`, `Builds`,
209
- `Builders::*`.
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, notarization
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: 'deps.yaml', package: 'package.yaml', build: 'build.yaml', arch: 'amd64',
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 deps.yaml (default: ./deps.yaml)') { |v| options[:file] = v }
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 deps.yaml path for validate.
54
+ # resolve), a crosspack.yml path for validate.
57
55
  positional = ARGV.shift if ARGV.first && !ARGV.first.start_with?('-')
58
56
 
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
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
- 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
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 if build_manifest
101
- ok = manifest.valid? && pkg.valid? && (build_manifest.nil? || build_manifest.valid?)
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
- begin
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
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: #{options[:build]} not found — build matrix not shown"
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 = Crosspack::Manifest.load(File.expand_path(options[:file], options[:root]))
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
- 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?
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
- abort_with "#{options[:build]} not found — crosspack deps needs a build manifest" unless build_manifest
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
- abort_with "#{options[:build]} not found — crosspack build needs a build manifest" unless build_manifest
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
- manifest: options[:package],
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
- pkg_path = File.expand_path(options[:package], options[:root])
198
- sources = begin
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.yaml + human explanation.
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 < StandardError; end
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.yaml schema is invalid (#{@errors.size} errors):"]
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}: schema is valid (#{@entries.size} #{noun}: #{@entries.map(&:id).join(', ')})."
119
+ lines << "#{path}: the build: section is valid (#{@entries.size} #{noun}: #{@entries.map(&:id).join(', ')})."
136
120
  else
137
- lines << "#{path}: build.yaml schema is invalid (#{@errors.size} errors):"
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)', 'file must be a non-empty mapping with at least a name and a matrix')
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: the application name, e.g. myapp')
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 build.yaml. Host selectors (distro id, ID_LIKE tokens, os,
6
- # "*" — see Platform.selectors) are matched against each dependency's
7
- # hosts: rules; the first hit wins. Every rule carries its own commands:
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
@@ -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.yaml matrix — host: #{host}"]
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?
@@ -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 build.yaml
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: matrix of build.yaml — distro id,
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
@@ -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
- # build.yaml works under POSIX shells and cmd.exe. The same facts are also
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crossbuild
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crossbuild
4
- # Computes the build version from the `version:` field of build.yaml.
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} in build.yaml)" if value.empty?
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('build.yaml') runs every
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 = manifest.is_a?(BuildManifest) ? manifest : BuildManifest.load(manifest)
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.yaml, not this file.
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.yaml (resolved per target), never from ELF soname scanning.
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
@@ -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.yaml still resolves, but produces a warning (possible typo).
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.yaml + human explanation.
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.yaml. Root keys are canonical dependency
35
- # names; each maps to { targets: { family: { "version": [pkgs] } } } or a
36
- # scalar verdict for a whole family. A reserved top-level `version:` key
37
- # declares the schema version (see SCHEMA_VERSION).
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.yaml schema version this crosspack understands. Files
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.yaml schema is invalid (#{@errors.size} errors):"]
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}: schema is valid."
75
+ lines << "#{path}: the deps: section is valid."
91
76
  else
92
- lines << "#{path}: deps.yaml schema is invalid (#{@errors.size} errors):"
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.yaml declares schema version #{v}, " \
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
- 'file must be a non-empty mapping: canonical name -> { targets: ... }'
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
@@ -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.yaml itself.
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.yaml is empty — nothing to show.' if columns.empty?
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.yaml): declares WHAT and HOW to pack —
7
- # payload, executables, symlinks, desktop entry, icon. Compilation is not
8
- # crosspack's business; artifacts are picked from `sources` as-is.
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[name maintainer description sources prefix payload].freeze
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.yaml schema is invalid (#{@errors.size} errors):"]
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}: schema is valid."
38
+ "#{path}: the package: section is valid."
55
39
  else
56
- ["#{path}: package.yaml schema is invalid (#{@errors.size} errors):",
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)', 'file must be a non-empty mapping: name, maintainer, payload, ...')
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
 
@@ -4,22 +4,21 @@ require 'fileutils'
4
4
  require 'tmpdir'
5
5
 
6
6
  module Crosspack
7
- # Single entry point: takes a compiled artifacts directory (per
8
- # package.yaml), resolves dependencies for the target (per deps.yaml) and
9
- # produces a native package under output_base/<family>/<version>/<arch>/.
10
- # The builder is chosen by the target format — deb, rpm or PKGBUILD.
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(manifest:, deps:, target:, version: nil, root: Dir.pwd,
14
+ def self.pack(config:, target:, version: nil, root: Dir.pwd,
15
15
  output_base: 'crosspacks')
16
- new(manifest: manifest, deps: deps, target: target, version: version,
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(manifest:, deps:, target:, version:, root:, output_base:)
21
- @pkg_path = manifest
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
- @pkg = PackageManifest.load(@pkg_path)
31
- @deps = Manifest.load(@deps_path)
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?
@@ -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.yaml.
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.yaml has no rules for family #{family} (target #{target}). " \
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crosspack
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  end
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(manifest: 'package.yaml',
34
- # deps: 'deps.yaml', target: Target.parse('debian-12'), version: '1.0-1')
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.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