crosspack 0.5.1 → 0.6.2
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 +43 -0
- data/README.md +27 -2
- data/exe/crosspack +55 -20
- data/lib/crossbuild/build_manifest.rb +2 -1
- data/lib/crossbuild/dep_installer.rb +3 -1
- data/lib/crosspack/colors.rb +33 -0
- data/lib/crosspack/package_manifest.rb +123 -7
- data/lib/crosspack/packer.rb +6 -6
- data/lib/crosspack/target.rb +39 -0
- data/lib/crosspack/version.rb +1 -1
- data/lib/crosspack.rb +1 -0
- data/logo.png +0 -0
- metadata +20 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 369c6df533b4a7e576fb1bfe4af308d0e422082934a533995355eb1bec6f8854
|
|
4
|
+
data.tar.gz: 65eca41ff0574b5460dffcfe9a2e33f36936ddff9184a4ed2908df0290f711e8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7449ad323b7beb15a046ced0dfb3e47caf437d3844f660af93d4da19e3d2fd74cc64ed62f3e531589e6079c49309ee82ec7c1959f3cdef093cc517a9e67cc2d7
|
|
7
|
+
data.tar.gz: e58bf61ce945544bc8ba0dd96c41760ffa8637cdb934bef375c18c3d70ef3aa465369a8122455008964cd608eabf066e79a6dfe60fa5f5bca87051d42b5e5bc0
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,49 @@ 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.6.2] - 2026-09-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Colored CLI output via the new `colorize` runtime dependency: success
|
|
12
|
+
messages green, errors red, validation warnings yellow (the `ok`/`MISSING`
|
|
13
|
+
statuses in the deps report included). Colors apply only to a TTY and
|
|
14
|
+
respect `NO_COLOR`, so piped and logged output stays plain.
|
|
15
|
+
- The gem now ships `logo.png` in its files.
|
|
16
|
+
|
|
17
|
+
## [0.6.1] - 2026-09-13
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- `crosspack deps` no longer dumps a Ruby backtrace when the (detected or
|
|
21
|
+
given) target has no matrix entry — it aborts with the plain message, now
|
|
22
|
+
with an actionable hint (pass a declared target or extend artifacts.to).
|
|
23
|
+
- `crosspack --version` (bare, also `-v`) prints the gem version instead of
|
|
24
|
+
demanding an argument; `--version X` keeps overriding the build/pack
|
|
25
|
+
version.
|
|
26
|
+
|
|
27
|
+
[0.6.2]: https://github.com/OrelSokolov/crosspack/releases/tag/v0.6.2
|
|
28
|
+
[0.6.1]: https://github.com/OrelSokolov/crosspack/releases/tag/v0.6.1
|
|
29
|
+
|
|
30
|
+
## [0.6.0] - 2026-09-13
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- Host target default: `crosspack deps` / `build` / `pack` invoked without a
|
|
34
|
+
target run for the current host — `macos`, `windows-11.0` or the distro
|
|
35
|
+
family + version from `/etc/os-release` (unknown distro ids fall back to
|
|
36
|
+
the first known `ID_LIKE` token; unrecognizable hosts ask for an explicit
|
|
37
|
+
target).
|
|
38
|
+
- Per-target payload names: `package.payload` entries may now be written as a
|
|
39
|
+
mapping `common key -> {target selector -> real file name}` instead of a
|
|
40
|
+
flat list. The key is the common name (used verbatim on every target
|
|
41
|
+
unless overridden); selectors are an exact target (`windows-11.0`), a
|
|
42
|
+
family (`windows`) or `"*"` (exact target wins, then family, then `"*"`).
|
|
43
|
+
An entry with a selector map applies only to targets it covers, so one
|
|
44
|
+
payload lists `app`/`app.exe`, `.so` libraries and Windows DLLs together —
|
|
45
|
+
each target resolves to the files its build actually produced. The flat
|
|
46
|
+
list form remains valid (same name everywhere). `executables` keeps naming
|
|
47
|
+
common keys and resolves per target at pack time.
|
|
48
|
+
|
|
49
|
+
[0.6.0]: https://github.com/OrelSokolov/crosspack/releases/tag/v0.6.0
|
|
50
|
+
|
|
8
51
|
## [0.5.1] - 2026-09-13
|
|
9
52
|
|
|
10
53
|
### Fixed
|
data/README.md
CHANGED
|
@@ -22,6 +22,15 @@ crosspack build ubuntu-24.04
|
|
|
22
22
|
crosspack pack ubuntu-24.04
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
The target may be omitted — the stage commands then run for the current
|
|
26
|
+
host: `macos`, `windows-11.0` or the distro family + version from
|
|
27
|
+
`/etc/os-release` (unknown ids fall back to the first known `ID_LIKE`
|
|
28
|
+
token):
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
crosspack deps && crosspack build && crosspack pack
|
|
32
|
+
```
|
|
33
|
+
|
|
25
34
|
## crosspack.yml — one file, three sections
|
|
26
35
|
|
|
27
36
|
The top level carries the shared facts — `name` (required once) and the
|
|
@@ -91,11 +100,27 @@ package:
|
|
|
91
100
|
lib_dir: lib/myapp # optional, default lib/<name>
|
|
92
101
|
|
|
93
102
|
payload: # files from sources -> <prefix>/<lib_dir>/
|
|
94
|
-
- myapp
|
|
103
|
+
- myapp # flat form: the same name on every target
|
|
95
104
|
- helper
|
|
96
105
|
- model.onnx
|
|
97
106
|
|
|
98
|
-
|
|
107
|
+
# Mapping form: per-target file names. The key is the common name (and the
|
|
108
|
+
# default file name); selectors are an exact target (windows-11.0), a
|
|
109
|
+
# family (windows), an os (linux — every distro family) or "*" — exact
|
|
110
|
+
# target wins, then family, then os, then "*". An entry with selectors
|
|
111
|
+
# applies only to targets it covers, so .so libraries and Windows DLLs can
|
|
112
|
+
# share one payload:
|
|
113
|
+
#
|
|
114
|
+
# payload:
|
|
115
|
+
# myapp:
|
|
116
|
+
# "*": myapp
|
|
117
|
+
# windows: myapp.exe
|
|
118
|
+
# libonnxruntime.so.1:
|
|
119
|
+
# linux: libonnxruntime.so.1
|
|
120
|
+
# onnxruntime.dll:
|
|
121
|
+
# windows: onnxruntime.dll
|
|
122
|
+
|
|
123
|
+
executables: [myapp, helper] # common keys; chmod 755, resolved per target
|
|
99
124
|
|
|
100
125
|
links: # symlinks relative to prefix
|
|
101
126
|
bin/myapp: ../lib/myapp/myapp
|
data/exe/crosspack
CHANGED
|
@@ -12,25 +12,48 @@ rescue LoadError
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def abort_with(message)
|
|
15
|
-
warn message
|
|
15
|
+
warn Crosspack::Colors.red(message)
|
|
16
16
|
exit 1
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# Validate reports line by line: valid → green, invalid → red, warnings → yellow.
|
|
20
|
+
def colorized_report(text)
|
|
21
|
+
text.lines.map do |line|
|
|
22
|
+
chomped = line.chomp
|
|
23
|
+
colored =
|
|
24
|
+
if chomped.include?('is invalid')
|
|
25
|
+
Crosspack::Colors.red(chomped)
|
|
26
|
+
elsif chomped.include?('is valid')
|
|
27
|
+
Crosspack::Colors.green(chomped)
|
|
28
|
+
elsif chomped.strip.start_with?('⚠')
|
|
29
|
+
Crosspack::Colors.yellow(chomped)
|
|
30
|
+
end
|
|
31
|
+
colored ? "#{colored}\n" : line
|
|
32
|
+
end.join
|
|
33
|
+
end
|
|
34
|
+
|
|
19
35
|
options = { file: Crosspack::Config::DEFAULT_PATH, arch: 'amd64',
|
|
20
36
|
format: nil, version: nil, root: Dir.pwd, output_base: nil,
|
|
21
37
|
check_only: false, no_deps: false, all: false }
|
|
38
|
+
|
|
39
|
+
# `crosspack --version` (also bare -v) prints the gem version like any other
|
|
40
|
+
# CLI; with a value the flag stays the build/pack version override.
|
|
41
|
+
if ARGV.size == 1 && %w[--version -v].include?(ARGV.first)
|
|
42
|
+
puts "crosspack #{Crosspack::VERSION}"
|
|
43
|
+
exit
|
|
44
|
+
end
|
|
22
45
|
parser = OptionParser.new do |opts|
|
|
23
46
|
opts.banner = "crosspack #{Crosspack::VERSION} — staged pipeline: deps → build → pack\n" \
|
|
24
47
|
"Usage: crosspack <command> [options]\n" \
|
|
25
|
-
"Stages: deps <target> [--check] verify/install build deps
|
|
26
|
-
" build <target
|
|
27
|
-
" pack <target> [--version X] package builds/<target> (version from the build stamp)\n" \
|
|
48
|
+
"Stages: deps [<target>] [--check] verify/install build deps (default: this host)\n" \
|
|
49
|
+
" build [<target>]|--all compile into builds/ (default: this host)\n" \
|
|
50
|
+
" pack [<target>] [--version X] package builds/<target> (default: this host; version from the build stamp)\n" \
|
|
28
51
|
'Commands: validate, resolve, matrix, version, targets'
|
|
29
52
|
|
|
30
53
|
opts.on('-f', '--file PATH', 'path to crosspack.yml (default: ./crosspack.yml)') { |v| options[:file] = v }
|
|
31
54
|
opts.on('--arch ARCH', 'architecture: amd64/x86_64, arm64/aarch64 (default: amd64)') { |v| options[:arch] = v }
|
|
32
55
|
opts.on('--format FORMAT', 'output format for resolve: deb, rpm, pkgbuild (default: derived from the target)') { |v| options[:format] = v.to_sym }
|
|
33
|
-
opts.on('--version VERSION', 'override the version for build/pack') { |v| options[:version] = v }
|
|
56
|
+
opts.on('--version VERSION', 'override the version for build/pack; a bare `crosspack --version` prints the gem version') { |v| options[:version] = v }
|
|
34
57
|
opts.on('--root PATH', 'project root for relative paths (default: .)') { |v| options[:root] = v }
|
|
35
58
|
opts.on('--output-base DIR', 'output directory (build stage: the manifest output, default builds; pack: default ./crosspacks)') { |v| options[:output_base] = v }
|
|
36
59
|
opts.on('--check', 'with `deps`: only report presence, never install (exit 1 if missing)') { options[:check_only] = true }
|
|
@@ -79,6 +102,19 @@ def entry_for_target!(manifest, target, host_os, host_arch)
|
|
|
79
102
|
end
|
|
80
103
|
|
|
81
104
|
entry
|
|
105
|
+
rescue Crossbuild::Error => e
|
|
106
|
+
abort_with e.message
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Stage commands (deps/build/pack) run for the current host when no target
|
|
110
|
+
# is given: macos, windows-11.0 or the distro from /etc/os-release.
|
|
111
|
+
def host_target!(command)
|
|
112
|
+
raw = Crosspack::Target.host_string
|
|
113
|
+
abort_with "Could not detect this host's target — pass one explicitly: crosspack #{command} <target> " \
|
|
114
|
+
'(families: ubuntu-24.04, debian-12, fedora-41, arch, macos, windows-11.0)' if raw.nil?
|
|
115
|
+
|
|
116
|
+
puts "→ no target given; using this host's target: #{raw}"
|
|
117
|
+
raw
|
|
82
118
|
end
|
|
83
119
|
|
|
84
120
|
case command
|
|
@@ -86,10 +122,10 @@ when 'validate'
|
|
|
86
122
|
deps_manifest = config.deps_manifest
|
|
87
123
|
pkg = config.package_manifest
|
|
88
124
|
build_manifest = config.build_manifest
|
|
89
|
-
puts config.error_report
|
|
90
|
-
puts deps_manifest.error_report
|
|
91
|
-
puts pkg.error_report
|
|
92
|
-
puts build_manifest.error_report
|
|
125
|
+
puts colorized_report(config.error_report)
|
|
126
|
+
puts colorized_report(deps_manifest.error_report)
|
|
127
|
+
puts colorized_report(pkg.error_report)
|
|
128
|
+
puts colorized_report(build_manifest.error_report)
|
|
93
129
|
ok = config.valid? && deps_manifest.valid? && pkg.valid? && build_manifest.valid?
|
|
94
130
|
exit(ok ? 0 : 1)
|
|
95
131
|
when 'matrix'
|
|
@@ -131,12 +167,12 @@ when 'version'
|
|
|
131
167
|
abort_with e.message
|
|
132
168
|
end
|
|
133
169
|
when 'deps'
|
|
134
|
-
|
|
170
|
+
target_str = positional || host_target!('deps')
|
|
135
171
|
build_manifest = config.build_manifest
|
|
136
172
|
abort_with 'the build: section is invalid — run: crosspack validate' unless build_manifest.valid?
|
|
137
|
-
entry_for_target!(build_manifest, positional, Crossbuild::Platform.os, Crossbuild::Platform.arch)
|
|
138
|
-
installer = Crossbuild::DepInstaller.new(build_manifest, root: options[:root])
|
|
139
173
|
begin
|
|
174
|
+
entry_for_target!(build_manifest, target_str, Crossbuild::Platform.os, Crossbuild::Platform.arch)
|
|
175
|
+
installer = Crossbuild::DepInstaller.new(build_manifest, root: options[:root])
|
|
140
176
|
if options[:check_only]
|
|
141
177
|
installer.check_all
|
|
142
178
|
puts "deps on this host:\n#{installer.report}"
|
|
@@ -146,15 +182,14 @@ when 'deps'
|
|
|
146
182
|
rescue Crossbuild::Error => e
|
|
147
183
|
abort_with e.message
|
|
148
184
|
end
|
|
149
|
-
puts "deps ready:\n#{installer.report}"
|
|
185
|
+
puts Crosspack::Colors.green("deps ready:\n#{installer.report}")
|
|
150
186
|
when 'build'
|
|
151
187
|
build_manifest = config.build_manifest
|
|
152
188
|
abort_with 'the build: section is invalid — run: crosspack validate' unless build_manifest.valid?
|
|
153
189
|
if options[:all]
|
|
154
190
|
target = nil
|
|
155
191
|
else
|
|
156
|
-
|
|
157
|
-
target = positional
|
|
192
|
+
target = positional || host_target!('build')
|
|
158
193
|
end
|
|
159
194
|
begin
|
|
160
195
|
result = Crossbuild::Builder.new(build_manifest, root: options[:root],
|
|
@@ -163,12 +198,12 @@ when 'build'
|
|
|
163
198
|
rescue Crossbuild::Error, Crosspack::InvalidManifestError => e
|
|
164
199
|
abort_with e.message
|
|
165
200
|
end
|
|
166
|
-
puts "\n📦 #{result.entries.size} entr#{result.entries.size == 1 ? 'y' : 'ies'} built, version #{result.version}"
|
|
201
|
+
puts Crosspack::Colors.green("\n📦 #{result.entries.size} entr#{result.entries.size == 1 ? 'y' : 'ies'} built, version #{result.version}")
|
|
167
202
|
puts 'Pack with: crosspack pack <target>'
|
|
168
203
|
when 'pack'
|
|
169
|
-
|
|
204
|
+
target_str = positional || host_target!('pack')
|
|
170
205
|
begin
|
|
171
|
-
target = Crosspack::Target.parse(
|
|
206
|
+
target = Crosspack::Target.parse(target_str, arch: options[:arch])
|
|
172
207
|
path = Crosspack.pack(
|
|
173
208
|
config: config,
|
|
174
209
|
target: target,
|
|
@@ -177,11 +212,11 @@ when 'pack'
|
|
|
177
212
|
output_base: options[:output_base] || 'crosspacks'
|
|
178
213
|
)
|
|
179
214
|
rescue Crosspack::Target::Error => e
|
|
180
|
-
abort_with "Invalid target #{
|
|
215
|
+
abort_with "Invalid target #{target_str.inspect}: #{e.message}"
|
|
181
216
|
rescue Crosspack::InvalidManifestError, Crosspack::ResolveError, Crosspack::BuildError => e
|
|
182
217
|
abort_with e.message
|
|
183
218
|
end
|
|
184
|
-
puts "📦 #{path}"
|
|
219
|
+
puts Crosspack::Colors.green("📦 #{path}")
|
|
185
220
|
when 'targets'
|
|
186
221
|
# What can actually be packed: builds tree + resolvable deps per target.
|
|
187
222
|
pkg = config.package_manifest
|
|
@@ -143,7 +143,8 @@ module Crossbuild
|
|
|
143
143
|
if matches.empty?
|
|
144
144
|
raise Error,
|
|
145
145
|
"no matrix entry distributes to target #{raw.inspect}; " \
|
|
146
|
-
"declared targets: #{declared.join(', ')}"
|
|
146
|
+
"declared targets: #{declared.join(', ')}. Pass one of them explicitly, " \
|
|
147
|
+
"or add #{raw.inspect} to the entry's artifacts.to in crosspack.yml"
|
|
147
148
|
end
|
|
148
149
|
if matches.size > 1
|
|
149
150
|
raise Error,
|
|
@@ -53,8 +53,10 @@ module Crossbuild
|
|
|
53
53
|
return ' (no deps declared)' if @manifest.deps.empty?
|
|
54
54
|
|
|
55
55
|
(@statuses || check_all).map do |s|
|
|
56
|
+
state = format('%-8s', s.installed ? 'ok' : 'MISSING')
|
|
57
|
+
state = Crosspack::Colors.public_send(s.installed ? :green : :red, state)
|
|
56
58
|
format(' %-20s %-8s %-8s %s',
|
|
57
|
-
s.dep,
|
|
59
|
+
s.dep, state, s.selector, s.command)
|
|
58
60
|
end.join("\n")
|
|
59
61
|
end
|
|
60
62
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'colorize'
|
|
4
|
+
|
|
5
|
+
module Crosspack
|
|
6
|
+
# CLI message coloring (green = success, red = errors, yellow = warnings),
|
|
7
|
+
# powered by the colorize gem. Colors are turned off automatically when the
|
|
8
|
+
# output is not a TTY or NO_COLOR is set, so piped and logged output stays
|
|
9
|
+
# clean.
|
|
10
|
+
module Colors
|
|
11
|
+
class << self
|
|
12
|
+
attr_writer :enabled
|
|
13
|
+
|
|
14
|
+
def enabled?
|
|
15
|
+
return @enabled unless @enabled.nil?
|
|
16
|
+
|
|
17
|
+
ENV['NO_COLOR'].nil? && ($stdout.tty? || $stderr.tty?)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def red(text)
|
|
21
|
+
enabled? ? text.to_s.colorize(:red) : text
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def green(text)
|
|
25
|
+
enabled? ? text.to_s.colorize(:green) : text
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def yellow(text)
|
|
29
|
+
enabled? ? text.to_s.colorize(:yellow) : text
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -5,10 +5,25 @@ module Crosspack
|
|
|
5
5
|
# WHAT and HOW to pack — payload, executables, symlinks, desktop entry,
|
|
6
6
|
# icon. Compilation is not crosspack's business; artifacts are picked from
|
|
7
7
|
# `sources` as-is.
|
|
8
|
+
#
|
|
9
|
+
# Payload entries may carry per-target file names: the key is the common
|
|
10
|
+
# name (used verbatim everywhere unless a selector map says otherwise),
|
|
11
|
+
# and each selector (exact target "windows-11.0", family "windows" or "*")
|
|
12
|
+
# maps to the real file name that target's build produces. An entry with a
|
|
13
|
+
# selector map applies only to targets it covers — a Windows build dir has
|
|
14
|
+
# no .so libraries and vice versa.
|
|
8
15
|
class PackageManifest
|
|
9
16
|
REQUIRED = %w[maintainer description sources prefix payload].freeze
|
|
10
17
|
OPTIONAL = %w[name summary license section lib_dir executables links desktop icon].freeze
|
|
11
18
|
|
|
19
|
+
# Payload selectors may name an os in addition to a family: every distro
|
|
20
|
+
# family packs a linux target, so "linux:" covers deb/rpm/PKGBUILD
|
|
21
|
+
# without enumerating them (macos and windows are their own families).
|
|
22
|
+
OS_BY_FAMILY = Hash.new('linux').merge(macos: 'macos', windows: 'windows').freeze
|
|
23
|
+
|
|
24
|
+
# One payload file: its common key plus the per-target name overrides.
|
|
25
|
+
PayloadFile = Struct.new(:key, :names, keyword_init: true)
|
|
26
|
+
|
|
12
27
|
attr_reader :path, :data, :errors, :warnings
|
|
13
28
|
|
|
14
29
|
def initialize(path, raw)
|
|
@@ -17,6 +32,7 @@ module Crosspack
|
|
|
17
32
|
@raw = raw
|
|
18
33
|
@errors = []
|
|
19
34
|
@warnings = []
|
|
35
|
+
@payload_entries = []
|
|
20
36
|
validate
|
|
21
37
|
end
|
|
22
38
|
|
|
@@ -72,14 +88,40 @@ module Crosspack
|
|
|
72
88
|
@data['prefix']
|
|
73
89
|
end
|
|
74
90
|
|
|
91
|
+
# Common keys of the payload entries (the executables list and links
|
|
92
|
+
# reference these); use payload_for(target) for the real file names.
|
|
75
93
|
def payload
|
|
76
|
-
|
|
94
|
+
@payload_entries.map(&:key)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Real file names the given target's package carries. Every entry
|
|
98
|
+
# resolves through its selector map — exact target string, then family,
|
|
99
|
+
# then "*"; entries without a map use their key on every target; entries
|
|
100
|
+
# whose selectors do not cover the target are skipped.
|
|
101
|
+
def payload_for(target)
|
|
102
|
+
@payload_entries.filter_map { |entry| resolve_entry(entry, target) }
|
|
77
103
|
end
|
|
78
104
|
|
|
79
105
|
def executables
|
|
80
106
|
Array(@data['executables'])
|
|
81
107
|
end
|
|
82
108
|
|
|
109
|
+
# Executable file names for the target. The executables list names
|
|
110
|
+
# common payload keys; they resolve to the target's real file names.
|
|
111
|
+
def executables_for(target)
|
|
112
|
+
executables.map do |key|
|
|
113
|
+
entry = @payload_entries.find { |e| e.key == key }
|
|
114
|
+
resolved = entry && resolve_entry(entry, target)
|
|
115
|
+
if resolved.nil?
|
|
116
|
+
raise InvalidManifestError,
|
|
117
|
+
"executables: #{key.inspect} resolves to no file for target #{target} — " \
|
|
118
|
+
'its payload entry has no name for that target'
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
resolved
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
83
125
|
def summary
|
|
84
126
|
@data['summary'] || description.lines.map(&:chomp).reject(&:empty?).first || name
|
|
85
127
|
end
|
|
@@ -153,17 +195,91 @@ module Crosspack
|
|
|
153
195
|
value = @data['payload']
|
|
154
196
|
return if value.nil? # already reported as missing
|
|
155
197
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
198
|
+
@payload_entries =
|
|
199
|
+
if value.is_a?(Array)
|
|
200
|
+
validate_payload_list(value)
|
|
201
|
+
elsif value.is_a?(Hash)
|
|
202
|
+
validate_payload_map(value)
|
|
203
|
+
else
|
|
204
|
+
@errors << Issue.new('payload', 'must be a non-empty list of artifact files or a mapping: key -> {target -> real file name}')
|
|
205
|
+
[]
|
|
206
|
+
end
|
|
207
|
+
return unless @payload_entries.empty?
|
|
208
|
+
|
|
209
|
+
@errors << Issue.new('payload', 'must name at least one artifact file from sources')
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Legacy/simple form: a flat list — every name applies to every target.
|
|
213
|
+
def validate_payload_list(value)
|
|
214
|
+
value.each_with_index.with_object([]) do |(item, i), entries|
|
|
215
|
+
if item.is_a?(String) && !item.strip.empty? && !item.start_with?('/')
|
|
216
|
+
entries << PayloadFile.new(key: item, names: {})
|
|
217
|
+
else
|
|
218
|
+
@errors << Issue.new("payload[#{i}]", 'file name must be a non-empty relative string (no leading "/")')
|
|
219
|
+
end
|
|
159
220
|
end
|
|
160
|
-
|
|
161
|
-
next if item.is_a?(String) && !item.strip.empty? && !item.start_with?('/')
|
|
221
|
+
end
|
|
162
222
|
|
|
163
|
-
|
|
223
|
+
# Mapping form: common key -> null (key everywhere) or a selector map
|
|
224
|
+
# (exact target / family / "*") of real file names per target.
|
|
225
|
+
def validate_payload_map(value)
|
|
226
|
+
value.each_with_object([]) do |(key, names), entries|
|
|
227
|
+
if !key.is_a?(String) || key.strip.empty? || key.start_with?('/')
|
|
228
|
+
@errors << Issue.new("payload.#{key.inspect}", 'must be a non-empty relative file name (no leading "/")')
|
|
229
|
+
next
|
|
230
|
+
end
|
|
231
|
+
if names.nil? || names == {}
|
|
232
|
+
entries << PayloadFile.new(key: key, names: {})
|
|
233
|
+
next
|
|
234
|
+
end
|
|
235
|
+
unless names.is_a?(Hash)
|
|
236
|
+
@errors << Issue.new("payload.#{key}", 'must be null or a mapping of target selector -> real file name')
|
|
237
|
+
next
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
resolved = {}
|
|
241
|
+
entry_valid = true
|
|
242
|
+
names.each do |selector, name|
|
|
243
|
+
unless valid_selector?(selector)
|
|
244
|
+
@errors << Issue.new("payload.#{key}.#{selector}",
|
|
245
|
+
"unknown target selector (allowed: \"*\", an os (linux) or a family " \
|
|
246
|
+
"(#{Target::FAMILIES.join(', ')}), optionally with a version, e.g. windows-11.0)")
|
|
247
|
+
entry_valid = false
|
|
248
|
+
next
|
|
249
|
+
end
|
|
250
|
+
if !name.is_a?(String) || name.strip.empty? || name.start_with?('/')
|
|
251
|
+
@errors << Issue.new("payload.#{key}.#{selector}", 'file name must be a non-empty relative string (no leading "/")')
|
|
252
|
+
entry_valid = false
|
|
253
|
+
next
|
|
254
|
+
end
|
|
255
|
+
resolved[selector] = name
|
|
256
|
+
end
|
|
257
|
+
entries << PayloadFile.new(key: key, names: resolved) if entry_valid
|
|
164
258
|
end
|
|
165
259
|
end
|
|
166
260
|
|
|
261
|
+
def valid_selector?(selector)
|
|
262
|
+
return true if selector == '*'
|
|
263
|
+
return true if selector == 'linux' # the one os selector (macos/windows are families)
|
|
264
|
+
|
|
265
|
+
Target.parse(selector.to_s)
|
|
266
|
+
true
|
|
267
|
+
rescue Target::Error
|
|
268
|
+
false
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Exact target string ("windows-11.0") wins over the family ("windows"),
|
|
272
|
+
# then the os ("linux"), then "*"; an entry without a map carries its
|
|
273
|
+
# key on every target.
|
|
274
|
+
def resolve_entry(entry, target)
|
|
275
|
+
return entry.key if entry.names.empty?
|
|
276
|
+
|
|
277
|
+
entry.names[target.to_s] ||
|
|
278
|
+
entry.names[target.family.to_s] ||
|
|
279
|
+
entry.names[OS_BY_FAMILY[target.family]] ||
|
|
280
|
+
entry.names['*']
|
|
281
|
+
end
|
|
282
|
+
|
|
167
283
|
def validate_executables
|
|
168
284
|
value = @data['executables']
|
|
169
285
|
return if value.nil?
|
data/lib/crosspack/packer.rb
CHANGED
|
@@ -96,7 +96,7 @@ module Crosspack
|
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
lib_dst = File.join(@pkg.prefix, @pkg.lib_dir)
|
|
99
|
-
files = @pkg.
|
|
99
|
+
files = @pkg.payload_for(@target).to_h { |n| [File.join(src_dir, n), File.join(lib_dst, n)] }
|
|
100
100
|
|
|
101
101
|
if @pkg.icon
|
|
102
102
|
icon_src = File.expand_path(@pkg.icon, @root)
|
|
@@ -110,7 +110,7 @@ module Crosspack
|
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
symlinks = @pkg.links.to_h { |link, target| [File.join(@pkg.prefix, link), target] }
|
|
113
|
-
executables = @pkg.
|
|
113
|
+
executables = @pkg.executables_for(@target).map { |exe| File.join(lib_dst, exe) }
|
|
114
114
|
|
|
115
115
|
{ files: files, symlinks: symlinks, executables: executables,
|
|
116
116
|
src_dir: src_dir, lib_dst: lib_dst }
|
|
@@ -192,7 +192,7 @@ module Crosspack
|
|
|
192
192
|
depends: resolved_depends(:pkgbuild),
|
|
193
193
|
arch: [@target.package_arch(:pkgbuild)],
|
|
194
194
|
# PKGBUILD sources are archive-relative names, not local paths.
|
|
195
|
-
files: @pkg.
|
|
195
|
+
files: @pkg.payload_for(@target).to_h { |n| [n, File.join(@pkg_data[:lib_dst], n)] },
|
|
196
196
|
symlinks: @pkg_data[:symlinks],
|
|
197
197
|
executables: @pkg_data[:executables],
|
|
198
198
|
source: ["#{@pkg.name}-#{version}.tar.gz"],
|
|
@@ -213,7 +213,7 @@ module Crosspack
|
|
|
213
213
|
def pack_windows
|
|
214
214
|
output = File.join(@target.output_dir(@output_base, :winget),
|
|
215
215
|
"#{@pkg.name}-#{Builders::Wix.msi_version(@version)}.msi")
|
|
216
|
-
files = @pkg.
|
|
216
|
+
files = @pkg.payload_for(@target).to_h { |n| [File.join(@pkg_data[:src_dir], n), n] }
|
|
217
217
|
Builders::Wix.build(
|
|
218
218
|
name: @pkg.name,
|
|
219
219
|
version: @version,
|
|
@@ -228,13 +228,13 @@ module Crosspack
|
|
|
228
228
|
# macOS: .app bundle staging; DMG itself requires a Mac (hdiutil).
|
|
229
229
|
def pack_macos
|
|
230
230
|
output_dir = @target.output_dir(@output_base, :cask)
|
|
231
|
-
files = @pkg.
|
|
231
|
+
files = @pkg.payload_for(@target).to_h { |n| [File.join(@pkg_data[:src_dir], n), n] }
|
|
232
232
|
Builders::AppDir.build(
|
|
233
233
|
name: @pkg.name,
|
|
234
234
|
version: @version,
|
|
235
235
|
summary: @pkg.summary,
|
|
236
236
|
files: files,
|
|
237
|
-
executables: @pkg.
|
|
237
|
+
executables: @pkg.executables_for(@target),
|
|
238
238
|
output: output_dir
|
|
239
239
|
)
|
|
240
240
|
end
|
data/lib/crosspack/target.rb
CHANGED
|
@@ -55,6 +55,45 @@ module Crosspack
|
|
|
55
55
|
new(family_sym, version, norm_arch)
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
# The target of the machine crosspack runs on, for stage commands invoked
|
|
59
|
+
# without an explicit target: "macos", "windows-11.0" or the distro
|
|
60
|
+
# family + version from the os-release file ("ubuntu-24.04",
|
|
61
|
+
# "debian-12", "fedora-41", "arch"). Unknown distro ids fall back to the
|
|
62
|
+
# first ID_LIKE token that names a family. Returns nil when the host is
|
|
63
|
+
# not recognizable — the CLI then asks for an explicit target.
|
|
64
|
+
# Windows defaults to the 11.0 target vocabulary (10 is EOL); pass a
|
|
65
|
+
# target explicitly when packing for another release.
|
|
66
|
+
def self.host_string(ruby_platform: RUBY_PLATFORM, release_path: '/etc/os-release')
|
|
67
|
+
case ruby_platform
|
|
68
|
+
when /darwin/ then 'macos'
|
|
69
|
+
when /mingw|mswin|cygwin/i then 'windows-11.0'
|
|
70
|
+
when /linux/
|
|
71
|
+
facts = host_os_release(release_path)
|
|
72
|
+
family = ([facts['ID']] + facts['ID_LIKE'].to_s.split)
|
|
73
|
+
.filter_map { |id| id.to_s.to_sym if id }
|
|
74
|
+
.find { |f| FAMILIES.include?(f) }
|
|
75
|
+
return nil if family.nil?
|
|
76
|
+
|
|
77
|
+
version = facts['VERSION_ID'].to_s
|
|
78
|
+
return family.to_s if VERSIONLESS_FAMILIES.include?(family) ||
|
|
79
|
+
OPTIONAL_VERSION_FAMILIES.include?(family)
|
|
80
|
+
return nil if version.empty? # e.g. Debian sid: no pin-able version
|
|
81
|
+
|
|
82
|
+
"#{family}-#{version}"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.host_os_release(path)
|
|
87
|
+
File.read(path).each_line.with_object({}) do |line, facts|
|
|
88
|
+
key, value = line.split('=', 2)
|
|
89
|
+
next if value.nil?
|
|
90
|
+
|
|
91
|
+
facts[key.strip] = value.strip.delete_prefix('"').delete_suffix('"')
|
|
92
|
+
end
|
|
93
|
+
rescue Errno::ENOENT
|
|
94
|
+
{}
|
|
95
|
+
end
|
|
96
|
+
|
|
58
97
|
def initialize(family, version = nil, arch = :x86_64)
|
|
59
98
|
@family = family.to_sym
|
|
60
99
|
@version = version && version.to_s
|
data/lib/crosspack/version.rb
CHANGED
data/lib/crosspack.rb
CHANGED
data/logo.png
ADDED
|
Binary file
|
metadata
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: crosspack
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oleg Orlov
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
-
dependencies:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: colorize
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.0'
|
|
13
26
|
description: 'One command, one config file, one target vocabulary: crosspack deps
|
|
14
27
|
<target> verifies and installs the build host dependencies, crosspack build <target>
|
|
15
28
|
runs the crosspack.yml build matrix entry and fans artifacts into builds/, crosspack
|
|
@@ -43,6 +56,7 @@ files:
|
|
|
43
56
|
- lib/crosspack/builders/rpm.rb
|
|
44
57
|
- lib/crosspack/builders/wix.rb
|
|
45
58
|
- lib/crosspack/builds.rb
|
|
59
|
+
- lib/crosspack/colors.rb
|
|
46
60
|
- lib/crosspack/config.rb
|
|
47
61
|
- lib/crosspack/manifest.rb
|
|
48
62
|
- lib/crosspack/matrix.rb
|
|
@@ -51,13 +65,13 @@ files:
|
|
|
51
65
|
- lib/crosspack/resolver.rb
|
|
52
66
|
- lib/crosspack/target.rb
|
|
53
67
|
- lib/crosspack/version.rb
|
|
68
|
+
- logo.png
|
|
54
69
|
homepage: https://github.com/OrelSokolov/crosspack
|
|
55
70
|
licenses:
|
|
56
71
|
- Nonstandard
|
|
57
72
|
metadata:
|
|
58
73
|
source_code_uri: https://github.com/OrelSokolov/crosspack
|
|
59
74
|
changelog_uri: https://github.com/OrelSokolov/crosspack/blob/main/CHANGELOG.md
|
|
60
|
-
post_install_message:
|
|
61
75
|
rdoc_options: []
|
|
62
76
|
require_paths:
|
|
63
77
|
- lib
|
|
@@ -72,8 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
72
86
|
- !ruby/object:Gem::Version
|
|
73
87
|
version: '0'
|
|
74
88
|
requirements: []
|
|
75
|
-
rubygems_version: 3.
|
|
76
|
-
signing_key:
|
|
89
|
+
rubygems_version: 3.6.9
|
|
77
90
|
specification_version: 4
|
|
78
91
|
summary: 'One config (crosspack.yml), staged pipeline: deps → build → pack native
|
|
79
92
|
packages (deb/rpm/PKGBUILD/winget/cask)'
|