libpng 1.6.58.4-aarch64-linux-ohos

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.
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ module Libpng
6
+ # FFI bindings to the bundled `libpng16.{so,dylib,dll}`. Lives in its
7
+ # own autoloaded module so that requiring `libpng` (the top-level
8
+ # entry point used by `ext/extconf.rb` during the source-gem build)
9
+ # does NOT eagerly dlopen the shared library -- it may not exist yet
10
+ # at that point.
11
+ #
12
+ # The first time an encoder or decoder references `Libpng::Binding`,
13
+ # this file loads, ffi_lib fires, and the FFI functions attach. From
14
+ # then on, calls go directly through the attached C functions.
15
+ module Binding
16
+ extend FFI::Library
17
+
18
+ ffi_lib_flags :now, :global
19
+
20
+ lib_filename = if FFI::Platform.windows?
21
+ 'libpng16.dll'
22
+ elsif FFI::Platform.mac?
23
+ 'libpng16.dylib'
24
+ else
25
+ 'libpng16.so'
26
+ end
27
+
28
+ # __dir__ is lib/libpng/; the shared library ships at lib/libpng/.
29
+ ffi_lib File.expand_path(lib_filename, __dir__)
30
+
31
+ # libpng simplified API (png_image / png_image_*).
32
+ attach_function :png_image_begin_read_from_memory,
33
+ %i[pointer pointer size_t], :int
34
+ attach_function :png_image_finish_read,
35
+ %i[pointer pointer pointer int pointer], :int
36
+ attach_function :png_image_write_to_memory,
37
+ %i[pointer pointer pointer int pointer int pointer], :int
38
+ attach_function :png_image_free, [:pointer], :void
39
+
40
+ # libpng standard (non-simplified) write API.
41
+ attach_function :png_create_write_struct,
42
+ %i[string pointer pointer pointer], :pointer
43
+ attach_function :png_create_info_struct, [:pointer], :pointer
44
+ attach_function :png_destroy_write_struct, %i[pointer pointer], :void
45
+ attach_function :png_set_IHDR,
46
+ %i[pointer pointer uint32 uint32 int int int int int], :void
47
+ attach_function :png_set_rows, %i[pointer pointer pointer], :void
48
+ attach_function :png_set_PLTE, %i[pointer pointer pointer int], :void
49
+ attach_function :png_set_tRNS,
50
+ %i[pointer pointer pointer int pointer], :void
51
+ attach_function :png_set_filter, %i[pointer int int], :void
52
+ attach_function :png_set_compression_level, %i[pointer int], :void
53
+ attach_function :png_set_write_fn,
54
+ %i[pointer pointer pointer pointer], :void
55
+ attach_function :png_write_png, %i[pointer pointer int pointer], :void
56
+ end
57
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Libpng
4
+ # Maps pixel formats to bytes-per-pixel. Used by both the simplified
5
+ # and standard encoders / decoders to compute row stride.
6
+ #
7
+ # Pure data class -- no I/O, no allocations beyond the lookup itself.
8
+ module BytesPerPixel
9
+ # Per-channel byte count for the supported bit depths. Linear (16-bit)
10
+ # formats aren't currently exposed via the simplified API path; the
11
+ # standard encoder uses this table when bit_depth: 16 is requested.
12
+ CHANNEL_BYTES_8_BIT = 1
13
+ CHANNEL_BYTES_16_BIT = 2
14
+
15
+ # Channels per PNG_COLOR_TYPE_* (png.h).
16
+ CHANNELS_BY_COLOR_TYPE = {
17
+ Libpng::COLOR_TYPE_GRAY => 1,
18
+ Libpng::COLOR_TYPE_GRAY_ALPHA => 2,
19
+ Libpng::COLOR_TYPE_RGB => 3,
20
+ Libpng::COLOR_TYPE_RGB_ALPHA => 4,
21
+ Libpng::COLOR_TYPE_PALETTE => 1
22
+ }.freeze
23
+
24
+ def self.for_format(format_value)
25
+ case format_value
26
+ when Libpng::FORMAT_GRAY then 1
27
+ when Libpng::FORMAT_GA, Libpng::FORMAT_AG then 2
28
+ when Libpng::FORMAT_RGB, Libpng::FORMAT_BGR then 3
29
+ when Libpng::FORMAT_RGBA, Libpng::FORMAT_ARGB,
30
+ Libpng::FORMAT_BGRA, Libpng::FORMAT_ABGR then 4
31
+ else
32
+ raise Libpng::Error, "no bytes-per-pixel mapping for format 0x#{format_value.to_s(16)}"
33
+ end
34
+ end
35
+
36
+ def self.for_color_type(color_type, bit_depth: 8)
37
+ channels = CHANNELS_BY_COLOR_TYPE[color_type] ||
38
+ raise(Libpng::Error, "unknown color_type #{color_type}")
39
+ channel_bytes = bit_depth == 16 ? CHANNEL_BYTES_16_BIT : CHANNEL_BYTES_8_BIT
40
+ channels * channel_bytes
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,271 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zlib'
4
+
5
+ module Libpng
6
+ # Walks a PNG byte buffer chunk-by-chunk. Used to strip ancillary
7
+ # chunks (sRGB, gAMA, cHRM, iCCP, etc.) that libpng's simplified
8
+ # write API emits by default, and to extract metadata (IHDR fields,
9
+ # text chunks, color chunks, pHYs) during decode.
10
+ #
11
+ # The chunk layout is documented in the PNG spec:
12
+ # [4 bytes length][4 bytes type][length bytes data][4 bytes CRC]
13
+ # Length and CRC are big-endian uint32. The signature is the 8 bytes
14
+ # before the first chunk: 89 50 4E 47 0D 0A 1A 0A.
15
+ class ChunkWalker
16
+ SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10].freeze
17
+
18
+ # Raised on any structural issue: bad signature, chunk running past
19
+ # EOF, CRC mismatch, missing IEND.
20
+ class FormatError < Error; end
21
+
22
+ # PNG gAMA fixed-point scale (per spec: gamma = uint32 / 100000).
23
+ GAMMA_SCALE = 100_000.0
24
+
25
+ # PNG cHRM fixed-point scale (per spec: chromaticity = uint32 / 100000).
26
+ CHRM_SCALE = 100_000.0
27
+
28
+ def initialize(png_bytes)
29
+ @bytes = png_bytes.bytes
30
+ end
31
+
32
+ # Iterate every chunk in order. Yields [type_string, data_bytes,
33
+ # offset] to the block. Returns an Enumerator if no block given.
34
+ def each_chunk
35
+ return enum_for(:each_chunk) unless block_given?
36
+
37
+ verify_signature
38
+ offset = 8
39
+ until offset + 8 > @bytes.length
40
+ len = @bytes[offset, 4].pack('C*').unpack1('N')
41
+ type = @bytes[offset + 4, 4].pack('C*')
42
+ chunk_total = 12 + len
43
+ if offset + chunk_total > @bytes.length
44
+ raise FormatError,
45
+ "PNG chunk at offset #{offset} (#{type}) runs past EOF"
46
+ end
47
+
48
+ crc_input = @bytes[offset + 4, 4 + len].pack('C*')
49
+ crc_actual = @bytes[offset + 8 + len, 4].pack('C*').unpack1('N')
50
+ crc_expected = Zlib.crc32(crc_input)
51
+ raise FormatError, "PNG chunk CRC mismatch at offset #{offset} (#{type})" unless crc_actual == crc_expected
52
+
53
+ data = @bytes[offset + 8, len].pack('C*')
54
+ yield type, data, offset
55
+
56
+ offset += chunk_total
57
+ break if type == 'IEND'
58
+ end
59
+ end
60
+
61
+ # Returns a new binary String containing only the PNG signature
62
+ # plus IHDR, IDAT, and IEND chunks. Other chunks (sRGB, gAMA, cHRM,
63
+ # iCCP, tEXt, zTXt, iTXt, bKGD, pHYs, oFFs, tIME, sCAL, hIST,
64
+ # sPLT, unknown chunks) are dropped.
65
+ def strip_ancillary
66
+ kept = SIGNATURE.pack('C*')
67
+ each_chunk do |type, data, _offset|
68
+ next unless %w[IHDR IDAT IEND].include?(type)
69
+
70
+ len_bytes = [data.length].pack('N')
71
+ type_bytes = type
72
+ crc_bytes = [Zlib.crc32(type_bytes + data)].pack('N')
73
+ kept << len_bytes << type_bytes << data << crc_bytes
74
+ end
75
+ kept.force_encoding('ASCII-8BIT')
76
+ end
77
+
78
+ # Returns the raw IHDR data bytes (13 bytes), or raises if missing.
79
+ def ihdr_data
80
+ each_chunk { |type, data, _| return data if type == 'IHDR' }
81
+ raise FormatError, 'PNG has no IHDR chunk'
82
+ end
83
+
84
+ # Parse IHDR fields. Returns a Hash with :width, :height,
85
+ # :bit_depth, :color_type, :compression, :filter, :interlace.
86
+ def ihdr_fields
87
+ data = ihdr_data
88
+ raise FormatError, "IHDR is #{data.length} bytes, expected 13" unless data.length == 13
89
+
90
+ width, height, bd, ct, comp, filt, intc = data.unpack('NNCCCCC')
91
+ {
92
+ width: width,
93
+ height: height,
94
+ bit_depth: bd,
95
+ color_type: ct,
96
+ compression: comp,
97
+ filter: filt,
98
+ interlace: intc
99
+ }
100
+ end
101
+
102
+ # Parse text chunks (tEXt, zTXt, iTXt) into a flat Hash of
103
+ # keyword -> UTF-8 String. When the same keyword appears in
104
+ # multiple chunks, the last occurrence wins (PNG spec permits
105
+ # this; behavior matches libpng's read path).
106
+ #
107
+ # - tEXt: Latin-1 keyword + Latin-1 value, transcoded to UTF-8.
108
+ # - zTXt: Latin-1 keyword + zlib-compressed Latin-1 value,
109
+ # inflated then transcoded to UTF-8.
110
+ # - iTXt: UTF-8 keyword + UTF-8 value (optionally zlib-compressed),
111
+ # with separate language tag and translated keyword that are
112
+ # dropped here (callers who need them should walk chunks directly).
113
+ #
114
+ # Malformed chunks are silently skipped: a single broken zTXt
115
+ # should not poison the rest of the decode.
116
+ def text_chunks
117
+ result = {}
118
+ each_chunk do |type, data, _|
119
+ case type
120
+ when 'tEXt'
121
+ parse_text(data, result)
122
+ when 'zTXt'
123
+ parse_ztxt(data, result)
124
+ when 'iTXt'
125
+ parse_itxt(data, result)
126
+ end
127
+ end
128
+ result
129
+ end
130
+
131
+ # Parse color-space chunks (gAMA, cHRM, sRGB, iCCP) into a Hash.
132
+ # Returns Symbol keys:
133
+ #
134
+ # :gamma -> Float (gAMA, value/100000)
135
+ # :white_point_x, :white_point_y -> Float (cHRM)
136
+ # :red_x, :red_y, :green_x, :green_y, :blue_x, :blue_y -> Float
137
+ # :srgb_intent -> Integer 0..3 (sRGB)
138
+ # :icc_profile_name -> UTF-8 String (iCCP, profile name)
139
+ # :icc_profile -> binary String (iCCP, decompressed bytes)
140
+ #
141
+ # Malformed chunks (e.g. truncated cHRM, bad iCCP compression) are
142
+ # silently skipped. ICC profiles can be large (10KB-1MB); callers
143
+ # who don't need them should walk chunks manually rather than call
144
+ # this method.
145
+ def color_chunks
146
+ result = {}
147
+ each_chunk do |type, data, _|
148
+ case type
149
+ when 'gAMA'
150
+ parse_gama(data, result)
151
+ when 'cHRM'
152
+ parse_chrm(data, result)
153
+ when 'sRGB'
154
+ parse_srgb(data, result)
155
+ when 'iCCP'
156
+ parse_iccp(data, result)
157
+ end
158
+ end
159
+ result
160
+ end
161
+
162
+ private
163
+
164
+ def verify_signature
165
+ sig = @bytes.first(8)
166
+ raise FormatError, 'not a PNG file (bad signature)' unless sig == SIGNATURE
167
+ end
168
+
169
+ # Split a binary string at the first NUL byte. Returns [head, tail]
170
+ # where head excludes the NUL and tail is everything after. Returns
171
+ # [nil, nil] when no NUL is present.
172
+ def split_at_null(bytes)
173
+ idx = bytes.index("\x00")
174
+ return [nil, nil] if idx.nil?
175
+
176
+ [bytes[0, idx], bytes[(idx + 1)..]]
177
+ end
178
+
179
+ # Force Latin-1 byte sequence into UTF-8. Bytes 0x80-0xFF that have
180
+ # no assigned meaning in ISO-8859-1 still round-trip through this
181
+ # encoding because Latin-1 is a strict superset of the byte values.
182
+ def latin1_to_utf8(bytes)
183
+ bytes.force_encoding('ISO-8859-1').encode('UTF-8')
184
+ end
185
+
186
+ def parse_text(data, result)
187
+ key, value = split_at_null(data)
188
+ return if key.nil?
189
+
190
+ result[key] = latin1_to_utf8(value)
191
+ end
192
+
193
+ def parse_ztxt(data, result)
194
+ key, rest = split_at_null(data)
195
+ return if key.nil? || rest.nil? || rest.length < 2
196
+
197
+ _compression_method = rest.getbyte(0)
198
+ inflated = Zlib.inflate(rest[1..])
199
+ result[key] = latin1_to_utf8(inflated)
200
+ rescue Zlib::Error
201
+ # Skip malformed zTXt rather than failing the whole decode.
202
+ end
203
+
204
+ def parse_itxt(data, result)
205
+ # iTXt layout:
206
+ # keyword\0
207
+ # compression_flag (1 byte, 0 = uncompressed, 1 = zlib)
208
+ # compression_method (1 byte, 0 = zlib/deflate)
209
+ # language_tag\0 (ASCII, may be empty)
210
+ # translated_keyword\0 (UTF-8, may be empty)
211
+ # text (UTF-8, optionally zlib-compressed)
212
+ key, rest = split_at_null(data)
213
+ return if key.nil? || rest.nil? || rest.length < 3
214
+
215
+ compression_flag = rest.getbyte(0)
216
+ _compression_method = rest.getbyte(1)
217
+ _lang, after_lang = split_at_null(rest[2..])
218
+ return if after_lang.nil?
219
+
220
+ _translated, text_bytes = split_at_null(after_lang)
221
+ return if text_bytes.nil?
222
+
223
+ text_bytes = Zlib.inflate(text_bytes) if compression_flag == 1
224
+ result[key] = text_bytes.force_encoding('UTF-8')
225
+ rescue Zlib::Error
226
+ # Skip malformed iTXt rather than failing the whole decode.
227
+ end
228
+
229
+ def parse_gama(data, result)
230
+ return if data.length < 4
231
+
232
+ result[:gamma] = data.unpack1('N') / GAMMA_SCALE
233
+ end
234
+
235
+ def parse_chrm(data, result)
236
+ return if data.length < 32
237
+
238
+ wp_x, wp_y, rx, ry, gx, gy, bx, by = data.unpack('N8')
239
+ result[:white_point_x] = wp_x / CHRM_SCALE
240
+ result[:white_point_y] = wp_y / CHRM_SCALE
241
+ result[:red_x] = rx / CHRM_SCALE
242
+ result[:red_y] = ry / CHRM_SCALE
243
+ result[:green_x] = gx / CHRM_SCALE
244
+ result[:green_y] = gy / CHRM_SCALE
245
+ result[:blue_x] = bx / CHRM_SCALE
246
+ result[:blue_y] = by / CHRM_SCALE
247
+ end
248
+
249
+ def parse_srgb(data, result)
250
+ return if data.empty?
251
+
252
+ result[:srgb_intent] = data.getbyte(0)
253
+ end
254
+
255
+ def parse_iccp(data, result)
256
+ # iCCP layout:
257
+ # profile_name\0
258
+ # compression_method (1 byte, must be 0 = zlib/deflate)
259
+ # compressed_profile (zlib stream)
260
+ name, rest = split_at_null(data)
261
+ return if name.nil? || rest.nil? || rest.length < 2
262
+
263
+ _compression_method = rest.getbyte(0)
264
+ profile = Zlib.inflate(rest[1..])
265
+ result[:icc_profile_name] = name.force_encoding('UTF-8')
266
+ result[:icc_profile] = profile
267
+ rescue Zlib::Error
268
+ # Skip malformed iCCP rather than failing the whole decode.
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Libpng
4
+ # Result of {Libpng.decode}. A frozen-keyword Struct so it crosses
5
+ # Ractor boundaries cleanly via moving semantics.
6
+ #
7
+ # +width+ image width in pixels (Integer)
8
+ # +height+ image height in pixels (Integer)
9
+ # +format+ pixel format String ("RGBA", "RGB", "GRAY", "GA")
10
+ # +pixels+ binary String of raw pixel bytes, row-major top-down
11
+ # +bit_depth+ bits per channel (8 or 16). nil if not extracted.
12
+ # +color_type+ PNG_COLOR_TYPE_* integer from IHDR. nil if not extracted.
13
+ # +interlace+ PNG_INTERLACE_* integer from IHDR. nil if not extracted.
14
+ # +text+ Hash<String,String> of tEXt/zTXt/iTXt keyword -> UTF-8
15
+ # value. Empty Hash when no text chunks present.
16
+ # +color+ Hash<Symbol,*> of gAMA/cHRM/sRGB/iCCP fields. Empty
17
+ # Hash when no color metadata present.
18
+ DecodedImage = Struct.new(:width, :height, :format, :pixels,
19
+ :bit_depth, :color_type, :interlace,
20
+ :text, :color,
21
+ keyword_init: true)
22
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Libpng
4
+ # Raised on any libpng-side failure: bad dimensions, unknown pixel
5
+ # format, corrupt PNG input, libpng-internal error captured via the
6
+ # error callback, etc.
7
+ class Error < StandardError; end
8
+ end
Binary file
@@ -0,0 +1,247 @@
1
+ require 'rbconfig'
2
+ require 'mini_portile2'
3
+ require 'pathname'
4
+ require 'tmpdir'
5
+ require 'open3'
6
+
7
+ module Libpng
8
+ # MiniPortile-based recipe for building libpng from source. Mirrors the
9
+ # pattern used by emf2svg-ruby: during `gem install`, ext/extconf.rb
10
+ # invokes Recipe#cook, which downloads the libpng source tarball, runs
11
+ # configure + make, and installs the shared library into the gem's lib/
12
+ # directory. The pre-compiled gems (built via `rake gem:native:<plat>`)
13
+ # ship the .so/.dylib/.dll and disable extconf.rb entirely.
14
+ class Recipe < MiniPortileCMake
15
+ # Pinned libpng source URL + sha256. Bump deliberately to refresh
16
+ # the upstream — and remember to update both fields together.
17
+ LIBPNG_URL = "https://downloads.sourceforge.net/project/libpng/libpng16/#{Libpng::LIBPNG_VERSION}/libpng-#{Libpng::LIBPNG_VERSION}.tar.gz".freeze
18
+ # sha256 of the libpng-X.Y.Z.tar.gz tarball. Verify with:
19
+ # curl -sL <URL> | shasum -a 256
20
+ LIBPNG_SHA256 = '8c9b05b675ca7301a458df2c2e46f26e1d41ff36b8863f8c33530bc58c2e6225'.freeze
21
+
22
+ ROOT = Pathname.new(File.expand_path('../..', __dir__))
23
+
24
+ def initialize
25
+ super('libpng', Libpng::LIBPNG_VERSION)
26
+
27
+ @files << {
28
+ url: LIBPNG_URL,
29
+ sha256: LIBPNG_SHA256
30
+ }
31
+
32
+ @target = ROOT.join(@target).to_s
33
+ @printed = {}
34
+ setup_cross_compile if cross_compile?
35
+ end
36
+
37
+ # libpng ships a CMake build alongside the autotools one. We use CMake
38
+ # for consistency with mini_portile2's defaults and to handle Windows
39
+ # cleanly (autotools on native Windows is fragile).
40
+ # No additional configuration is needed; defaults build PNG_SHARED=ON
41
+ # and PNG_STATIC=OFF, which is what we want.
42
+
43
+ def cook_if_not
44
+ cook unless File.exist?(checkpoint)
45
+ end
46
+
47
+ def cook
48
+ super
49
+ FileUtils.touch(checkpoint)
50
+ end
51
+
52
+ def checkpoint
53
+ File.join(@target, "#{name}-#{version}-#{target_platform}.installed")
54
+ end
55
+
56
+ def configure_defaults
57
+ # Build a static+self-contained shared library: we only ship the .so
58
+ # to consumers, so libpng's transitive dep on zlib must be linked in.
59
+ opts = super
60
+ opts << '-DPNG_SHARED=ON'
61
+ opts << '-DPNG_STATIC=OFF'
62
+ opts << '-DPNG_TESTS=OFF'
63
+ opts << '-DPNG_FRAMEWORK=OFF'
64
+ # macOS: avoid the .framework build; we want a plain .dylib.
65
+ opts << '-DCMAKE_INSTALL_LIBDIR=lib'
66
+ opts << '-DCMAKE_BUILD_TYPE=Release'
67
+ opts
68
+ end
69
+
70
+ def install
71
+ super
72
+ # After `make install`, the shared lib lives under ports/<name>/<ver>/.
73
+ # On Linux/macOS that's lib/. On Windows, CMake's GNUInstallDirs puts
74
+ # the .dll in bin/ and the import library (.dll.a) in lib/ — we only
75
+ # ship the .dll, so search both.
76
+ libs = Dir.glob(File.join(port_path, shared_lib_install_glob))
77
+ raise "no libpng shared lib produced under #{port_path}" if libs.empty?
78
+
79
+ target_dir = ROOT.join('lib', 'libpng')
80
+ FileUtils.mkdir_p(target_dir)
81
+ FileUtils.cp_r(libs, target_dir, verbose: true)
82
+
83
+ verify_libs
84
+ end
85
+
86
+ def verify_libs
87
+ each_built_lib do |path|
88
+ out, st = Open3.capture2("file #{path}")
89
+ raise "Failed to query file #{path}: #{out}" unless st.exitstatus.zero?
90
+
91
+ next if target_format.eql?('skip')
92
+
93
+ raise "Invalid file format '#{out}', /#{target_format.source}/ expected" unless target_format.match?(out)
94
+
95
+ message("Verifying #{path} ... OK\n")
96
+ end
97
+ end
98
+
99
+ def execute(action, command, command_opts = {})
100
+ super(action, command, command_opts.merge(debug: false))
101
+ end
102
+
103
+ def message(text)
104
+ return super unless text.start_with?("\rDownloading")
105
+
106
+ match = text.match(/(\rDownloading .*)\(\s*\d+%\)/)
107
+ pattern = match ? match[1] : text
108
+ return if @printed[pattern]
109
+
110
+ @printed[pattern] = true
111
+ super
112
+ end
113
+
114
+ private
115
+
116
+ def port_path
117
+ File.join(@target, 'ports', "#{name}-#{version}") # MiniPortile default
118
+ end
119
+
120
+ def shared_lib_glob
121
+ if MiniPortile.windows?
122
+ 'libpng16*.dll'
123
+ elsif MiniPortile.darwin?
124
+ 'libpng16*.dylib'
125
+ else
126
+ 'libpng16.so*'
127
+ end
128
+ end
129
+
130
+ # Glob (with port_path prefix) for the freshly installed shared lib.
131
+ # On Windows the .dll installs to bin/; on Unix-likes it stays in lib/.
132
+ def shared_lib_install_glob
133
+ if MiniPortile.windows?
134
+ '{bin,lib}/libpng16*.dll'
135
+ else
136
+ "lib/#{shared_lib_glob}"
137
+ end
138
+ end
139
+
140
+ def each_built_lib(&block)
141
+ Dir.glob(ROOT.join('lib', 'libpng', shared_lib_glob)).each(&block)
142
+ end
143
+
144
+ def host_platform
145
+ @host_platform ||=
146
+ case @host
147
+ when /\Ax86_64.*mingw32/
148
+ 'x64-mingw32'
149
+ when /\A(aarch64|arm64).*mingw/
150
+ 'aarch64-mingw-ucrt'
151
+ when /\Ax86_64.*linux-musl/
152
+ 'x86_64-linux-musl'
153
+ when /\A(aarch64|arm64).*linux-musl/
154
+ 'aarch64-linux-musl'
155
+ when /\Ax86_64.*linux/
156
+ 'x86_64-linux'
157
+ when /\A(aarch64|arm64).*linux/
158
+ 'aarch64-linux'
159
+ when /\Ax86_64.*(darwin|macos|osx)/
160
+ 'x86_64-darwin'
161
+ when /\A(arm64|aarch64).*(darwin|macos|osx)/
162
+ 'arm64-darwin'
163
+ else
164
+ @host
165
+ end
166
+ end
167
+
168
+ def target_platform
169
+ @target_platform ||=
170
+ case ENV.fetch('target_platform', nil)
171
+ when /\A(arm64|aarch64).*(darwin|macos|osx)/
172
+ 'arm64-darwin'
173
+ when /\Ax86_64.*(darwin|macos|osx)/
174
+ 'x86_64-darwin'
175
+ when /\A(arm64|aarch64).*linux-musl/
176
+ 'aarch64-linux-musl'
177
+ when /\A(arm64|aarch64).*linux-ohos/
178
+ # OHOS (OpenHarmony / Huawei HarmonyOS PC) is musl-based arm64.
179
+ # The resulting binary is ELF64 aarch64 linked against musl,
180
+ # identical at the file-format level to aarch64-linux-musl --
181
+ # only the gem's platform label differs so RubyGems on OHOS
182
+ # selects the right variant.
183
+ 'aarch64-linux-ohos'
184
+ when /\A(arm64|aarch64).*linux/
185
+ 'aarch64-linux'
186
+ when /\Ax86_64.*linux-musl/
187
+ 'x86_64-linux-musl'
188
+ else
189
+ ENV.fetch('target_platform', host_platform)
190
+ end
191
+ end
192
+
193
+ def cross_compile?
194
+ target_platform != host_platform
195
+ end
196
+
197
+ # Configure MiniPortile + CMake for cross-compilation. Native builds
198
+ # (host_platform == target_platform) skip this entirely.
199
+ def setup_cross_compile
200
+ # All targeted platforms now have native runners (ubuntu-24.04-arm for
201
+ # aarch64-linux, windows-11-arm for aarch64-mingw-ucrt, Alpine containers
202
+ # for the musl variants). This hook is kept as a seam for future
203
+ # cross-compile targets (e.g. aarch64-linux on an x86_64 host).
204
+ end
205
+
206
+ def cpu_type
207
+ case target_platform
208
+ when 'aarch64-linux', 'aarch64-linux-musl', 'aarch64-linux-ohos',
209
+ 'arm64-darwin', 'aarch64-mingw-ucrt' then 'aarch64'
210
+ when 'x86_64-linux', 'x86_64-linux-musl', 'x86_64-darwin', /\Ax64-mingw/ then 'x86_64'
211
+ else
212
+ super
213
+ end
214
+ end
215
+
216
+ def cmake_system_name
217
+ case target_platform
218
+ when 'aarch64-linux', 'x86_64-linux', 'aarch64-linux-musl',
219
+ 'x86_64-linux-musl', 'aarch64-linux-ohos' then 'Linux'
220
+ when 'arm64-darwin', 'x86_64-darwin' then 'Darwin'
221
+ when /\A(aarch64-)?mingw/, /\Ax64-mingw/ then 'Windows'
222
+ else
223
+ super
224
+ end
225
+ end
226
+
227
+ def target_format
228
+ @target_format ||=
229
+ case target_platform
230
+ when 'arm64-darwin'
231
+ /Mach-O 64-bit dynamically linked shared library arm64/
232
+ when 'x86_64-darwin'
233
+ /Mach-O 64-bit dynamically linked shared library x86_64/
234
+ when 'aarch64-linux', 'aarch64-linux-musl', 'aarch64-linux-ohos'
235
+ /ELF 64-bit LSB shared object, ARM aarch64/
236
+ when 'x86_64-linux', 'x86_64-linux-musl'
237
+ /ELF 64-bit LSB shared object, x86-64/
238
+ when 'aarch64-mingw-ucrt'
239
+ /PE32\+ executable.*\(DLL\).*ARM64/
240
+ when /\Ax64-mingw(32|-ucrt)/
241
+ /PE32\+ executable.*\(DLL\).*x86-64/
242
+ else
243
+ 'skip'
244
+ end
245
+ end
246
+ end
247
+ end