Files
ruby/ext/zap/cxx_compiler.rb
zeekay 41415a22ef Finish capnp→ZAP rebrand; fix Ruby 4 build + bump dev deps
Rebrand all ZAP-facing identifiers off Cap'n Proto:
- module Capnp→Zap, ext binding namespace ruby_capn_proto→ruby_zap,
  header guard RUBY_CAPN_PROTO_H→RUBY_ZAP_H, entry point Init_zap,
  gem capn_proto→zap, schemas .capnp→.zap, media/logo, gemspec, README,
  spec, examples. Zero capnp/Cap'n Proto brand prose remains.

Binding-namespace nuance (kept on purpose): the native ext links the
upstream serialization runtime (libcapnp/kj). Its capnp:: C++ symbols and
<capnp/...> headers are the linked library's real API — there is no
zap:: namespace or libzap to retarget to yet — so those references, the
-lcapnp/-lcapnpc/-lkj link flags, and the pycapnp dev-only fixture
generators stay, each documented as the external dependency surface
(retargets to <zap/...>/-lzap when the ZAP C++ runtime ships).

Build/modernization:
- extconf.rb: discover the runtime via dir_config + pkg_config so the
  ext builds against non-default prefixes (e.g. Homebrew), not just
  /usr/local. Native ext now compiles + links cleanly (zap.bundle).
- ext: parenthesize `if (FIXNUM_P(x))` in the two dynamic builders —
  required since Ruby's FIXNUM_P macro no longer wraps its expansion
  (Ruby 4 build break).
- Rakefile: put lib on $LOAD_PATH and load the gemspec via
  Gem::Specification.load; pin rake-compiler ~> 0.9; make the rspec
  rake task degrade with a clear message instead of aborting the whole
  Rakefile when the harness can't load.
- gemspec: bump dev deps within-major (rspec ~> 2.14, rake ~> 13.0,
  rake-compiler ~> 0.9); add required_ruby_version.
- .gitignore: ignore native-ext build artifacts (*.o, Makefile,
  mkmf.log, *.bundle.dSYM, *.gem).

Verified: all spec/zap_spec.rb assertions pass against the compiled
binding (write/read + to_bytes round-trips, unknown-field and
out-of-bounds raise Zap::Exception, dynamic struct assign); the
addressbook example decodes end-to-end.
2026-06-19 23:57:58 -07:00

127 lines
3.3 KiB
Ruby

require 'shellwords'
require 'tmpdir'
# Discovers support for C++11 features,
# and any required CXXFLAGS for such features.
class CXXCompiler
CXX_COMPILER_FEATURES = <<-CODE
template <typename T>
struct check
{
static_assert(sizeof(int) <= sizeof(T), "not big enough");
};
typedef check<check<bool>> right_angle_brackets;
int a;
decltype(a) b;
typedef check<int> check_type;
check_type c;
check_type&& cr = static_cast<check_type&&>(c);
#ifdef __has_include
#if __has_include(<type_traits>)
#include <type_traits>
#endif
#endif
CODE
CXX_LIBRARY_FEATURES = <<-CODE
#include <initializer_list>
#include <unordered_map>
#include <atomic>
#include <thread>
CODE
# env: The environment variables (e.g. CXX, CXXFLAGS).
def initialize(env)
@env = env.dup
@exist = system("command -v #{@env['CXX'].shellescape} >/dev/null 2>&1")
end
# True if the compiler is accessible on the $PATH.
def exist?
@exist
end
# True if support for C++11 was detected, false otherwise.
def has_cxx11_compiler_support?
!!std_flag
end
# True if support for C++11 libraries was detected, false otherwise.
def has_cxx11_library_support?
!!stdlib_flag
end
# The required std flag (e.g. '-std=c++11'),
# empty string if unecessary given the current environment and/or compiler,
# or nil in the case where the compiler has no support.
def std_flag
test_compiler_flags(@env)
@std_flag
end
# The required stdlib flag (e.g. '-stdlib=libc++'),
# empty string if unecessary given the current environment and/or compiler,
# or nil in the case where the compiler has no support.
def stdlib_flag
test_compiler_flags(@env)
@stdlib_flag
end
private
# Detect any required flags.
def test_compiler_flags(env)
return if @tested || !exist?
@tested = true
cxxflags = (env['CXXFLAGS'] || '')
env = env.dup
flags = ['', '-std=gnu++11', '-std=gnu++0x', '-std=c++11', '-std=c++0x']
@std_flag = flags.detect do |flag|
env['CXXFLAGS'] = [cxxflags, flag].join(' ')
compile(CXX_COMPILER_FEATURES, env)
end
return unless @std_flag
env = env.dup
flags = ['', '-stdlib=libc++']
@stdlib_flag = flags.detect do |flag|
env['CXXFLAGS'] = [cxxflags, std_flag, flag].join(' ')
compile(CXX_LIBRARY_FEATURES, env)
end
end
# Compile a snippet of C++, returning true or false to indicate
# success.
def compile(code, env)
Dir.mktmpdir do |dir|
Dir.chdir(dir) do
File.open("test.cc", "w") {|f| f.write(code)}
system("#{env['CXX'].shellescape} #{env['CXXFLAGS']} -c test.cc >/dev/null 2>&1")
end
end
end
end
if $0 == __FILE__
compiler = CXXCompiler.new(ENV.to_hash)
cxx = ENV['CXX']
cxxflags = ENV['CXXFLAGS']
extra_cxxflags = [compiler.std_flag, compiler.stdlib_flag].join(' ')
compiler_support = compiler.has_cxx11_compiler_support? ? 'YES' : 'NO'
lib_support = compiler.has_cxx11_library_support? ? 'YES' : 'NO'
puts " given CXX = #{cxx}"
puts " given CXXFLAGS = #{cxxflags}"
puts "additional CXXFLAGS = #{extra_cxxflags}"
puts ""
puts "Has C++11 compiler support? #{compiler_support}"
puts "Has C++11 library support? #{lib_support}"
end