crosspack 0.2.1 → 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.
@@ -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,
@@ -8,6 +8,10 @@ module Crosspack
8
8
  module Builds
9
9
  ARCH_DIR_RE = /\A(amd64|x86_64|x64|arm64|aarch64)\z/i.freeze
10
10
 
11
+ # Written by `crosspack build <target>` into every builds/<target>/
12
+ # directory; `crosspack pack <target>` reads it as the default version.
13
+ STAMP_NAME = '.crosspack-build'
14
+
11
15
  # Returns [Target] for every target directory found under root.
12
16
  def self.available(root)
13
17
  return [] unless File.directory?(root)
@@ -30,6 +34,14 @@ module Crosspack
30
34
  lines.join("\n")
31
35
  end
32
36
 
37
+ # The version stamped by `crosspack build <target>` into the target's
38
+ # builds/ directory, or nil when the directory has no stamp (packed by
39
+ # an older build or never built through the stage pipeline).
40
+ def self.version_for(target, root)
41
+ stamp = File.join(target.output_dir(root.to_s, target.format), STAMP_NAME)
42
+ File.exist?(stamp) ? File.read(stamp).strip : nil
43
+ end
44
+
33
45
  private
34
46
 
35
47
  def self.scan_family(dir, family)
@@ -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,27 @@ 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.
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).
37
38
  class Manifest
38
- attr_reader :path, :deps, :errors, :warnings
39
+ # The deps: section schema version this crosspack understands. Sections
40
+ # declaring a newer version are rejected at load time with an upgrade
41
+ # hint; older (or absent) versions are accepted.
42
+ SCHEMA_VERSION = 1
39
43
 
40
- def self.load(path)
41
- unless File.file?(path)
42
- raise InvalidManifestError,
43
- "Dependencies file not found: #{path}\n" \
44
- 'Create deps.yaml next to the Rakefile (see crosspack/README.md).'
45
- end
46
- begin
47
- raw = YAML.safe_load(File.read(path), permitted_classes: [], aliases: false)
48
- rescue Psych::SyntaxError => e
49
- raise InvalidManifestError,
50
- "#{path}: YAML syntax error on line #{e.line}: #{e.problem}"
51
- end
52
- new(path, raw)
53
- end
44
+ attr_reader :path, :deps, :schema_version, :errors, :warnings
54
45
 
55
46
  def initialize(path, raw)
56
47
  @path = path
57
- @deps = raw.is_a?(Hash) ? raw : {}
58
- @raw = raw
48
+ @schema_version = raw.is_a?(Hash) ? raw['version'] : nil
59
49
  @errors = []
60
50
  @warnings = []
51
+ check_schema_version!
52
+ @deps = raw.is_a?(Hash) ? raw.reject { |key, _| key == 'version' } : {}
53
+ @raw = raw
61
54
  validate
62
55
  end
63
56
 
@@ -69,7 +62,7 @@ module Crosspack
69
62
  def validate!
70
63
  return self if valid?
71
64
 
72
- lines = ["#{path}: deps.yaml schema is invalid (#{@errors.size} errors):"]
65
+ lines = ["#{path}: the deps: section is invalid (#{@errors.size} errors):"]
73
66
  @errors.each { |e| lines << " ✗ #{e}" }
74
67
  lines << 'Build aborted: fix the listed nodes and retry.'
75
68
  raise InvalidManifestError, lines.join("\n")
@@ -79,9 +72,9 @@ module Crosspack
79
72
  def error_report
80
73
  lines = []
81
74
  if valid?
82
- lines << "#{path}: schema is valid."
75
+ lines << "#{path}: the deps: section is valid."
83
76
  else
84
- lines << "#{path}: deps.yaml schema is invalid (#{@errors.size} errors):"
77
+ lines << "#{path}: the deps: section is invalid (#{@errors.size} errors):"
85
78
  @errors.each { |e| lines << " ✗ #{e}" }
86
79
  end
87
80
  @warnings.each { |w| lines << " ⚠ #{w}" }
@@ -90,15 +83,36 @@ module Crosspack
90
83
 
91
84
  private
92
85
 
86
+ # A schema version newer than this crosspack supports is a hard, loud
87
+ # error at load time — every command (resolve, matrix, pack) reports it,
88
+ # not just validate. Malformed versions (not an integer >= 1) are
89
+ # ordinary schema errors.
90
+ def check_schema_version!
91
+ v = @schema_version
92
+ return if v.nil?
93
+
94
+ unless v.is_a?(Integer) && v >= 1
95
+ @errors << Issue.new('version', "schema version must be an integer >= 1, got #{v.inspect}")
96
+ return
97
+ end
98
+ return if v <= SCHEMA_VERSION
99
+
100
+ raise InvalidManifestError,
101
+ "#{@path}: the deps: section declares schema version #{v}, " \
102
+ "but this crosspack (#{Crosspack::VERSION}) supports up to #{SCHEMA_VERSION}. " \
103
+ 'Update the gem (gem update crosspack) or lower the declared version.'
104
+ end
105
+
93
106
  def validate
94
107
  unless @raw.is_a?(Hash) && !@raw.empty?
95
108
  @errors << Issue.new(
96
109
  '(root)',
97
- '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'
98
112
  )
99
113
  return
100
114
  end
101
- @raw.each { |name, body| validate_dep(name.to_s, body) }
115
+ @raw.each { |name, body| validate_dep(name.to_s, body) unless name == 'version' }
102
116
  end
103
117
 
104
118
  def validate_dep(name, body)
