-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
QOL make our gem requireable and autoload/eager load some constants #171
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
lib/protoboeuf/google/**/*.rb linguist-generated=true | ||
test/fixtures/autoloadergen/google/**/*.rb linguist-generated=true | ||
test/fixtures/autoloadergen/google/test_protos.correct.rb linguist-generated=false |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
# frozen_string_literal: true | ||
|
||
require "erb" | ||
require "syntax_tree" | ||
require "pathname" | ||
|
||
module ProtoBoeuf | ||
class AutoloaderGen | ||
# This class generates top-level autoloader modules for our well known types. Given autogenerated .rb files like: | ||
# - lib/protoboeuf/google/protobuf/foo.rb | ||
# - lib/protoboeuf/google/protobuf/bar.rb | ||
# | ||
# generate lib/protoboeuf/google/protobuf.rb that looks like: | ||
# | ||
# module ProtoBoeuf | ||
# module Google | ||
# module Protobuf | ||
# autoload :FooMessage1, "protoboeuf/google/protobuf/foo" | ||
# autoload :FooMessage2, "protoboeuf/google/protobuf/foo" | ||
# autoload :BarMessage1, "protoboeuf/google/protobuf/bar" | ||
# end | ||
# end | ||
# end | ||
|
||
BASE_LIB_DIR = File.expand_path("..", __dir__) | ||
|
||
attr_reader :module_filename, | ||
:child_ruby_filenames, | ||
:generated_autoloader_module_parts, | ||
:parent_module_parts, | ||
:require_paths_for_child_constants | ||
|
||
def initialize(module_filename, parent_module = "ProtoBoeuf::Google") | ||
@module_filename = module_filename | ||
@parent_module_parts = parent_module.split("::") | ||
|
||
# Given lib/protoboeuf/google.rb, glob lib/protoboeuf/google/**/*.rb | ||
@child_ruby_filenames = Dir[module_filename.pathmap("%X/**/*.rb")].sort | ||
autoloader_full_module_name = nil | ||
|
||
# Build a map of what we want to autoload :ConstantName => protoboeuf/require/path | ||
@require_paths_for_child_constants = child_ruby_filenames.each_with_object({}) do |filename, require_paths| | ||
child_constants = constants_for_child_ruby_filename(filename) | ||
# For the autoloader_full_module_name we can just pick the first child constant we come across and take the | ||
# first three parts. For example, ProtoBoeuf::Google::Api::FieldBehavior would be ProtoBoeuf::Google::Api. | ||
if @autoloader_module_name.nil? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this ever get set? |
||
autoloader_full_module_name = child_constants.first.split("::")[0..2].join("::") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we generalize this |
||
end | ||
|
||
# Make our absolute filename relative to the base lib directory for our autoload calls. | ||
require_path = Pathname.new(filename).relative_path_from(BASE_LIB_DIR).sub_ext("") | ||
child_constants.each do |child_constant| | ||
# child_constant is fully qualified, but we just want the last part | ||
require_paths[child_constant.split("::").last] = require_path | ||
end | ||
end | ||
|
||
@generated_autoloader_module_parts = autoloader_full_module_name.split("::") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we use this value for something else, or are we joining it just to split it again? |
||
end | ||
|
||
def to_ruby | ||
SyntaxTree.format(ERB.new(<<~RUBY, trim_mode: "-").result(binding)) | ||
# frozen_string_literal: true | ||
# rubocop:disable all | ||
# Autogenerated by `rake well_known_types`. Do not edit! | ||
<%- generated_autoloader_module_parts.each do |module_name| -%> | ||
module <%= module_name %> | ||
<%- end -%> | ||
<%- | ||
# Iterating over the sorted keys gives us lexographically sorted autoload statements | ||
-%> | ||
<%- require_paths_for_child_constants.keys.sort.each do |constant_name| -%> | ||
<%- require_path = require_paths_for_child_constants[constant_name] -%> | ||
autoload :<%= constant_name %>, "<%= require_path %>" | ||
<%- end -%> | ||
<%- generated_autoloader_module_parts.each do |module_name| -%> | ||
end | ||
<%- end -%> | ||
RUBY | ||
end | ||
|
||
private | ||
|
||
def constants_for_child_ruby_filename(filename) | ||
@constants_for_child_ruby_filename ||= {} | ||
|
||
return @constants_for_child_ruby_filename[filename] if @constants_for_child_ruby_filename.key?(filename) | ||
|
||
loaded = Module.new do | ||
module_eval File.binread(filename) | ||
end | ||
|
||
@constants_for_child_ruby_filename[filename] = loaded::ProtoBoeuf::Google.constants.flat_map do |const_name| | ||
mod = loaded | ||
parent_module_parts.each do |part| | ||
mod = mod.const_get(part) | ||
end | ||
mod = mod.const_get(const_name) | ||
|
||
next unless mod.is_a?(Module) | ||
|
||
# The top-level module will be our anonymous Module we created above | ||
parent_module_name = mod.name.split("::")[1..].join("::") | ||
|
||
mod.constants.map { |const_name| "#{parent_module_name}::#{const_name}" } | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
# frozen_string_literal: true | ||
|
||
# There isn't a clean 1:1 mapping between constants and *.rb files, so eager load instead of autoload. | ||
|
||
Dir[File.expand_path("google/**/*.rb", __dir__)].each { |file| require file } | ||
module ProtoBoeuf | ||
module Google | ||
autoload :Api, "protoboeuf/google/api" | ||
autoload :Protobuf, "protoboeuf/google/protobuf" | ||
end | ||
end |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# frozen_string_literal: true | ||
|
||
require "helper" | ||
require "protoboeuf/autoloadergen" | ||
|
||
class AutoloaderGenTest < ProtoBoeuf::Test | ||
FIXTURE_PATH = File.expand_path("fixtures/autoloadergen/google", __dir__) | ||
|
||
def test_generates_autoloader_module | ||
# test/fixtures/autoloadergen/google/test_protos/*.proto needs an autoloader at | ||
# test/fixtures/autoloadergen/google/test_protos.rb | ||
autoloader_rb_path = File.expand_path("test_protos.rb", FIXTURE_PATH) | ||
|
||
autoloader_ruby = ProtoBoeuf::AutoloaderGen.new(autoloader_rb_path).to_ruby | ||
|
||
# If you ever want to regenerate the expected_autoloader_ruby, run: | ||
# File.binwrite(File.expand_path("test_protos.correct.rb", FIXTURE_PATH), autoloader_ruby) | ||
expected_autoloader_ruby = File.binread(File.expand_path("test_protos.correct.rb", FIXTURE_PATH)) | ||
|
||
assert_equal(expected_autoloader_ruby, autoloader_ruby) | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# frozen_string_literal: true | ||
# rubocop:disable all | ||
|
||
# Autogenerated by `rake well_known_types`. Do not edit! | ||
module ProtoBoeuf | ||
module Google | ||
module TestProtos | ||
autoload :Bicycle, | ||
"../test/fixtures/autoloadergen/google/test_protos/transportation" | ||
autoload :Boat, | ||
"../test/fixtures/autoloadergen/google/test_protos/transportation" | ||
autoload :Color, "../test/fixtures/autoloadergen/google/test_protos/color" | ||
autoload :Vehicle, | ||
"../test/fixtures/autoloadergen/google/test_protos/transportation" | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
syntax = "proto3"; | ||
|
||
package google.test_protos; | ||
|
||
enum Color { | ||
UNKNOWN = 0; | ||
RED = 1; | ||
BLUE = 2; | ||
GREEN = 3; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
syntax = "proto3"; | ||
|
||
package google.test_protos; | ||
|
||
import "color.proto"; | ||
|
||
message Boat { | ||
string make = 1; | ||
int32 year = 2; | ||
Color color = 3; | ||
} | ||
|
||
message Bicycle { | ||
string make = 1; | ||
int32 year = 2; | ||
Color color = 3; | ||
} | ||
|
||
message Vehicle { | ||
string make = 1; | ||
string model = 2; | ||
int32 year = 3; | ||
Color color = 4; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This
String#pathmap
comes from rake.Can we simplify that to get rid of that dependency?
Maybe just
Dir["#{module_filename.delete_suffix(".rb")}/**/*.rb"]
would be enough?