Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions doc/guides/amalgamation.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,22 @@ Typical sizes depend on included gems:
- Local includes (`.cstub` files) are automatically inlined
- Generated files (`mrblib.c`, `gem_init.c`) are included

### Gem Defines

Gems that add preprocessor defines affecting core structures are
automatically detected and included at the top of `mruby.h`.
Supported patterns: `MRB_USE_*`, `MRB_UTF8_*`, `HAVE_MRUBY_*`.
### Build Configuration Defines

The defines the build compiles with are written at the top of `mruby.h`, so
that including it is enough to get the same `mrb_value` layout, integer width
and feature set as the build the amalgamation was generated from. Both the
defines the build configuration names (`conf.cc.defines`) and the ones gems
contribute (`spec.build.defines`) are emitted. Each is wrapped in `#ifndef`,
so passing the same define on the command line is not a redefinition.

Two kinds are not written, and are left to whoever compiles the amalgamation:

- `MRB_DEBUG`, which only decides whether `mrb_assert` checks. `-DNDEBUG`
above is the same kind of choice.
- `MRB_USE_CXX_EXCEPTION` and `MRB_USE_CXX_ABI`, which require a C++
compiler. `mruby.c` is C, so a header that demanded them could not be
compiled as generated.

### Build Order

Expand Down
46 changes: 32 additions & 14 deletions lib/mruby/amalgam.rb
Original file line number Diff line number Diff line change
Expand Up @@ -250,25 +250,43 @@ def write_header_preamble(f)

PREAMBLE

# Add build-level defines from gems (e.g., MRB_USE_TASK_SCHEDULER)
gem_defines = collect_gem_defines
unless gem_defines.empty?
f.puts "/* Gem-required defines */"
gem_defines.each do |d|
f.puts "#define #{d}"
build_defines = collect_build_defines
unless build_defines.empty?
f.puts "/* Build configuration defines */"
build_defines.each do |name, value|
f.puts "#ifndef #{name}"
f.puts(value ? "#define #{name} #{value}" : "#define #{name}")
f.puts "#endif"
end
f.puts
end
end

# Collect defines added by gems that affect core headers
def collect_gem_defines
defines = []
@build.defines.each do |d|
# Include defines that affect mrb_state or core functionality
defines << d if d =~ /^MRB_USE_|^MRB_UTF8_|^HAVE_MRUBY_/
end
defines.uniq.sort
# The defines this build compiles with, which the consumer must compile with
# too: they decide the layout of `mrb_value` and `mrb_state` and which
# functions exist, so a header read without them describes a different
# mruby than the one in mruby.c. The build config writes most of them
# (`MRB_NO_STDIO`, `MRB_INT64`, the boxing choice), a gem writes the rest
# through `spec.build.defines` (`MRB_USE_BIGINT`, `HAVE_MRUBY_IO_GEM`);
# neither is distinguishable from the header's side, so both are emitted.
#
# `internal_defines` is left out on purpose: it holds what the build adds
# from its own switches, and `MRB_USE_CXX_ABI` there would make the header
# unusable from C. `cxx.defines` is left out for the same reason, the
# amalgam being C. The consumer that wants a C++ build passes those itself.
def collect_build_defines
defines = {}
[@build.defines, @build.cc.defines].flatten.each do |d|
name, value = d.to_s.split("=", 2)
next unless name =~ /\A[A-Za-z_][A-Za-z0-9_]*\z/
next if name.start_with?("__STDC_")
# First writer wins, `@build.defines` over `@build.cc.defines`, which is
# the way the two land on the command line: `all_flags` puts
# `build.defines` last and the last `-D` of a name is the one in effect.
# `||=` would be wrong here, a valueless define holding nil.
defines[name] = value unless defines.key?(name)
end
defines.sort
Comment thread
coderabbitai[bot] marked this conversation as resolved.
end

def write_header_postamble(f)
Expand Down
15 changes: 8 additions & 7 deletions lib/mruby/build.rb
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def debug_enabled?

def enable_debug
compilers.each do |c|
c.defines += %w(MRB_DEBUG)
c.internal_defines += %w(MRB_DEBUG)
c.setup_debug(self)
end
@mrbc.compile_options += ' -g'
Expand Down Expand Up @@ -225,7 +225,7 @@ def enable_cxx_exception
end
@cxx_exception_enabled = true
compilers.each { |c|
c.defines += %w(MRB_USE_CXX_EXCEPTION)
c.internal_defines += %w(MRB_USE_CXX_EXCEPTION)
c.flags << c.cxx_exception_flag
}
linker.command = cxx.command if toolchains.find { |v| v == 'gcc' }
Expand All @@ -245,7 +245,7 @@ def enable_cxx_abi
raise "cxx_exception already enabled"
end
compilers.each { |c|
c.defines += %w(MRB_USE_CXX_EXCEPTION MRB_USE_CXX_ABI)
c.internal_defines += %w(MRB_USE_CXX_EXCEPTION MRB_USE_CXX_ABI)
c.flags << c.cxx_compile_flag
c.flags = c.flags.flatten - c.cxx_invalid_flags.flatten
}
Expand Down Expand Up @@ -388,8 +388,9 @@ def defines_final!
end

