[Question] How to use zeitwerk correctly in the gem entry file?
Closed this issue · 5 comments
I am writing a code referring to here in the document.
https://github.com/fxn/zeitwerk#autoloading
# lib/my_gem.rb (main file)
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "zeitwerk"
end
require "pathname"
Pathname("my_gem").mkpath
Pathname("my_gem/my_logger.rb").write("MyGem::MyLogger = 'OK'")
require "zeitwerk"
loader = Zeitwerk::Loader.for_gem
loader.setup
module MyGem
p MyLogger
end
Execute the above code to get the following error:
ruby my_gem.rb
WARNING: Zeitwerk defines the constant MyGem after the directory
./my_gem
To prevent that, please configure the loader to ignore it:
loader.ignore("#{__dir__}/my_gem")
Otherwise, there is a flag to silence this warning:
Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
WARNING: Zeitwerk defines the constant MyGem after the file
./my_gem.rb
To prevent that, please configure the loader to ignore it:
loader.ignore("#{__dir__}/my_gem.rb")
Otherwise, there is a flag to silence this warning:
Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
/usr/local/var/rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/loader.rb:471:in `block (3 levels) in raise_if_conflicting_directory': loader (Zeitwerk::Error)
#<Zeitwerk::GemLoader:0x000000010c7f4768
@autoloaded_dirs=[],
@autoloads={},
@collapse_dirs=#<Set: {}>,
@collapse_glob_patterns=#<Set: {}>,
@eager_load_exclusions=#<Set: {}>,
@eager_loaded=false,
@ignored_glob_patterns=#<Set: {}>,
@ignored_paths=#<Set: {}>,
@inflector=#<Zeitwerk::GemInflector:0x000000010c7f4010 @version_file="/private/tmp/lib/my_gem/version.rb">,
@initialized_at=2022-11-30 12:07:19.210589 +0900,
@lib="/private/tmp/lib",
@logger=nil,
@mutex=#<Thread::Mutex:0x000000010c7f4128>,
@mutex2=#<Thread::Mutex:0x000000010c7f40d8>,
@namespace_dirs={},
@on_load_callbacks={},
@on_setup_callbacks=[],
@on_unload_callbacks={},
@reloading_enabled=false,
@root_file="/private/tmp/lib/my_gem.rb",
@roots={},
@setup=false,
@shadowed_files=#<Set: {}>,
@tag="my_gem",
@to_unload={},
@warn_on_extra_files=true>
wants to manage directory /private/tmp/lib, which is already managed by
#<Zeitwerk::GemLoader:0x000000010c7ad638
@autoloaded_dirs=[],
@autoloads={"/private/tmp/lib/my_gem.rb"=>[Object, :MyGem]},
@collapse_dirs=#<Set: {}>,
@collapse_glob_patterns=#<Set: {}>,
@eager_load_exclusions=#<Set: {}>,
@eager_loaded=false,
@ignored_glob_patterns=#<Set: {}>,
@ignored_paths=#<Set: {}>,
@inflector=#<Zeitwerk::GemInflector:0x000000010c7ac080 @overrides={}, @version_file="./my_gem/version.rb">,
@initialized_at=2022-11-30 12:07:19.197537 +0900,
@lib=".",
@logger=nil,
@mutex=#<Thread::Mutex:0x000000010c7ac3c8>,
@mutex2=#<Thread::Mutex:0x000000010c7ac378>,
@namespace_dirs={"MyGem"=>["/private/tmp/lib/my_gem"]},
@on_load_callbacks={},
@on_setup_callbacks=[],
@on_unload_callbacks={},
@reloading_enabled=false,
@root_file="/private/tmp/lib/my_gem.rb",
@roots={"/private/tmp/lib"=>Object},
@setup=true,
@shadowed_files=#<Set: {}>,
@tag="my_gem",
@to_unload={},
@warn_on_extra_files=true>
from /usr/local/var/rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/loader.rb:465:in `each_key'
from /usr/local/var/rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/loader.rb:465:in `block (2 levels) in raise_if_conflicting_directory'
from /usr/local/var/rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/loader.rb:461:in `each'
from /usr/local/var/rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/loader.rb:461:in `block in raise_if_conflicting_directory'
from /usr/local/var/rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/loader.rb:458:in `synchronize'
from /usr/local/var/rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/loader.rb:458:in `raise_if_conflicting_directory'
from /usr/local/var/rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/loader/config.rb:123:in `push_dir'
from /usr/local/var/rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/gem_loader.rb:26:in `initialize'
from /usr/local/var/rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/gem_loader.rb:13:in `new'
from /usr/local/var/rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/gem_loader.rb:13:in `_new'
from /usr/local/var/rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/registry.rb:90:in `loader_for_gem'
from /usr/local/var/rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/loader.rb:286:in `for_gem'
from /private/tmp/lib/my_gem.rb:14:in `<top (required)>'
from /usr/local/var/rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:30:in `require'
from /usr/local/var/rbenv/versions/3.1.0/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:30:in `require'
from my_gem.rb:17:in `<main>'
Compilation exited abnormally with code 1 at Wed Nov 30 12:07:19
If I change the Loader part as follows, the error will not occur.
require "zeitwerk"
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
loader.ignore("#{__dir__}/my_gem.rb")
loader.setup
As a way to set up zeitwerk in gem entry file,
Is it wrong to use it?
Or is this okay?
The entrypoint should be a file my_gem.rb
at the same level that the my_gem
directory.
So, it would loook like this:
lib/my_gem.rb
lib/my_gem/my_logger.rb
@fxn Thank you for your reply.
Certainly my_gem.rb
is at the same level as my_gem
directory.
/tmp/lib $ exa -T
.
├── my_gem
│ └── my_logger.rb
└── my_gem.rb
/tmp/lib $ find
.
./my_gem.rb
./my_gem
./my_gem/my_logger.rb
/tmp/lib $ ruby my_gem.rb
(The error is displayed here)
Is it strange somewhere?
Ah! I see.
So, the problem is that gem entrypoints are expected to be required:
% ruby -I. -rmy_gem -e1
"OK"
Thanks!
I understanded that it should not be executed directly even with entrypoint.
If I called by require from parent directory, it worked correctly.
# /tmp/a.rb
$LOAD_PATH.unshift("lib")
require "my_gem"
$ ruby a.rb
"OK"
Exactly, if you require
the entrypoint from any client code, it will work.
This is an inception in which the file that defines the root namespace of the gem is being executed, even before the loader is even instantiated, and before the constant is defined. But the convenience of for_gem
, so easy to use, wins over the edge case and Zeitwerk is a bit more complicated internally to make it work.