crosspack 0.6.2 → 0.7.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: 369c6df533b4a7e576fb1bfe4af308d0e422082934a533995355eb1bec6f8854
4
- data.tar.gz: 65eca41ff0574b5460dffcfe9a2e33f36936ddff9184a4ed2908df0290f711e8
3
+ metadata.gz: 8d03bbc2ce62c3f8cc511df1f4e9ade332adc838a361e2730154c4d9b94403a3
4
+ data.tar.gz: 8b3785216051ef0e2c926e9feb4a61d8473286fb3ba1a6b91d4ac19cf0969e88
5
5
  SHA512:
6
- metadata.gz: 7449ad323b7beb15a046ced0dfb3e47caf437d3844f660af93d4da19e3d2fd74cc64ed62f3e531589e6079c49309ee82ec7c1959f3cdef093cc517a9e67cc2d7
7
- data.tar.gz: e58bf61ce945544bc8ba0dd96c41760ffa8637cdb934bef375c18c3d70ef3aa465369a8122455008964cd608eabf066e79a6dfe60fa5f5bca87051d42b5e5bc0
6
+ metadata.gz: 5372d580eb8e5730c13f1fffcd08c626e272ae436dc8f0d53a9a504e398f750d25948e29e8741c138044b3a2981f2082aa73e1ca418eaa0f399988ce56cf3d94
7
+ data.tar.gz: 63de5f8eafe2856831605c1e667c3d2ed32a65b29c405eb8ebf854c7792616e3fa2b7370ba963cd8d78e7aa9a02edec9ed14458a61e9d4f204772856abfba6f9
data/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ 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.7.0] - 2026-09-13
9
+
10
+ ### Added
11
+ - Host-only commands: `crosspack run` builds for the current host (the same
12
+ stage `crosspack build <host target>` runs) and then launches the main
13
+ binary straight from `builds/` — the first `package.executables` entry,
14
+ not the package. Arguments after `--` are passed to the app
15
+ (`crosspack run -- --dev`); the app's exit code is propagated.
16
+ - `crosspack install` launches this host's package with its system
17
+ installer: the newest package in `crosspacks/<host target>/` goes to
18
+ `xdg-open` (deb/rpm), `msiexec /i` (MSI) or `open` (macOS dmg/.app);
19
+ for arch crosspack generates only a PKGBUILD, so it points at
20
+ `makepkg -si`.
21
+ - crosspack.yml `run:` / `install:` sections: per-host launch-command
22
+ overrides using the same host selectors as `build.deps` (distro id,
23
+ ID_LIKE, os, `"*"`), with the `{{path}}` placeholder (the built binary
24
+ for run, the package file for install) plus the usual build facts.
25
+ Default without a `run:` section: execute the binary directly (a macOS
26
+ `.app` bundle is `open`ed).
27
+
28
+ [0.7.0]: https://github.com/OrelSokolov/crosspack/releases/tag/v0.7.0
29
+
8
30
  ## [0.6.2] - 2026-09-13
9
31
 
10
32
  ### Added
data/README.md CHANGED
@@ -31,14 +31,15 @@ token):
31
31
  crosspack deps && crosspack build && crosspack pack
32
32
  ```
33
33
 
34
- ## crosspack.yml — one file, three sections
34
+ ## crosspack.yml — one file, three sections + two host hooks
35
35
 
36
36
  The top level carries the shared facts — `name` (required once) and the
37
37
  optional `version` scheme — and three sections: `build:` (what and where to
38
38
  build), `package:` (what and how to pack) and `deps:` (runtime dependencies
39
- resolved to concrete packages per distro). The shared name/version feed the
40
- build section, the name feeds the package section; each section keeps its
41
- own schema.
39
+ resolved to concrete packages per distro). Two optional hooks `run:` and
40
+ `install:` override the host-only launch commands. The shared name/version
41
+ feed the build section, the name feeds the package section; each section
42
+ keeps its own schema.
42
43
 
43
44
  ```yaml