# True when this build compiles with -D<name>, whether the build config
# asked for it or a gem contributed it. A gem reads this to configure
# itself against a capability another gem provides.
# asked for it, a gem contributed it, or the build added it from one of
# its own switches. A gem reads this to configure itself against a
# capability another gem provides.
#
# Until `defines_final!` the answer would depend on how far down the gem
# list the caller sits, since a gem contributes its defines when its own
Expand All @@ -406,8 +407,8 @@ def has_define?(name)
# A define may carry a value, as `FOO=1` does, so compare the name and
# not the value. The `-D` is the compiler's, added when the flags are
# assembled, and is no part of the name.
[defines, *compilers.map(&:defines)].flatten
.any? {|d| d.to_s.split('=', 2).first == name}
return true if defines.flatten.any? {|d| d.to_s.split('=', 2).first == name}
compilers.any? {|c| c.has_define?(name)}
end

def define_rules
Expand Down
23 changes: 22 additions & 1 deletion lib/mruby/build/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ def _run(options, params={})

class Command::Compiler < Command
attr_accessor :label, :flags, :include_paths, :defines, :source_exts
# Defines are held in two lists, split by who asked for them. `defines` is
# what the build config and the gems write. `internal_defines` is what the
# build adds on its own behalf, from its own switches (`enable_debug`,
# `enable_cxx_abi`) or from a toolchain. Both reach the compiler as `-D`;
# keeping them apart lets a toolchain set its own without overwriting what
# the config already wrote, and lets a caller ask for one list alone.
attr_accessor :internal_defines
attr_accessor :compile_options, :option_define, :option_include_path, :out_ext
attr_accessor :cxx_compile_flag, :cxx_exception_flag, :cxx_invalid_flags
attr_writer :preprocess_options
Expand All @@ -48,6 +55,7 @@ def initialize(build, source_exts=[], label: "CC")
@source_exts = source_exts
@include_paths = ["#{MRUBY_ROOT}/include"]
@defines = []
@internal_defines = []
@option_include_path = %q[-I"%s"]
@option_define = %q[-D"%s"]
@compile_options = %q[%{flags} -o "%{outfile}" -c "%{infile}"]
Expand All @@ -61,6 +69,18 @@ def preprocess_options
@preprocess_options ||= @compile_options.sub(/(?:\A|\s)\K-c(?=\s)/, "-E -P")
end

# True when this compiler compiles with -D<name>, whichever of the two
# lists it sits in. A gem asks this while its own mrbgem.rake body runs,
# where `Build#has_define?` refuses to answer because the gems that come
# after it have not contributed their defines yet.
def has_define?(name)
name = name.to_s
# A define may carry a value, as `FOO=1` does, so compare the name and
# not the value.
[defines, internal_defines].flatten
.any? {|d| d.to_s.split('=', 2).first == name}
end

def search_header_path(name)
header_search_paths.find do |v|
File.exist? build.filename("#{v}/#{name}").sub(/^"(.*)"$/, '\1')
Expand All @@ -73,7 +93,8 @@ def search_header(name)
end

def all_flags(_defines=[], _include_paths=[], _flags=[])
define_flags = [defines, _defines, build.defines].flatten.map{ |d| option_define % d }
define_flags = [defines, internal_defines, _defines, build.defines].flatten
.map{ |d| option_define % d }
include_path_flags = [include_paths, _include_paths].flatten.map do |f|
option_include_path % filename(f)
end
Expand Down
2 changes: 1 addition & 1 deletion mrbgems/mruby-compiler/mrbgem.rake
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ MRuby::Gem::Specification.new('mruby-compiler') do |spec|
elsif !cc.defines.include?('MRB_NO_GEMS')
cc.defines << 'MRC_TARGET_MRUBY'
end
cc.defines << 'MRC_DEBUG' if cc.defines.any? { |d| d.match?(/\AMRB_DEBUG(=|\z)/) }
cc.defines << 'MRC_DEBUG' if cc.has_define?('MRB_DEBUG')
cc.defines << 'PRISM_BUILD_MINIMAL' unless cc.defines.include?('MRC_DEBUG')
# PRISM_BUILD_MINIMAL stubs out pm_prettyprint(), so `mruby -v` can only dump
# the AST where it is compiled in
Expand Down
2 changes: 1 addition & 1 deletion tasks/toolchains/visualcpp.rake
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ MRuby::Toolchain.new(:visualcpp) do |conf, _params|
compiler.command = ENV['CXX'] || 'cl.exe'
compiler.flags = [*(ENV['CXXFLAGS'] || ENV['CFLAGS'] || compiler_flags + %w(/EHs))]
end
compiler.defines = %w(MRB_STACK_EXTEND_DOUBLING)
compiler.internal_defines |= %w(MRB_STACK_EXTEND_DOUBLING)
compiler.option_include_path = %q[/I"%s"]
compiler.option_define = '/D%s'
compiler.compile_options = %Q[/Zi /c /Fo"%{outfile}" %{flags} "%{infile}"]
Expand Down
Loading