Rails 5.2.8.1升级至6.1时CircleCI资产预编译Zeitwerk冲突问题
我们有一个Rails 5.2.8.1应用,正在升级到Rails 6.1。本地运行仅出现少量问题,但在CircleCI执行资产预编译时触发错误:Zeitwerk的rails.once加载器试图管理actiontext-6.1.7/app/helpers目录,而该目录已被默认的rails.main加载器接管。我们并未手动使用过这个load once加载器。
application.rb中的加载器配置
config.autoload_paths += %W[ #{config.root}/lib #{config.root}/lib/simple_form #{config.root}/app/jobs #{config.root}/app/jobs/concerns #{config.root}/app/controllers/concerns #{config.root}/app/mailers/concerns #{config.root}/app/models/concerns #{config.root}/app/models/reports #{config.root}/app/services/pdf_data #{config.root}/app/presenters #{config.root}/app/assets/templates #{config.root}/app/event_handlers #{config.root}/app/queries #{config.root}/app/concepts #{config.root}/vendor/ #{config.root}/app/pdfs ] config.eager_load_paths += %W[ #{config.root}/lib ]
错误输出
#!/bin/bash -eo pipefail WEBPACKER_PRECOMPILE=false RAILS_NO_DB=true bundle exec rake assets:precompile --trace ** Invoke assets:precompile (first_time) ** Invoke assets:environment (first_time) ** Execute assets:environment ** Invoke environment (first_time) ** Execute environment rake aborted! Zeitwerk::Error: loader #<Zeitwerk::Loader:0x000055eb81710b98 @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=ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector, @initialized_at=2022-12-07 14:27:29.123263169 +0000, @logger=nil, @mutex=#<Thread::Mutex:0x000055eb81710738>, @mutex2=#<Thread::Mutex:0x000055eb81710710>, @namespace_dirs={}, @on_load_callbacks={}, @on_setup_callbacks=[], @on_unload_callbacks={}, @reloading_enabled=false, @roots={}, @setup=false, @shadowed_files=#<Set: {}>, @tag="rails.once", @to_unload={}> wants to manage directory /home/circleci/flightlogger-rails/vendor/bundle/ruby/2.7.0/gems/actiontext-6.1.7/app/helpers, which is already managed by #<Zeitwerk::Loader:0x000055eb81712f88 @autoloaded_dirs=[], @autoloads={}, @collapse_dirs=#<Set: {}>, @collapse_glob_patterns=#<Set: {}>, @eager_load_exclusions= #<Set: {"/home/circleci/flightlogger-rails/lib/simple_form", "/home/circleci/flightlogger-rails/app/models/reports", "/home/circleci/flightlogger-rails/app/assets/templates", "/home/circleci/flightlogger-rails/vendor"}>, @eager_loaded=false, @ignored_glob_patterns=#<Set: {}>, @ignored_paths=#<Set: {}>, @inflector=ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector, @initialized_at=2022-12-07 14:27:29.122727016 +0000, @logger=nil, @mutex=#<Thread::Mutex:0x000055eb81712ab0>, @mutex2=#<Thread::Mutex:0x000055eb81712a88>, @namespace_dirs={}, @on_load_callbacks={}, @on_setup_callbacks=[], @on_unload_callbacks={}, @reloading_enabled=false, @roots= {"/home/circleci/flightlogger-rails/lib"=>Object, "/home/circleci/flightlogger-rails/lib/simple_form"=>Object, "/home/circleci/flightlogger-rails/app/jobs"=>Object, "/home/circleci/flightlogger-rails/app/jobs/concerns"=>Object, "/home/circleci/flightlogger-rails/app/controllers/concerns"=>Object, "/home/circleci/flightlogger-rails/app/mailers/concerns"=>Object, "/home/circleci/flightlogger-rails/app/models/concerns"=>Object, "/home/circleci/flightlogger-rails/app/models/reports"=>Object, "/home/circleci/flightlogger-rails/app/presenters"=>Object, "/home/circleci/flightlogger-rails/app/assets/templates"=>Object, "/home/circleci/flightlogger-rails/app/event_handlers"=>Object, "/home/circleci/flightlogger-rails/app/queries"=>Object, "/home/circleci/flightlogger-rails/app/concepts"=>Object, "/home/circleci/flightlogger-rails/vendor"=>Object, "/home/circleci/flightlogger-rails/app/pdfs"=>Object, "/home/circleci/flightlogger-rails/app/associations"=>Object, "/home/circleci/flightlogger-rails/app/channels"=>Object, "/home/circleci/flightlogger-rails/app/controllers"=>Object, "/home/circleci/flightlogger-rails/app/decorators"=>Object, "/home/circleci/flightlogger-rails/app/forms"=>Object, "/home/circleci/flightlogger-rails/app/graphql"=>Object, "/home/circleci/flightlogger-rails/app/graphql/concerns"=>Object, "/home/circleci/flightlogger-rails/app/helpers"=>Object, "/home/circleci/flightlogger-rails/app/interactors"=>Object, "/home/circleci/flightlogger-rails/app/mailers"=>Object, "/home/circleci/flightlogger-rails/app/models"=>Object, "/home/circleci/flightlogger-rails/app/permitters"=>Object, "/home/circleci/flightlogger-rails/app/policies"=>Object, "/home/circleci/flightlogger-rails/app/public_api"=>Object, "/home/circleci/flightlogger-rails/app/serializers"=>Object, "/home/circleci/flightlogger-rails/app/services"=>Object, "/home/circleci/flightlogger-rails/app/validators"=>Object, "/home/circleci/flightlogger-rails/vendor/bundle/ruby/2.7.0/gems/jquery-minicolors-rails-2.2.6.2/app/inputs"=> Object, "/home/circleci/flightlogger-rails/engines/sms/app/concerns"=>Object, "/home/circleci/flightlogger-rails/engines/sms/app/controllers"=>Object, "/home/circleci/flightlogger-rails/engines/sms/app/helpers"=>Object, "/home/circleci/flightlogger-rails/engines/sms/app/inputs"=>Object, "/home/circleci/flightlogger-rails/engines/sms/app/mailers"=>Object, "/home/circleci/flightlogger-rails/engines/sms/app/models"=>Object, "/home/circleci/flightlogger-rails/vendor/bundle/ruby/2.7.0/gems/sentry-rails-5.6.0/app/jobs"=> Object, "/home/circleci/flightlogger-rails/vendor/bundle/ruby/2.7.0/gems/react_on_rails-13.1.0/app/helpers"=> Object}, @setup=false, @shadowed_files=#<Set: {}>, @tag="rails.main", @to_unload={}>
问题根源
你把#{config.root}/vendor/加到了config.autoload_paths里,这让Zeitwerk主加载器把整个vendor目录都管了起来,包括里面的vendor/bundle(bundler安装gem的目录)。而ActionText的app/helpers目录本来是由Rails自带的rails.once加载器负责的,两个加载器抢同一个目录,就引发了冲突。
另外,Rails 6+用Zeitwerk当默认自动加载器,像app/jobs、app/controllers/concerns这些标准目录,Zeitwerk会自动识别,根本不用手动加到autoload_paths里,之前的配置纯属画蛇添足。
修复方法
清理autoload_paths,删掉多余条目
把config.autoload_paths里的标准目录和vendor/全删掉,只留自定义的目录:config.autoload_paths += %W[ #{config.root}/lib #{config.root}/lib/simple_form #{config.root}/app/models/reports #{config.root}/app/services/pdf_data #{config.root}/app/presenters #{config.root}/app/assets/templates #{config.root}/app/event_handlers #{config.root}/app/queries #{config.root}/app/concepts #{config.root}/app/pdfs ]重点是必须删掉
#{config.root}/vendor/,这是冲突的核心原因。如果要加载vendor下的自定义代码,别直接加整个目录
要是vendor里有你自己写的代码要加载,别把整个vendor目录加进去,只加具体的子目录,比如:# 比如vendor下有custom_code目录需要加载 config.autoload_paths += %W[#{config.root}/vendor/custom_code]绝对不能让加载器管到
vendor/bundle,那是gem的地盘。检查eager_load_paths
config.eager_load_paths里的#{config.root}/lib可以留着,要是lib目录的结构符合Zeitwerk的命名规则,其实不加也没事,留着也不影响。CircleCI命令可选调整
试试给预编译命令加上RAILS_ENV=production,确保环境一致:WEBPACKER_PRECOMPILE=false RAILS_NO_DB=true RAILS_ENV=production bundle exec rake assets:precompile --trace
验证
先在本地跑一遍预编译命令,确认没问题:
WEBPACKER_PRECOMPILE=false RAILS_NO_DB=true bundle exec rake assets:precompile
本地正常的话,再推到CircleCI测试。
内容的提问来源于stack exchange,提问作者Nicholas Ladefoged