44
45
  name: myapp
@@ -142,6 +143,15 @@ deps:
142
143
  fedora: { "*": [webkit2gtk4.1] }
143
144
  macos: system # OS component
144
145
  windows: system # preinstalled (WebView2)
146
+
147
+ # host-only launch hooks (optional): same host selectors as build.deps
148
+ run: # `crosspack run`: how to launch the built binary
149
+ hosts:
150
+ darwin: open build/bin/MyApp.app # .app bundles must be opened, not exec'd
151
+
152
+ install: # `crosspack install`: how to launch this host's package
153
+ hosts:
154
+ ubuntu: sudo apt-get install -y "{{path}}"
145
155
  ```
146
156
 
147
157
  Placeholders `{{version}}`, `{{name}}`, `{{platform}}`, `{{os}}`, `{{arch}}`
@@ -192,6 +202,10 @@ crosspack deps <target> [--check] # verify/install build deps (--check: r
192
202
  crosspack build <target>|--all [--no-deps] [--version X]
193
203
  crosspack pack <target> [--version X] # version defaults to the build stamp
194
204
 
205
+ # host-only: this machine, no target argument
206
+ crosspack run [-- --dev] # build for this host, then launch the main binary
207
+ crosspack install # launch this host's package with its installer
208
+
195
209
  # inspection
196
210
  crosspack validate # crosspack.yml: top level + all sections
197
211
  crosspack resolve <target> # Depends line for the target
@@ -212,6 +226,32 @@ crosspacks/fedora/41/x86_64/myapp-2026.08.31-1234.x86_64.rpm (needs rpmbuild)
212
226
  crosspacks/arch/x86_64/PKGBUILD
213
227
  ```
214
228
 
229
+ ### Host-only commands: run and install
230
+
231
+ `run` and `install` apply to the current host only — the build target *is*
232
+ the run target, so they take no target argument. `crosspack run` runs the
233
+ build stage for this host (same as `crosspack build <host target>`), then
234
+ launches the first `package.executables` binary straight from `builds/` —
235
+ the binary, not the package. Everything after `--` goes to the app:
236
+
237
+ ```
238
+ crosspack run -- --dev
239
+ ```
240
+
241
+ `crosspack install` takes the newest package packed for this host in
242
+ `crosspacks/` and hands it to the system installer. Defaults: `xdg-open`
243
+ for deb/rpm (opens GNOME Software/installer), `msiexec /i` for the MSI,
244
+ `open` for a macOS dmg/.app; for arch crosspack only generates a PKGBUILD,
245
+ so it points you at `makepkg -si` instead.
246
+
247
+ Both are per-system by nature, so each accepts a `hosts:` override with the
248
+ same selectors as `build.deps` (distro id, `ID_LIKE`, os, `"*"` — first
249
+ match wins). Commands support the `{{path}}` placeholder (the binary in
250
+ `builds/` for run, the package file for install) plus the usual
251
+ `{{name}}`/`{{version}}`/`{{os}}`/`{{arch}}` facts; quote values that start
252
+ with `{{` (YAML flow indicators). Without a `run:` section the binary is
253
+ executed directly, except a macOS `.app` bundle which is `open`ed.
254
+
215
255
  ## Library
216
256
 
