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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -0
- data/README.md +148 -117
- data/exe/crosspack +118 -44
- data/lib/crossbuild/build_manifest.rb +122 -27
- data/lib/crossbuild/builder.rb +32 -6
- data/lib/crossbuild/dep_installer.rb +127 -0
- data/lib/crossbuild/distributor.rb +15 -2
- data/lib/crossbuild/matrix.rb +29 -2
- data/lib/crossbuild/platform.rb +35 -2
- data/lib/crossbuild/runner.rb +1 -1
- data/lib/crossbuild/version.rb +1 -1
- data/lib/crossbuild/version_scheme.rb +3 -2
- data/lib/crossbuild.rb +15 -7
- data/lib/crosspack/builders/pkgbuild.rb +1 -1
- data/lib/crosspack/builders/rpm.rb +2 -1
- data/lib/crosspack/builds.rb +12 -0
- data/lib/crosspack/config.rb +164 -0
- data/lib/crosspack/manifest.rb +44 -30
- data/lib/crosspack/matrix.rb +2 -2
- data/lib/crosspack/package_manifest.rb +16 -26
- data/lib/crosspack/packer.rb +34 -15
- data/lib/crosspack/resolver.rb +2 -2
- data/lib/crosspack/version.rb +1 -1
- data/lib/crosspack.rb +3 -2
- metadata +10 -9
- data/exe/crossbuild +0 -76
|
@@ -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.
|
|
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,
|
data/lib/crosspack/builds.rb
CHANGED
|
@@ -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
|
data/lib/crosspack/manifest.rb
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
35
|
-
# names; each maps to
|
|
36
|
-
# scalar verdict for a
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
@
|
|
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
|
|
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}:
|
|
75
|
+
lines << "#{path}: the deps: section is valid."
|
|
83
76
|
else
|
|
84
|
-
lines << "#{path}: deps
|
|
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
|
-
'
|
|
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)
|
data/lib/crosspack/matrix.rb
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
7
|
-
# payload, executables, symlinks, desktop entry,
|
|
8
|
-
# crosspack's business; artifacts are picked from
|
|
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[
|
|
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
|
|
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}:
|
|
38
|
+
"#{path}: the package: section is valid."
|
|
55
39
|
else
|
|
56
|
-
["#{path}: package
|
|
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)',
|
|
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
|
|
data/lib/crosspack/packer.rb
CHANGED
|
@@ -4,30 +4,33 @@ require 'fileutils'
|
|
|
4
4
|
require 'tmpdir'
|
|
5
5
|
|
|
6
6
|
module Crosspack
|
|
7
|
-
# Single entry point: takes a
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
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(
|
|
14
|
+
def self.pack(config:, target:, version: nil, root: Dir.pwd,
|
|
13
15
|
output_base: 'crosspacks')
|
|
14
|
-
new(
|
|
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(
|
|
19
|
-
@
|
|
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
|
-
@
|
|
29
|
-
@
|
|
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
|
-
"
|
|
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
|
-
|
|
95
|
+
"Run: crosspack build #{@target}"
|
|
77
96
|
end
|
|
78
97
|
|
|
79
98
|
lib_dst = File.join(@pkg.prefix, @pkg.lib_dir)
|
data/lib/crosspack/resolver.rb
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
data/lib/crosspack/version.rb
CHANGED
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(
|
|
34
|
-
#
|
|
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.
|
|
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
|
+
date: 2026-09-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
|
-
description: '
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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:
|
|
78
|
+
summary: 'Staged cross-platform pipeline: deps → build → pack native packages (deb/rpm/PKGBUILD/winget/cask)'
|
|
78
79
|
test_files: []
|