crosspack 0.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cb2e2fb91b4f2215649e739ff6ec7128a11e06abe8d776f870d686261439a974
4
+ data.tar.gz: 3b17f1325d46d5147a0d87fee2d62ee2cb021f4a11c4ab16cf54a94562d843ad
5
+ SHA512:
6
+ metadata.gz: 19cfe0db0863bff0e3b69d39ccefd0066ae99a85586ae8cabe19951897f0852b70966cc39372740a9a7e779ad36c81827797094befe8e292c7f420d1a17a0578
7
+ data.tar.gz: 9b220eff4186f4f1f88875d0b802c0d7a76f1b5d394e0ec828706a2e56d71676f809c1726de82e39f950ad323056b73104c9022c9fa181f554830817748dd612
data/CHANGELOG.md ADDED
@@ -0,0 +1,36 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.2.0] - 2026-09-08
9
+
10
+ ### Added
11
+ - Merged the `crossbuild` gem into `crosspack`: one gem, two commands.
12
+ `crossbuild build` runs `build.yaml` matrix steps on the host and fans
13
+ artifacts out into `builds/<family>[/<version>]/<arch>/`; `crosspack pack`
14
+ packages that tree as before.
15
+ - `build.yaml` manifest with schema validation, version schemes
16
+ (`calver` / `git-tag` / `env:VAR` / literal), `{{version}}`-style
17
+ placeholder expansion and `CROSSBUILD_*` environment facts.
18
+ - crossbuild CLI: `validate`, `matrix`, `version`, `build`, `targets`.
19
+ - Library API: `Crossbuild.build` plus public `BuildManifest`,
20
+ `VersionScheme`, `Runner`, `Distributor`, `Matrix`, `Builder`, `Platform`.
21
+
22
+ ## [0.1.0] - 2026-09-08
23
+
24
+ ### Added
25
+ - `package.yaml` / `deps.yaml` manifests with embedded schema validation and
26
+ path-pointing error messages.
27
+ - Dependency resolution per distro family/version (`Resolver`), dependency ×
28
+ target matrix rendering (`Matrix`).
29
+ - Builders: deb (`dpkg-deb`), rpm (`rpmbuild`), PKGBUILD, WiX sources (`.wxs`),
30
+ macOS app bundle staging.
31
+ - CLI: `validate`, `resolve`, `matrix`, `pack`, `targets`.
32
+ - Library API: `Crosspack.pack` plus public `PackageManifest`, `Manifest`,
33
+ `Resolver`, `Matrix`, `Target`, `Builders::*`.
34
+
35
+ [0.2.0]: https://github.com/OrelSokolov/crosspack/releases/tag/v0.2.0
36
+ [0.1.0]: https://github.com/OrelSokolov/crosspack/releases/tag/v0.1.0
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2026 Oleg Orlov
2
+
3
+ All rights reserved.
4
+
5
+ This software and its source code are proprietary and confidential.
6
+ No part of this software may be copied, modified, distributed, sublicensed,
7
+ or sold without the prior written permission of the copyright holder.
8
+
9
+ This software is provided by the copyright holder "as is" and any express
10
+ or implied warranties, including, but not limited to, the implied warranties
11
+ of merchantability and fitness for a particular purpose are disclaimed.
12
+ In no event shall the copyright holder be liable for any direct, indirect,
13
+ incidental, special, exemplary, or consequential damages (including, but
14
+ not limited to, procurement of substitute goods or services; loss of use,
15
+ data, or profits; or business interruption) however caused and on any theory
16
+ of liability, whether in contract, strict liability, or tort (including
17
+ negligence or otherwise) arising in any way out of the use of this software,
18
+ even if advised of the possibility of such damage.
data/README.md ADDED
@@ -0,0 +1,192 @@
1
+ # crosspack
2
+
3
+ Cross-platform build **and** pack toolchain for Wails-style desktop apps,
4
+ shipped as one gem with two cooperating commands:
5
+
6
+ - **`crossbuild`** — runs the build steps of a project from a small
7
+ `build.yaml` matrix and fans the produced artifacts out into the
8
+ per-target tree `builds/<family>[/<version>]/<arch>/`.
9
+ - **`crosspack`** — turns that compiled tree into native packages
10
+ (deb / rpm / PKGBUILD / WiX / app bundle) for any target
11
+ distro × version × arch, driven by two small manifests.
12
+
13
+ The two commands share the same target vocabulary (`Crosspack::Target`),
14
+ so the build tree and the pack tree never drift apart. Build and pack
15
+ stay cleanly separated: `crossbuild` never packages, `crosspack` never
16
+ compiles — feed the output of one straight into the other:
17
+
18
+ ```
19
+ crossbuild build # builds/ tree
20
+ crosspack pack --target debian-12 --version 2026.08.31-33837
21
+ ```
22
+
23
+ ## crossbuild: build.yaml
24
+
25
+ ```yaml
26
+ name: myapp
27
+ version: calver # calver | git-tag | env:VAR | any literal string
28
+ output: builds # default builds; mirrors package.yaml `sources`
29
+
30
+ matrix:
31
+ - build: linux/amd64 # os/arch this entry builds on (arch may be "any")
32
+ env: # extra env for the steps of this entry
33
+ CGO_ENABLED: "1"
34
+ steps: # shell commands, run from the project root
35
+ - wails build -platform linux/amd64 -ldflags "-X myapp/internal/app.appVersion={{version}}"
36
+ - cargo build --release -p helper --locked
37
+ artifacts:
38
+ from: build/bin # what got built
39
+ include: [myapp, helper, "*.onnx", "*.so*"] # default: ["*"]
40
+ to: [ubuntu-22.04, ubuntu-24.04, debian-12, debian-13]
41
+ mode: symlink # symlink | copy, default symlink
42
+
43
+ - id: windows # defaults to the build platform string
44
+ build: windows/amd64
45
+ steps:
46
+ - wails build -platform windows/amd64 -ldflags "-X ...={{version}}"
47
+ artifacts:
48
+ from: build/bin
49
+ include: [myapp.exe]
50
+ to: [windows-11.0]
51
+ ```
52
+
53
+ Placeholders `{{version}}`, `{{name}}`, `{{platform}}`, `{{os}}`, `{{arch}}`
54
+ and `{{id}}` are expanded by the gem itself (works the same under POSIX
55
+ shells and cmd.exe); the same facts are exported as `CROSSBUILD_VERSION`,
56
+ `CROSSBUILD_NAME`, ... environment variables. Steps may freely call existing
57
+ rake tasks (`rake build:prepare`) or helper scripts.
58
+
59
+ Artifacts land in the shared layout, e.g.:
60
+
61
+ ```
62
+ builds/ubuntu/24.04/amd64/myapp -> build/bin/myapp (symlink)
63
+ builds/windows/11.0/x86_64/myapp.exe
64
+ ```
65
+
66
+ ### Version schemes
67
+
68
+ | scheme | value |
69
+ |---------------|------------------------------------------------------------|
70
+ | `calver` | `YYYY.MM.DD-<secs since local midnight>`, e.g. `2026.08.31-33837` (default) |
71
+ | `git-tag` | latest `git describe --tags --abbrev=0`, fallback `0.0.0-dev` |
72
+ | `env:VAR` | taken from `VAR`; build fails loudly when unset |
73
+ | other string | used verbatim, e.g. `version: 1.2.3` |
74
+
75
+ ### crossbuild CLI
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.
87
+
88
+ ## crosspack: two manifests
89
+
90
+ **`package.yaml`** — what and how to pack:
91
+
92
+ ```yaml
93
+ name: myapp
94
+ maintainer: Your Name <you@example.com>
95
+ summary: MyApp - cross-platform desktop application # optional
96
+ description: |-
97
+ MyApp - cross-platform desktop application.
98
+ Bundles helper binaries and model assets alongside the GUI...
99
+ license: Proprietary # optional, default Proprietary
100
+ section: utils # optional, deb only
101
+
102
+ sources: builds # compiled artifacts tree written by crossbuild:
103
+ # builds/<family>/<version>/<arch>/ — pack without
104
+ # a matching build directory fails honestly
105
+ prefix: usr/local # install prefix inside the package
106
+ lib_dir: lib/myapp # optional, default lib/<name>
107
+
108
+ payload: # files from sources -> <prefix>/<lib_dir>/
109
+ - myapp
110
+ - helper
111
+ - model.onnx
112
+
113
+ executables: [myapp, helper] # chmod 755, must be in payload
114
+
115
+ links: # symlinks relative to prefix
116
+ bin/myapp: ../lib/myapp/myapp
117
+
118
+ desktop: # optional, .desktop generated
119
+ name: MyApp
120
+ comment: Cross-platform desktop application
121
+ categories: Utility;Audio
122
+
123
+ icon: build/appicon.png # optional, -> <prefix>/share/pixmaps/<name>.png
124
+ ```
125
+
126
+ **`deps.yaml`** — canonical dependency names resolved to concrete packages
127
+ per distro family/version (see the file itself for the full schema):
128
+
129
+ ```yaml
130
+ webkit2gtk:
131
+ targets:
132
+ debian:
133
+ "12": [libwebkit2gtk-4.1-0, libwebkit2gtk-4.0-37] # deb: a | b
134
+ fedora: { "*": [webkit2gtk4.1] }
135
+ macos: system # OS component
136
+ windows: system # preinstalled (WebView2)
137
+ ```
138
+
139
+ Both files are validated against embedded schemas with actionable,
140
+ path-pointing error messages; every builder refuses to run on an invalid
141
+ manifest.
142
+
143
+ ## CLI
144
+
145
+ ```
146
+ crosspack validate # both package.yaml and deps.yaml
147
+ crosspack resolve --target debian-12 # Depends line for the target
148
+ crosspack matrix # dependency x target table
149
+ crosspack pack --target debian-12 --version 2026.08.31-1234 [--root .]
150
+ ```
151
+
152
+ `pack` picks the builder from the target and writes to
153
+ `crosspacks/<family>/<version>/<arch>/`:
154
+
155
+ ```
156
+ crosspacks/ubuntu/26.04/amd64/myapp_2026.08.31-1234_amd64.deb
157
+ crosspacks/fedora/41/x86_64/myapp-2026.08.31-1234.x86_64.rpm (needs rpmbuild)
158
+ crosspacks/arch/x86_64/PKGBUILD
159
+ ```
160
+
161
+ ## Library
162
+
163
+ ```ruby
164
+ require 'crosspack' # pulls in Crossbuild too
165
+
166
+ Crossbuild.build('build.yaml', root: Dir.pwd) # all host entries
167
+ Crossbuild.build('build.yaml', entry_id: 'windows') # one entry explicitly
168
+
169
+ Crosspack.pack(
170
+ manifest: 'package.yaml', deps: 'deps.yaml',
171
+ target: Crosspack::Target.parse('debian-12', arch: 'amd64'),
172
+ version: '2026.08.31-1234',
173
+ root: Dir.pwd, output_base: 'crosspacks'
174
+ )
175
+ ```
176
+
177
+ Lower-level pieces are public too: on the build side `Crossbuild::
178
+ BuildManifest`, `VersionScheme`, `Runner`, `Distributor`, `Matrix`,
179
+ `Builder`, `Platform`; on the pack side `Crosspack::PackageManifest`,
180
+ `Manifest`, `Resolver`, `Matrix`, `Target`, `Builders::*`.
181
+
182
+ Deb needs `dpkg-deb` (present on any Debian/Ubuntu), rpm needs
183
+ `rpmbuild` (`sudo apt install rpm`), PKGBUILD generation needs nothing.
184
+ The WiX builder generates `.wxs` sources (run the WiX Toolset yourself to
185
+ get an MSI), the macOS builder stages the `.app` bundle (signing, notarization
186
+ and .dmg creation are out of scope). Requires Ruby >= 3.2.
187
+
188
+ ## Tests
189
+
190
+ ```
191
+ cd crosspack && rake test
192
+ ```
data/exe/crossbuild ADDED
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'optparse'
5
+
6
+ begin
7
+ require 'crossbuild'
8
+ rescue LoadError
9
+ require_relative '../lib/crossbuild'
10
+ end
11
+
12
+ def abort_with(message)
13
+ warn message
14
+ exit 1
15
+ end
16
+
17
+ options = { file: 'build.yaml', root: Dir.pwd, target: nil, version: nil, output_base: nil }
18
+ parser = OptionParser.new do |opts|
19
+ opts.banner = "crossbuild #{Crossbuild::VERSION} — cross-platform build orchestrator\n" \
20
+ "Usage: crossbuild <command> [options]\n" \
21
+ 'Commands: validate, matrix, version, build, targets'
22
+
23
+ opts.on('-f', '--file PATH', 'path to build.yaml (default: ./build.yaml)') { |v| options[:file] = v }
24
+ opts.on('--target ID', 'matrix entry to build, e.g. linux/amd64 (default: every host-buildable entry)') { |v| options[:target] = v }
25
+ opts.on('--version VERSION', 'override the version scheme for build') { |v| options[:version] = v }
26
+ opts.on('--root PATH', 'project root for relative paths (default: .)') { |v| options[:root] = v }
27
+ opts.on('--output-base DIR', 'output directory (default: the manifest output, builds)') { |v| options[:output_base] = v }
28
+ opts.on('-h', '--help', 'show help') do
29
+ puts opts
30
+ exit
31
+ end
32
+ end
33
+ parser.parse!
34
+
35
+ command = ARGV.shift
36
+ abort_with 'No command given. See: crossbuild --help' if command.nil?
37
+
38
+ # Positional file path: crossbuild validate path/to/build.yaml
39
+ options[:file] = ARGV.shift if ARGV.first && !ARGV.first.start_with?('-')
40
+
41
+ file = File.expand_path(options[:file], options[:root])
42
+ begin
43
+ manifest = Crossbuild::BuildManifest.load(file)
44
+ rescue Crossbuild::InvalidManifestError => e
45
+ abort_with e.message
46
+ end
47
+
48
+ case command
49
+ when 'validate'
50
+ puts manifest.error_report
51
+ exit(manifest.valid? ? 0 : 1)
52
+ when 'matrix'
53
+ abort_with 'Schema is invalid — the matrix cannot be rendered. Run: crossbuild validate' unless manifest.valid?
54
+ puts Crossbuild::Matrix.new(manifest).render
55
+ when 'version'
56
+ abort_with 'Schema is invalid. Run: crossbuild validate' unless manifest.valid?
57
+ begin
58
+ puts Crossbuild::VersionScheme.new(manifest.version).compute(root: options[:root])
59
+ rescue Crossbuild::VersionScheme::Error => e
60
+ abort_with e.message
61
+ end
62
+ when 'build'
63
+ begin
64
+ result = Crossbuild::Builder.new(manifest, root: options[:root], output_base: options[:output_base],
65
+ version: options[:version]).run(entry_id: options[:target])
66
+ rescue Crossbuild::Error, Crosspack::InvalidManifestError => e
67
+ abort_with e.message
68
+ end
69
+ puts "\n📦 #{result.entries.size} entr#{result.entries.size == 1 ? 'y' : 'ies'} built, version #{result.version}"
70
+ puts 'Pack with: crosspack pack --target <target> --version ' + result.version
71
+ when 'targets'
72
+ base = options[:output_base] || manifest.output
73
+ puts Crosspack::Builds.report(File.expand_path(base, options[:root]))
74
+ else
75
+ abort_with "Unknown command #{command.inspect}. Available: validate, matrix, version, build, targets"
76
+ end
data/exe/crosspack ADDED
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'optparse'
5
+
6
+ begin
7
+ require 'crosspack'
8
+ rescue LoadError
9
+ require_relative '../lib/crosspack'
10
+ end
11
+
12
+ def abort_with(message)
13
+ warn message
14
+ exit 1
15
+ end
16
+
17
+ options = { file: 'deps.yaml', package: 'package.yaml', arch: 'amd64', format: nil,
18
+ version: nil, root: Dir.pwd, output_base: 'crosspacks' }
19
+ parser = OptionParser.new do |opts|
20
+ opts.banner = "crosspack #{Crosspack::VERSION} — native package packer (deb/rpm/PKGBUILD/MSI/app)\n" \
21
+ "Usage: crosspack <command> [options]\n" \
22
+ 'Commands: validate, resolve, matrix, pack, targets'
23
+
24
+ opts.on('-f', '--file PATH', 'path to deps.yaml (default: ./deps.yaml)') { |v| options[:file] = v }
25
+ opts.on('--package PATH', 'path to package.yaml (default: ./package.yaml)') { |v| options[:package] = v }
26
+ opts.on('--target TARGET', 'target: debian-12, ubuntu-24.04, fedora-41, arch, macos, windows') { |v| options[:target] = v }
27
+ opts.on('--arch ARCH', 'architecture: amd64/x86_64, arm64/aarch64 (default: amd64)') { |v| options[:arch] = v }
28
+ 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', 'package version for pack (required for pack)') { |v| options[:version] = v }
30
+ 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 }
32
+ opts.on('-h', '--help', 'show help') do
33
+ puts opts
34
+ exit
35
+ end
36
+ end
37
+ begin
38
+ parser.parse!
39
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
40
+ abort_with "#{e.message}\nSee: crosspack --help"
41
+ end
42
+
43
+ command = ARGV.shift
44
+ abort_with 'No command given. See: crosspack --help' if command.nil?
45
+
46
+ # Positional file path: crosspack validate path/to/deps.yaml
47
+ options[:file] = ARGV.shift if ARGV.first && !ARGV.first.start_with?('-')
48
+
49
+ begin
50
+ manifest = Crosspack::Manifest.load(options[:file])
51
+ rescue Crosspack::InvalidManifestError => e
52
+ abort_with e.message
53
+ end
54
+
55
+ case command
56
+ when 'validate'
57
+ begin
58
+ pkg = Crosspack::PackageManifest.load(options[:package])
59
+ rescue Crosspack::InvalidManifestError => e
60
+ abort_with e.message
61
+ end
62
+ puts manifest.error_report
63
+ puts pkg.error_report
64
+ exit(manifest.valid? && pkg.valid? ? 0 : 1)
65
+ 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
+ begin
71
+ target = Crosspack::Target.parse(options[:target], arch: options[:arch])
72
+ rescue Crosspack::Target::Error => e
73
+ abort_with "Invalid target #{options[:target].inspect}: #{e.message}"
74
+ end
75
+ begin
76
+ resolver = Crosspack::Resolver.new(manifest)
77
+ format = options[:format] || target.format
78
+ result = resolver.depends(target, format: format)
79
+ if result.is_a?(Array)
80
+ puts "# #{target} (#{format})"
81
+ result.each { |d| puts d }
82
+ else
83
+ puts "# #{target} (#{format})"
84
+ puts "Depends: #{result}"
85
+ end
86
+ rescue Crosspack::ResolveError => e
87
+ abort_with "Failed to resolve dependencies for #{target}:\n #{e.message}"
88
+ end
89
+ when 'pack'
90
+ abort_with 'Specify a target: --target debian-12 and a version: --version 1.0-1' unless options[:target] && options[:version]
91
+ begin
92
+ target = Crosspack::Target.parse(options[:target], arch: options[:arch])
93
+ path = Crosspack.pack(
94
+ manifest: options[:package],
95
+ deps: options[:file],
96
+ target: target,
97
+ version: options[:version],
98
+ root: options[:root],
99
+ output_base: options[:output_base]
100
+ )
101
+ rescue Crosspack::Target::Error => e
102
+ abort_with "Invalid target #{options[:target].inspect}: #{e.message}"
103
+ rescue Crosspack::InvalidManifestError, Crosspack::ResolveError, Crosspack::BuildError => e
104
+ abort_with e.message
105
+ end
106
+ puts "📦 #{path}"
107
+ when 'targets'
108
+ # What can actually be packed: builds tree + resolvable deps per target.
109
+ pkg_path = File.expand_path(options[:package], options[:root])
110
+ sources = begin
111
+ Crosspack::PackageManifest.load(pkg_path).sources
112
+ rescue Crosspack::InvalidManifestError
113
+ nil
114
+ end
115
+ root = sources ? File.expand_path(sources, options[:root]) : File.join(options[:root], 'builds')
116
+ puts Crosspack::Builds.report(root)
117
+ else
118
+ abort_with "Unknown command #{command.inspect}. Available: validate, resolve, matrix, pack, targets"
119
+ end