217
257
  ```ruby
data/exe/crosspack CHANGED
@@ -48,6 +48,8 @@ parser = OptionParser.new do |opts|
48
48
  "Stages: deps [<target>] [--check] verify/install build deps (default: this host)\n" \
49
49
  " build [<target>]|--all compile into builds/ (default: this host)\n" \
50
50
  " pack [<target>] [--version X] package builds/<target> (default: this host; version from the build stamp)\n" \
51
+ "Host: run [-- <app args>] build for this host, then launch the main binary\n" \
52
+ " install launch this host's package with its installer\n" \
51
53
  'Commands: validate, resolve, matrix, version, targets'
52
54
 
53
55
  opts.on('-f', '--file PATH', 'path to crosspack.yml (default: ./crosspack.yml)') { |v| options[:file] = v }
@@ -126,7 +128,11 @@ when 'validate'
126
128
  puts colorized_report(deps_manifest.error_report)
127
129
  puts colorized_report(pkg.error_report)
128
130
  puts colorized_report(build_manifest.error_report)
129
- ok = config.valid? && deps_manifest.valid? && pkg.valid? && build_manifest.valid?
131
+ [config.run_manifest, config.install_manifest].each do |m|
132
+ puts colorized_report(m.error_report) if m.present?
133
+ end
134
+ ok = config.valid? && deps_manifest.valid? && pkg.valid? && build_manifest.valid? &&
135
+ config.run_manifest.valid? && config.install_manifest.valid?
130
136
  exit(ok ? 0 : 1)
131
137
  when 'matrix'
132
138
  deps_manifest = config.deps_manifest
@@ -217,6 +223,32 @@ when 'pack'
217
223
  abort_with e.message
218
224
  end
219
225
  puts Crosspack::Colors.green("📦 #{path}")
226
+ when 'run', 'install'
227
+ # Host-only commands: no target argument; anything after `--` goes to the
228
+ # launched process (crosspack run -- --dev).
229
+ if positional
230
+ abort_with "crosspack #{command} works only for the current host — no target argument " \
231
+ "(other targets make no sense here)"
232
+ end
233
+ args = ARGV.dup
234
+ args.shift if args.first == '--'
235
+ begin
236
+ status =
237
+ if command == 'run'
238
+ Crosspack::Launcher.run(
239
+ config, root: options[:root], args: args,
240
+ output_base: options[:output_base], version: options[:version], deps: !options[:no_deps]
241
+ )
242
+ else
243
+ Crosspack::Launcher.install(
244
+ config, root: options[:root], args: args,
245
+ output_base: options[:output_base] || 'crosspacks'
246
+ )
247
+ end
248
+ rescue Crosspack::InvalidManifestError, Crosspack::BuildError, Crossbuild::Error => e
249
+ abort_with e.message
250
+ end
251
+ exit(status) unless status.zero?
220
252
  when 'targets'
221
253
  # What can actually be packed: builds tree + resolvable deps per target.
222
254
  pkg = config.package_manifest
@@ -224,5 +256,5 @@ when 'targets'
224
256
  root = sources ? File.expand_path(sources, options[:root]) : File.join(options[:root], 'builds')
225
257
  puts Crosspack::Builds.report(root)
226
258
  else
227
- abort_with "Unknown command #{command.inspect}. Available: deps, build, pack, validate, resolve, matrix, version, targets"
259
+ abort_with "Unknown command #{command.inspect}. Available: deps, build, pack, run, install, validate, resolve, matrix, version, targets"
228
260
  end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crosspack
4
+ # Parses and validates the optional run:/install: sections of crosspack.yml.
5
+ # Both map host selectors (the same vocabulary as the deps: rules of the
6
+ # build: section: distro id from os-release, ID_LIKE token, os name, "*")
7
+ # to a shell command with {{path}} and the usual build facts:
8
+ #
9
+ # run:
10
+ # hosts:
11
+ # darwin: open build/bin/MyApp.app
12
+ # "*": "{{path}} --dev"
13
+ #
14
+ # A section is optional; when absent the host-only default applies
15
+ # (see Launcher).
16
+ class CommandManifest
17
+ SECTION_KEYS = %w[hosts].freeze
18
+ # A host selector: "*" or a distro id / ID_LIKE token / os name —
19
+ # the same vocabulary as the deps: rules of the build: section.
20
+ SELECTOR_RE = /\A(\*|[a-z0-9][a-z0-9+._-]*)\z/i.freeze
21
+
22
+ attr_reader :path, :section, :errors, :warnings
23
+
24
+ def initialize(path, raw, section)
25
+ @path = path
26
+ @section = section
27
+ @raw = raw
28
+ @hosts = {}
29
+ @errors = []
30
+ @warnings = []
31
+ validate
32
+ end
33
+
34
+ def present?
35
+ !@raw.nil?
36
+ end
37
+
38
+ def valid?
39
+ @errors.empty?
40
+ end
41
+
42
+ # The command for the given host selectors (see Crossbuild::Platform
43
+ # .selectors), first match wins; nil when nothing applies.
44
+ def command_for(selectors)
45
+ selectors.each { |s| return @hosts[s] if @hosts.key?(s) }
46
+ nil
47
+ end
48
+
49
+ def error_report
50
+ return "#{@path}: the #{@section}: section is absent — defaults apply" unless present?
51
+
52
+ lines = ["#{@path}: the #{@section}: section is #{@errors.empty? ? 'valid' : "invalid (#{@errors.size} errors)"}:"]
53
+ @errors.each { |e| lines << " ✗ #{e}" }
54
+ @warnings.each { |w| lines << " ⚠ #{w}" }
55
+ lines.join("\n")
56
+ end
57
+
58
+ def validate!
59
+ return self if valid?
60
+
61
+ raise InvalidManifestError, error_report
62
+ end
63
+
64
+ private
65
+
66
+ def validate
67
+ return unless present?
68
+
69
+ unless @raw.is_a?(Hash) && !@raw.empty?
70
+ @errors << Issue.new("#{@section}", "must be a non-empty mapping with the key: hosts; expected:\n" \
71
+ " #{@section}:\n" \
72
+ " hosts:\n" \
73
+ " ubuntu: #{@section == 'install' ? 'xdg-open {{path}}' : '{{path}} --dev'}")
74
+ return
75
+ end
76
+ check_unknown_keys
77
+
78
+ hosts = @raw['hosts']
79
+ return unless hosts
80
+
81
+ unless hosts.is_a?(Hash) && !hosts.empty?
82
+ @errors << Issue.new("#{@section}.hosts", 'must be a non-empty mapping of host selector -> shell command')
83
+ return
84
+ end
85
+
86
+ hosts.each do |selector, command|
87
+ validate_rule(selector.to_s, command)
88
+ end
89
+ end
90
+
91
+ def check_unknown_keys
92
+ @raw.keys.map(&:to_s).each do |key|
93
+ next if SECTION_KEYS.include?(key)
94
+
95
+ @errors << Issue.new("#{@section}.#{key}", "unknown key; allowed: #{SECTION_KEYS.join(', ')}")
96
+ end
97
+ end
98
+
99
+ def validate_rule(selector, command)
100
+ path = "#{@section}.hosts.#{selector}"
101
+ unless selector =~ SELECTOR_RE
102
+ @errors << Issue.new(path, "invalid host selector (expected a distro id, os name or \"*\")")
103
+ return
104
+ end
105
+ unless command.is_a?(String) && !command.strip.empty?
106
+ @errors << Issue.new(path, 'must be a non-empty shell command')
107
+ end
108
+ @hosts[selector] = command if command.is_a?(String) && !command.strip.empty?
109
+ end
110
+ end
111
+ end
@@ -15,7 +15,7 @@ module Crosspack
15
15
  # happens per section, not per file.
16
16
  class Config
17
17
  DEFAULT_PATH = 'crosspack.yml'
18
- TOP_LEVEL_KEYS = %w[name version build package deps].freeze
18
+ TOP_LEVEL_KEYS = %w[name version build package deps run install].freeze
19
19
  NAME_RE = /\A[a-z0-9][a-z0-9+._-]*\z/i.freeze
20
20
 
21
21
  attr_reader :path, :errors, :warnings
@@ -81,6 +81,15 @@ module Crosspack
81
81
  @deps_manifest ||= Manifest.new(@path, deps_section)
82
82
  end
83
83
 
84
+ # The optional run:/install: sections (host-only launch commands).
85
+ def run_manifest
86
+ @run_manifest ||= CommandManifest.new(@path, @raw['run'], 'run')
87
+ end
88
+
89
+ def install_manifest
90
+ @install_manifest ||= CommandManifest.new(@path, @raw['install'], 'install')
91
+ end
92
+
84
93
  private
85
94
 
86
95
  def validate
@@ -124,7 +133,7 @@ module Crosspack
124
133
  end
125
134
 
126
135
  def check_section_types
127
- %w[build package deps].each do |key|
136
+ %w[build package deps run install].each do |key|
128
137
  next unless @raw.key?(key) && !@raw[key].is_a?(Hash)
129
138
 
130
139
  @errors << Issue.new(key, 'must be a mapping')
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Crosspack
4
+ # Host-only launch commands: `crosspack run` (build the host target, then
5
+ # launch the application's main binary) and `crosspack install` (launch the
6
+ # package packed for this host). Both resolve the current host target, so
7
+ # they take no target argument and are meaningless on any other system.
8
+ #
9
+ # Commands may be overridden per host in crosspack.yml (run:/install:
10
+ # sections, selectors like the deps: rules of build:); placeholders:
11
+ # {{path}} plus the usual facts ({{name}}, {{version}}, {{os}}, {{arch}}).
12
+ # Defaults when nothing is configured:
13
+ # run: darwin + .app bundle -> `open {{path}}`, otherwise {{path}}
14
+ # install: deb/rpm -> xdg-open, msi -> msiexec /i, dmg/.app -> open
15
+ class Launcher
16
+ class Error < BuildError; end
17
+
18
+ # Builds the host target first (the same stage `crosspack build` runs),
19
+ # then launches the first package.executables binary from builds/.
20
+ # Returns the launched process's exit code.
21
+ def self.run(config, root: Dir.pwd, args: [], output_base: nil, version: nil,
22
+ deps: true, host_string: nil, host_os: Crossbuild::Platform.os,
23
+ host_arch: Crossbuild::Platform.arch, selectors: Crossbuild::Platform.selectors)
24
+ config = Config.coerce(config, root: root)
25
+ target = host_target('run', host_string, host_arch)
26
+ pkg = validated_package(config)
27
+ build_manifest = config.build_manifest
28
+ build_manifest.validate!
29
+
30
+ Crossbuild::Builder.new(build_manifest, root: root, output_base: output_base,
31
+ version: version, deps: deps,
32
+ host_os: host_os, host_arch: host_arch)
33
+ .run(target: target.to_s)
34
+
35
+ exe = pkg.executables_for(target).first
36
+ if exe.nil?
37
+ raise Error,
38
+ 'run: the package: section does not name the binary to launch — ' \
39
+ "add executables: [#{pkg.name}] to it (the first entry is what `crosspack run` launches)"
40
+ end
41
+
42
+ path = File.join(File.expand_path(target.output_dir(pkg.sources, target.format), root), exe)
43
+ unless File.exist?(path)
44
+ raise Error,
45
+ "run: expected the built binary at #{path}, but it is not there — " \
46
+ 'does the build step really produce it? Check package.executables against the build output'
47
+ end
48
+
49
+ command = config.run_manifest.command_for(selectors) || default_run_command(path, host_os)
50
+ execute(command, path: path, pkg: pkg, target: target, root: root, args: args)
51
+ end
52
+
53
+ # Finds the package packed for this host in crosspacks/ and launches it
54
+ # with the system installer. Returns the launched process's exit code.
55
+ def self.install(config, root: Dir.pwd, args: [], output_base: 'crosspacks',
56
+ host_string: nil, host_os: Crossbuild::Platform.os,
57
+ host_arch: Crossbuild::Platform.arch, selectors: Crossbuild::Platform.selectors)
58
+ config = Config.coerce(config, root: root)
59
+ target = host_target('install', host_string, host_arch)
60
+ pkg = validated_package(config)
61
+
62
+ command = config.install_manifest.command_for(selectors)
63
+ raise Error, pkgbuild_hint(target, root, output_base) if command.nil? && target.format == :pkgbuild
64
+
65
+ # {{path}} resolves to the package file (for arch: the PKGBUILD dir).
66
+ path = target.format == :pkgbuild ? packed_dir(target, root, output_base)
67
+ : package_file(target, pkg, root, output_base)
68
+ command ||= default_install_command(target.format)
69
+
70
+ execute(command, path: path, pkg: pkg, target: target, root: root, args: args)
71
+ end
72
+
73
+ # -- defaults ------------------------------------------------------------
74
+
75
+ # A macOS .app bundle must be opened, not exec'd; plain binaries and
76
+ # Windows exes run directly.
77
+ def self.default_run_command(path, host_os)
78
+ host_os == :darwin && File.directory?(path) ? 'open {{path}}' : '{{path}}'
79
+ end
80
+
81
+ def self.default_install_command(format)
82
+ case format
83
+ when :deb, :rpm then 'xdg-open {{path}}'
84
+ when :winget then 'msiexec /i {{path}}'
85
+ when :cask then 'open {{path}}'
86
+ else
87
+ raise Error, "install: no default installer command for format #{format.inspect} — " \
88
+ 'add an install: section with hosts: rules to crosspack.yml'
89
+ end
90
+ end
91
+
92
+ # -- internals -----------------------------------------------------------
93
+
94
+ class << self
95
+ private
96
+
97
+ # "this host's target" — the same detection the stage commands use.
98
+ def host_target(command, host_string, host_arch)
99
+ raw = host_string || Target.host_string
100
+ if raw.nil?
101
+ raise Error,
102
+ "Could not detect this host's target — crosspack #{command} runs only on this host " \
103
+ '(families: ubuntu-24.04, debian-12, fedora-41, arch, macos, windows-11.0)'
104
+ end
105
+
106
+ Target.parse(raw, arch: host_arch)
107
+ end
108
+
109
+ def validated_package(config)
110
+ pkg = config.package_manifest
111
+ pkg.validate!
112
+ pkg
113
+ end
114
+
115
+ def packed_dir(target, root, output_base)
116
+ File.expand_path(target.output_dir(output_base, target.format), root)
117
+ end
118
+
119
+ # The packed tree must exist before any installer runs — even with a
120
+ # custom command (its {{path}} resolves into this directory).
121
+ def check_packed_dir(target, root, output_base)
122
+ dir = packed_dir(target, root, output_base)
123
+ return if File.directory?(dir)
124
+
125
+ raise Error,
126
+ "Pack stage not done for target #{target}: expected a packed package in #{dir}.\n" \
127
+ "Run: crosspack pack #{target}"
128
+ end
129
+
130
+ # The single package file this host's installer should receive.
131
+ def package_file(target, pkg, root, output_base)
132
+ check_packed_dir(target, root, output_base)
133
+ dir = packed_dir(target, root, output_base)
134
+ file =
135
+ case target.format
136
+ when :deb then newest(dir, '*.deb')
137
+ when :rpm then newest(dir, '*.rpm')
138
+ when :winget then newest(dir, '*.msi')
139
+ when :cask then newest(dir, '*.dmg') || app_bundle(dir, pkg.name)
140
+ end
141
+
142
+ if file.nil?
143
+ raise winget_sources_hint(dir) if target.format == :winget && Dir[File.join(dir, '*.wxs')].any?
144
+
145
+ raise Error,
146
+ "No package for target #{target} in #{dir} — the pack stage produced nothing this " \
147
+ "command recognizes.\nRun: crosspack pack #{target}"
148
+ end
149
+ file
150
+ end
151
+
152
+ def newest(dir, pattern)
153
+ Dir[File.join(dir, pattern)].max_by { |f| File.mtime(f) }
154
+ end
155
+
156
+ def app_bundle(dir, name)
157
+ app = File.join(dir, "#{name}.app")
158
+ File.directory?(app) ? app : nil
159
+ end
160
+
161
+ def winget_sources_hint(dir)
162
+ 'install: the pack stage produced only WiX sources (.wxs) — build the MSI first, ' \
163
+ "see #{File.join(dir, 'BUILD-MSI.txt')}"
164
+ end
165
+
166
+ def pkgbuild_hint(target, root, output_base)
167
+ "install: packing for #{target} generates a PKGBUILD, not a built package — " \
168
+ "install it yourself:\n cd #{packed_dir(target, root, output_base)} && makepkg -si\n" \
169
+ 'or add an install: section with hosts: rules to crosspack.yml'
170
+ end
171
+
172
+ # Expands the {{...}} placeholders, appends extra CLI args and runs the
173
+ # command from the project root, passing the child's exit code through.
174
+ def execute(command, path:, pkg:, target:, root:, args:)
175
+ vars = {
176
+ name: pkg.name,
177
+ version: Builds.version_for(target, File.expand_path(pkg.sources, root)).to_s,
178
+ os: target.family.to_s,
179
+ arch: Crossbuild::Platform.display_arch(target.arch == :any ? :x86_64 : target.arch),
180
+ platform: target.to_s,
181
+ path: path
182
+ }
183
+ command = Crossbuild::Runner.new(root: root, vars: vars).interpolate(command)
184
+ command = "#{command} #{args.join(' ')}" unless args.empty?
185
+
186
+ puts "▶ #{command}"
187
+ result = system(command, chdir: root)
188
+ raise Error, "launch failed (command not found): #{command}" if result.nil?
189
+
190
+ $?.exitstatus || 0
191
+ end
192
+ end
193
+ end
194
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crosspack
4
- VERSION = '0.6.2'
4
+ VERSION = '0.7.0'
5
5
  end
data/lib/crosspack.rb CHANGED
@@ -1,15 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'crosspack/version'
4
+
5
+ module Crosspack
6
+ # A stage failed (deps/build/pack/launch). Defined before the requires:
7
+ # launcher.rb subclasses it at load time.
8
+ class BuildError < StandardError; end
9
+ end
10
+
4
11
  require_relative 'crosspack/colors'
5
12
  require_relative 'crosspack/target'
6
13
  require_relative 'crosspack/manifest'
14
+ require_relative 'crosspack/command_manifest'
7
15
  require_relative 'crosspack/package_manifest'
8
16
  require_relative 'crosspack/config'
9
17
  require_relative 'crosspack/resolver'
10
18
  require_relative 'crosspack/matrix'
11
19
  require_relative 'crosspack/builds'
12
20
  require_relative 'crosspack/packer'
21
+ require_relative 'crosspack/launcher'
13
22
  require_relative 'crosspack/builders/deb'
14
23
  require_relative 'crosspack/builders/rpm'
15
24
  require_relative 'crosspack/builders/pkgbuild'
@@ -30,7 +39,6 @@ module Crosspack
30
39
  # files: { source_path => destination_inside_package (no leading /) }
31
40
  # symlinks: { link_path_inside_package => link_target }
32
41
  # executables: [destination paths to chmod 0755]
33
- class BuildError < StandardError; end
34
42
 
35
43
  # Convenience wrapper: Crosspack.pack(config: 'crosspack.yml',
36
44
  # target: Target.parse('debian-12'), version: '1.0-1')
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.6.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oleg Orlov
@@ -57,7 +57,9 @@ files:
57
57
  - lib/crosspack/builders/wix.rb
58
58
  - lib/crosspack/builds.rb
59
59
  - lib/crosspack/colors.rb
60
+ - lib/crosspack/command_manifest.rb
60
61
  - lib/crosspack/config.rb
62
+ - lib/crosspack/launcher.rb
61
63
  - lib/crosspack/manifest.rb
62
64
  - lib/crosspack/matrix.rb
63
65
  - lib/crosspack/package_manifest.rb