@@ -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,30 +4,33 @@ 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
+ # version: when omitted, the version stamped by `crosspack build <target>`
12
+ # (.crosspack-build in the target's builds/ directory) is used.
11
13
  class Packer
12
- def self.pack(manifest:, deps:, target:, version:, root: Dir.pwd,
14
+ def self.pack(config:, target:, version: nil, root: Dir.pwd,
13
15
  output_base: 'crosspacks')
14
- new(manifest: manifest, deps: deps, target: target, version: version,
16
+ new(config: config, target: target, version: version,
15
17
  root: root, output_base: output_base).pack
16
18
  end
17
19
 
18
- def initialize(manifest:, deps:, target:, version:, root:, output_base:)
19
- @pkg_path = manifest
20
- @deps_path = deps
20
+ def initialize(config:, target:, version:, root:, output_base:)
21
+ @config = config
21
22
  @target = target
22
- @version = version.to_s
23
+ @version = version && version.to_s
23
24
  @root = root
24
- @output_base = output_base
25
+ @output_base = output_base || 'crosspacks'
25
26
  end
26
27
 
27
28
  def pack
28
- @pkg = PackageManifest.load(@pkg_path)
29
- @deps = Manifest.load(@deps_path)
29
+ @config = Config.coerce(@config, root: @root)
30
+ @pkg = @config.package_manifest
31
+ @deps = @config.deps_manifest
30
32
  validate_both!
33
+ resolve_version!
31
34
  @pkg_data = build_layout
32
35
 
33
36
  case @target.format
@@ -49,8 +52,24 @@ module Crosspack
49
52
 
50
53
  private
51
54
 
55
+ # --version wins; otherwise the version recorded by the build stage.
56
+ def resolve_version!
57
+ return unless @version.nil? || @version.strip.empty?
58
+
59
+ stamped = Builds.version_for(@target, File.expand_path(@pkg.sources, @root))
60
+ if stamped.nil? || stamped.strip.empty?
61
+ raise BuildError,
62
+ "No version for target #{@target}: no --version given and no build stamp " \
63
+ "(#{Builds::STAMP_NAME}) in #{@pkg.sources}. " \
64
+ "Run: crosspack build #{@target} — or pass --version explicitly."
65
+ end
66
+
67
+ @version = stamped
68
+ end
69
+
52
70
  def validate_both!
53
71
  errors = []
72
+ errors << @config.error_report unless @config.valid?
54
73
  errors << @pkg.error_report unless @pkg.valid?
55
74
  errors << @deps.error_report unless @deps.valid?
56
75
  return if errors.empty?
@@ -70,10 +89,10 @@ module Crosspack
70
89
  hint = available.empty? ? 'no built targets at all.' :
71
90
  "available builds: #{available.map { |t| "#{t} (#{t.output_dir(@pkg.sources, t.format)})" }.join(', ')}."
72
91
  raise BuildError,
73
- "No compiled artifacts for target #{@target}: expected them in #{src_dir}.\n" \
92
+ "Build stage not done for target #{@target}: expected compiled artifacts in #{src_dir}.\n" \
74
93
  "The builds tree (#{@pkg.sources}) mirrors the output tree: builds/<family>/<version>/<arch>/. " \
75
94
  "Currently #{hint}\n" \
76
- 'Build the application for this target first, then pack.'
95
+ "Run: crosspack build #{@target}"
77
96
  end
78
97
 
79
98
  lib_dst = File.join(@pkg.prefix, @pkg.lib_dir)
@@ -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.2.1'
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,35 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crosspack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oleg Orlov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-11 00:00:00.000000000 Z
11
+ date: 2026-09-13 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: 'Two cooperating commands: crossbuild runs the build steps of a project
14
- from a build.yaml matrix and fans the produced artifacts out into the per-target
15
- builds/ tree; crosspack turns that tree into native packages, driven by a cross-distro
16
- deps.yaml. Both share the same target vocabulary so the trees never drift apart.'
13
+ description: 'One command, one target vocabulary: crosspack deps <target> verifies
14
+ and installs the build host dependencies, crosspack build <target> runs the build.yaml
15
+ matrix entry and fans artifacts into builds/, crosspack pack <target> turns that
16
+ tree into a native package. Stages are gated — no packing before building — and
17
+ keyed by the same target strings throughout.'
17
18
  email:
18
19
  - orelcokolov@gmail.com
19
20
  executables:
20
21
  - crosspack
21
- - crossbuild
22
22
  extensions: []
23
23
  extra_rdoc_files: []
24
24
  files:
25
25
  - CHANGELOG.md
26
26
  - LICENSE
27
27
  - README.md
28
- - exe/crossbuild
29
28
  - exe/crosspack
30
29
  - lib/crossbuild.rb
31
30
  - lib/crossbuild/build_manifest.rb
32
31
  - lib/crossbuild/builder.rb
32
+ - lib/crossbuild/dep_installer.rb
33
33
  - lib/crossbuild/distributor.rb
34
34
  - lib/crossbuild/matrix.rb
35
35
  - lib/crossbuild/platform.rb
@@ -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
@@ -74,5 +75,5 @@ requirements: []
74
75
  rubygems_version: 3.5.22
75
76
  signing_key:
76
77
  specification_version: 4
77
- summary: Cross-platform build orchestrator and native package builder (deb/rpm/PKGBUILD/winget/cask)
78
+ summary: 'Staged cross-platform pipeline: deps → build → pack native packages (deb/rpm/PKGBUILD/winget/cask)'
78
79
  test_files: []