Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
@@ -0,0 +1,232 @@
require 'tempfile'
require 'open3'
module FFI
# ConstGenerator turns C constants into ruby values.
#
# @example a simple example for stdio
# require 'ffi/tools/const_generator'
# cg = FFI::ConstGenerator.new('stdio') do |gen|
# gen.const(:SEEK_SET)
# gen.const('SEEK_CUR')
# gen.const('seek_end') # this constant does not exist
# end # #calculate called automatically at the end of the block
#
# cg['SEEK_SET'] # => 0
# cg['SEEK_CUR'] # => 1
# cg['seek_end'] # => nil
# cg.to_ruby # => "SEEK_SET = 0\nSEEK_CUR = 1\n# seek_end not available"
class ConstGenerator
@options = {}
attr_reader :constants
# Creates a new constant generator that uses +prefix+ as a name, and an
# options hash.
#
# The only option is +:required+, which if set to +true+ raises an error if a
# constant you have requested was not found.
#
# @param [#to_s] prefix
# @param [Hash] options
# @return
# @option options [Boolean] :required
# @overload initialize(prefix, options)
# @overload initialize(prefix, options) { |gen| ... }
# @yieldparam [ConstGenerator] gen new generator is passed to the block
# When passed a block, {#calculate} is automatically called at the end of
# the block, otherwise you must call it yourself.
def initialize(prefix = nil, options = {})
@includes = ['stdio.h', 'stddef.h']
@constants = {}
@prefix = prefix
@required = options[:required]
@options = options
if block_given? then
yield self
calculate self.class.options.merge(options)
end
end
# Set class options
# These options are merged with {#initialize} options when it is called with a block.
# @param [Hash] options
# @return [Hash] class options
def self.options=(options)
@options = options
end
# Get class options.
# @return [Hash] class options
def self.options
@options
end
# @param [String] name
# @return constant value (converted if a +converter+ was defined).
# Access a constant by name.
def [](name)
@constants[name].converted_value
end
# Request the value for C constant +name+.
#
# @param [#to_s] name C constant name
# @param [String] format a printf format string to print the value out
# @param [String] cast a C cast for the value
# @param ruby_name alternate ruby name for {#to_ruby}
#
# @overload const(name, format=nil, cast='', ruby_name=nil, converter=nil)
# +converter+ is a Method or a Proc.
# @param [#call] converter convert the value from a string to the appropriate
# type for {#to_ruby}.
# @overload const(name, format=nil, cast='', ruby_name=nil) { |value| ... }
# Use a converter block. This block convert the value from a string to the
# appropriate type for {#to_ruby}.
# @yieldparam value constant value
def const(name, format = nil, cast = '', ruby_name = nil, converter = nil,
&converter_proc)
format ||= '%d'
cast ||= ''
if converter_proc and converter then
raise ArgumentError, "Supply only converter or converter block"
end
converter = converter_proc if converter.nil?
const = Constant.new name, format, cast, ruby_name, converter
@constants[name.to_s] = const
return const
end
# Calculate constants values.
# @param [Hash] options
# @option options [String] :cppflags flags for C compiler
# @return [nil]
# @raise if a constant is missing and +:required+ was set to +true+ (see {#initialize})
def calculate(options = {})
binary_path = nil
Tempfile.open("#{@prefix}.const_generator") do |f|
binary_path = f.path + ".bin"
@includes.each do |inc|
f.puts "#include <#{inc}>"
end
f.puts "\nint main(int argc, char **argv)\n{"
@constants.each_value do |const|
f.puts <<-EOF
#ifdef #{const.name}
printf("#{const.name} #{const.format}\\n", #{const.cast}#{const.name});
#endif
EOF
end
f.puts "\n\treturn 0;\n}"
f.flush
cc = ENV['CC'] || 'gcc'
output = `#{cc} #{options[:cppflags]} -D_DARWIN_USE_64_BIT_INODE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -x c -Wall -Werror #{f.path} -o #{binary_path} 2>&1`
unless $?.success? then
output = output.split("\n").map { |l| "\t#{l}" }.join "\n"
raise "Compilation error generating constants #{@prefix}:\n#{output}"
end
end
output = `#{binary_path}`
File.unlink(binary_path + (FFI::Platform.windows? ? ".exe" : ""))
output.each_line do |line|
line =~ /^(\S+)\s(.*)$/
const = @constants[$1]
const.value = $2
end
missing_constants = @constants.select do |name, constant|
constant.value.nil?
end.map { |name,| name }
if @required and not missing_constants.empty? then
raise "Missing required constants for #{@prefix}: #{missing_constants.join ', '}"
end
end
# Dump constants to +io+.
# @param [#puts] io
# @return [nil]
def dump_constants(io)
@constants.each do |name, constant|
name = [@prefix, name].join '.' if @prefix
io.puts "#{name} = #{constant.converted_value}"
end
end
# Outputs values for discovered constants. If the constant's value was
# not discovered it is not omitted.
# @return [String]
def to_ruby
@constants.sort_by { |name,| name }.map do |name, constant|
if constant.value.nil? then
"# #{name} not available"
else
constant.to_ruby
end
end.join "\n"
end
# Add additional C include file(s) to calculate constants from.
# @note +stdio.h+ and +stddef.h+ automatically included
# @param [List<String>, Array<String>] i include file(s)
# @return [Array<String>] array of include files
def include(*i)
@includes |= i.flatten
end
end
# This class hold constants for {ConstGenerator}
class ConstGenerator::Constant
attr_reader :name, :format, :cast
attr_accessor :value
# @param [#to_s] name
# @param [String] format a printf format string to print the value out
# @param [String] cast a C cast for the value
# @param ruby_name alternate ruby name for {#to_ruby}
# @param [#call] converter convert the value from a string to the appropriate
# type for {#to_ruby}.
def initialize(name, format, cast, ruby_name = nil, converter=nil)
@name = name
@format = format
@cast = cast
@ruby_name = ruby_name
@converter = converter
@value = nil
end
# Return constant value (converted if a +converter+ was defined).
# @return constant value.
def converted_value
if @converter
@converter.call(@value)
else
@value
end
end
# get constant ruby name
# @return [String]
def ruby_name
@ruby_name || @name
end
# Get an evaluable string from constant.
# @return [String]
def to_ruby
"#{ruby_name} = #{converted_value}"
end
end
end
@@ -0,0 +1,105 @@
require 'ffi/tools/struct_generator'
require 'ffi/tools/const_generator'
module FFI
##
# Generate files with C structs for FFI::Struct and C constants.
#
# == A simple example
#
# In file +zlib.rb.ffi+:
# module Zlib
# @@@
# constants do |c|
# c.include "zlib.h"
# c.const :ZLIB_VERNUM
# end
# @@@
#
# class ZStream < FFI::Struct
#
# struct do |s|
# s.name "struct z_stream_s"
# s.include "zlib.h"
#
# s.field :next_in, :pointer
# s.field :avail_in, :uint
# s.field :total_in, :ulong
# end
# @@@
# end
# end
#
# Translate the file:
# require "ffi/tools/generator"
# FFI::Generator.new "zlib.rb.ffi", "zlib.rb"
#
# Generates the file +zlib.rb+ with constant values and offsets:
# module Zlib
# ZLIB_VERNUM = 4784
#
# class ZStream < FFI::Struct
# layout :next_in, :pointer, 0,
# :avail_in, :uint, 8,
# :total_in, :ulong, 16
# end
#
# @see FFI::Generator::Task for easy integration in a Rakefile
class Generator
def initialize(ffi_name, rb_name, options = {})
@ffi_name = ffi_name
@rb_name = rb_name
@options = options
@name = File.basename rb_name, '.rb'
file = File.read @ffi_name
new_file = file.gsub(/^( *)@@@(.*?)@@@/m) do
@constants = []
@structs = []
indent = $1
original_lines = $2.count "\n"
instance_eval $2, @ffi_name, $`.count("\n")
new_lines = []
@constants.each { |c| new_lines << c.to_ruby }
@structs.each { |s| new_lines << s.generate_layout }
new_lines = new_lines.join("\n").split "\n" # expand multiline blocks
new_lines = new_lines.map { |line| indent + line }
padding = original_lines - new_lines.length
new_lines += [nil] * padding if padding >= 0
new_lines.join "\n"
end
open @rb_name, 'w' do |f|
f.puts "# This file is generated from `#{@ffi_name}'. Do not edit."
f.puts
f.puts new_file
end
end
def constants(options = {}, &block)
@constants << FFI::ConstGenerator.new(@name, @options.merge(options), &block)
end
def struct(options = {}, &block)
@structs << FFI::StructGenerator.new(@name, @options.merge(options), &block)
end
##
# Utility converter for constants
def to_s
proc { |obj| obj.to_s.inspect }
end
end
end
@@ -0,0 +1,32 @@
require 'ffi/tools/generator'
require 'rake'
require 'rake/tasklib'
##
# Add Rake tasks that generate files with C structs for FFI::Struct and C constants.
#
# @example a simple example for your Rakefile
# require "ffi/tools/generator_task"
# # Add a task to generate my_object.rb out of my_object.rb.ffi
# FFI::Generator::Task.new ["my_object.rb"], cflags: "-I/usr/local/mylibrary"
#
# The generated files are also added to the 'clear' task.
#
# @see FFI::Generator for a description of the file content
class FFI::Generator::Task < Rake::TaskLib
def initialize(rb_names, options={})
task :clean do rm_f rb_names end
rb_names.each do |rb_name|
ffi_name = "#{rb_name}.ffi"
file rb_name => ffi_name do |t|
puts "Generating #{rb_name}..." if Rake.application.options.trace
FFI::Generator.new ffi_name, rb_name, options
end
end
end
end
@@ -0,0 +1,195 @@
require 'tempfile'
module FFI
##
# Generates an FFI Struct layout.
#
# Given the @@@ portion in:
#
# class Zlib::ZStream < FFI::Struct
# @@@
# name "struct z_stream_s"
# include "zlib.h"
#
# field :next_in, :pointer
# field :avail_in, :uint
# field :total_in, :ulong
#
# # ...
# @@@
# end
#
# StructGenerator will create the layout:
#
# layout :next_in, :pointer, 0,
# :avail_in, :uint, 4,
# :total_in, :ulong, 8,
# # ...
#
# StructGenerator does its best to pad the layout it produces to preserve
# line numbers. Place the struct definition as close to the top of the file
# for best results.
class StructGenerator
@options = {}
attr_accessor :size
attr_reader :fields
def initialize(name, options = {})
@name = name
@struct_name = nil
@includes = []
@fields = []
@found = false
@size = nil
if block_given? then
yield self
calculate self.class.options.merge(options)
end
end
def self.options=(options)
@options = options
end
def self.options
@options
end
def calculate(options = {})
binary = File.join Dir.tmpdir, "rb_struct_gen_bin_#{Process.pid}"
raise "struct name not set" if @struct_name.nil?
Tempfile.open("#{@name}.struct_generator") do |f|
f.puts "#include <stdio.h>"
@includes.each do |inc|
f.puts "#include <#{inc}>"
end
f.puts "#include <stddef.h>\n\n"
f.puts "int main(int argc, char **argv)\n{"
f.puts " #{@struct_name} s;"
f.puts %[ printf("sizeof(#{@struct_name}) %u\\n", (unsigned int) sizeof(#{@struct_name}));]
@fields.each do |field|
f.puts <<-EOF
printf("#{field.name} %u %u\\n", (unsigned int) offsetof(#{@struct_name}, #{field.name}),
(unsigned int) sizeof(s.#{field.name}));
EOF
end
f.puts "\n return 0;\n}"
f.flush
cc = ENV['CC'] || 'gcc'
output = `#{cc} #{options[:cppflags]} #{options[:cflags]} -D_DARWIN_USE_64_BIT_INODE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -x c -Wall -Werror #{f.path} -o #{binary} 2>&1`
unless $?.success? then
@found = false
output = output.split("\n").map { |l| "\t#{l}" }.join "\n"
raise "Compilation error generating struct #{@name} (#{@struct_name}):\n#{output}"
end
end
output = `#{binary}`.split "\n"
File.unlink(binary + (FFI::Platform.windows? ? ".exe" : ""))
sizeof = output.shift
unless @size
m = /\s*sizeof\([^)]+\) (\d+)/.match sizeof
@size = m[1]
end
line_no = 0
output.each do |line|
md = line.match(/.+ (\d+) (\d+)/)
@fields[line_no].offset = md[1].to_i
@fields[line_no].size = md[2].to_i
line_no += 1
end
@found = true
end
def field(name, type=nil)
field = Field.new(name, type)
@fields << field
return field
end
def found?
@found
end
def dump_config(io)
io.puts "rbx.platform.#{@name}.sizeof = #{@size}"
@fields.each { |field| io.puts field.to_config(@name) }
end
def generate_layout
buf = ""
@fields.each_with_index do |field, i|
if buf.empty?
buf << "layout :#{field.name}, :#{field.type}, #{field.offset}"
else
buf << " :#{field.name}, :#{field.type}, #{field.offset}"
end
if i < @fields.length - 1
buf << ",\n"
end
end
buf
end
def get_field(name)
@fields.find { |f| name == f.name }
end
def include(i)
@includes << i
end
def name(n)
@struct_name = n
end
end
##
# A field in a Struct.
class StructGenerator::Field
attr_reader :name
attr_reader :type
attr_reader :offset
attr_accessor :size
def initialize(name, type)
@name = name
@type = type
@offset = nil
@size = nil
end
def offset=(o)
@offset = o
end
def to_config(name)
buf = []
buf << "rbx.platform.#{name}.#{@name}.offset = #{@offset}"
buf << "rbx.platform.#{name}.#{@name}.size = #{@size}"
buf << "rbx.platform.#{name}.#{@name}.type = #{@type}" if @type
buf
end
end
end