Closed bughit closed 9 years ago
some or all dependency references are absolute.
If you're using an asset that isn't inside of the root of where you're running sprockets, an absolute path will be used to generate the cache key. Can you give me an example project that shows the problem?
I haven't yet tried in a new project but can give you repro directions:
javascript_include_tag 'jquery.sparkline'
jquery.sparkline
is a folder with an index that //= require ./jquery.sparkline-2.1.2
javascript_include_tag 'jquery.sparkline'
raises saying that the absolute path to jquery.sparkline-2.1.2.js via project_copy is no longer under a load path (all the load paths being under project_folder)so this is actually very easy to repro with a new project
with v3.2.0 there's no error, but only because the cache is not used due to absolute paths
with v2.12.4 there's no error and the cache is used
There's a lot of grey area between 1 and 10. I'll attempt trying this tomorrow,
Symlink the tmp/cache in the copy to the original project? Is that what you're doing in a project?
the original project does not have the asset cache (assets:clobber in step 5) the copy has the cache (assets:precompile in step 7) so you need to make the copy's cache available to the original project by creating a symlink to it
I tried that and couldn't reproduce
I'm pointing at my copy's assets and cache:
$ ls -la
total 64
drwxr-xr-x 19 richardschneeman staff 646 Aug 14 11:56 .
drwxr-xr-x 63 richardschneeman staff 2142 Aug 14 11:52 ..
drwxr-xr-x 12 richardschneeman staff 408 Aug 14 11:52 .git
-rw-r--r-- 1 richardschneeman staff 474 Aug 14 11:47 .gitignore
-rw-r--r-- 1 richardschneeman staff 1476 Aug 14 11:47 Gemfile
-rw-r--r-- 1 richardschneeman staff 3851 Aug 14 11:47 Gemfile.lock
-rw-r--r-- 1 richardschneeman staff 478 Aug 14 11:47 README.rdoc
-rw-r--r-- 1 richardschneeman staff 249 Aug 14 11:47 Rakefile
drwxr-xr-x 8 richardschneeman staff 272 Aug 14 11:47 app
drwxr-xr-x 7 richardschneeman staff 238 Aug 14 11:47 bin
drwxr-xr-x 11 richardschneeman staff 374 Aug 14 11:47 config
-rw-r--r-- 1 richardschneeman staff 153 Aug 14 11:47 config.ru
drwxr-xr-x 7 richardschneeman staff 238 Aug 14 11:50 db
drwxr-xr-x 4 richardschneeman staff 136 Aug 14 11:47 lib
drwxr-xr-x 5 richardschneeman staff 170 Aug 14 11:49 log
lrwxr-xr-x 1 richardschneeman staff 28 Aug 14 11:56 public -> ../sprockets-96-copy/public/
drwxr-xr-x 9 richardschneeman staff 306 Aug 14 11:47 test
lrwxr-xr-x 1 richardschneeman staff 25 Aug 14 11:56 tmp -> ../sprockets-96-copy/tmp/
Using sprockets 3.3.0
$ bundle list | grep sprockets
* sprockets (3.3.0)
* sprockets-rails (2.3.2)
It works:
$ curl localhost:3000/users/new
<!DOCTYPE html>
<html>
<head>
<title>Sprockets96</title>
<link rel="stylesheet" media="all" href="/assets/application-0723cb9a2dd5a514d954f70e0fe0b89f6f9f1ae3a375c182f43b5f2b57e9c869.css" data-turbolinks-track="true" />
<script src="/assets/application-3698babcae573d7cf113fedbe38437ec3bc009e54bc20ccfe4a69d94b7a17732.js" data-turbolinks-track="true"></script>
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="SYrsgjE9ckapdN6cxl5h+VZKs6JJtk7dgd/8aCkBAyi8rHRuBA2+hHFBHpU5xTOB7whZ/H6zF87l2UUWpgZ+6Q==" />
</head>
<body>
<h1>New User</h1>
<form class="new_user" id="new_user" action="/users" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="✓" /><input type="hidden" name="authenticity_token" value="mULGsJ5EH1pa6xTh6LTP9hgvZgP8uiHHj701/onQbpdsZF5cq3TTmILe1OgXL52OoW2MXcu/eNTru4yABtcTVg==" />
<div class="actions">
<input type="submit" name="commit" value="Create User" />
</div>
</form>
<a href="/users">Back</a>
</body>
</html>
If you can repro the problem, can you wrap up the two repos you're using in another git repo and throw em' on github for me?
What are you doing IRL to cause this issue? Do you have multiple rails app deploys that are simlinked to the same asset directory is this only to show a repro case?
Don't link public/assets. The presence of precompiled assets bypasses the cache entirely I believe (right?). Also this is dev mode (on demand compilation is on)
The whole point is to have the original project use the cache generated in the copy. Also, I am linking just the tmp/cache/assets not the whole tmp (this probably does not matter)
In capistrano deploys the assets cache is shared between releases. I am dealing with a project in which on demand compilation is on in prod (that will change but not immediately ). On demand compilation tries to use shared cache entries generated by and pointing to previous releases and produces the error (this is exactly what this test demonstrates).
So please try without public/assets linking, if that does not work, I'll upload.
Still working for me:
$ rm -rf public
$ ls -la
total 56
drwxr-xr-x 19 richardschneeman staff 646 Aug 14 15:24 .
drwxr-xr-x 63 richardschneeman staff 2142 Aug 14 11:52 ..
drwxr-xr-x 12 richardschneeman staff 408 Aug 14 11:52 .git
-rw-r--r-- 1 richardschneeman staff 474 Aug 14 11:47 .gitignore
-rw-r--r-- 1 richardschneeman staff 1476 Aug 14 11:47 Gemfile
-rw-r--r-- 1 richardschneeman staff 3851 Aug 14 11:47 Gemfile.lock
-rw-r--r-- 1 richardschneeman staff 478 Aug 14 11:47 README.rdoc
-rw-r--r-- 1 richardschneeman staff 249 Aug 14 11:47 Rakefile
drwxr-xr-x 8 richardschneeman staff 272 Aug 14 11:47 app
drwxr-xr-x 7 richardschneeman staff 238 Aug 14 11:47 bin
drwxr-xr-x 11 richardschneeman staff 374 Aug 14 11:47 config
-rw-r--r-- 1 richardschneeman staff 153 Aug 14 11:47 config.ru
drwxr-xr-x 7 richardschneeman staff 238 Aug 14 11:50 db
drwxr-xr-x 3 richardschneeman staff 102 Aug 14 15:26 lib
drwxr-xr-x 5 richardschneeman staff 170 Aug 14 11:49 log
drwxr-xr-x 2 richardschneeman staff 68 Aug 14 15:24 public
drwxr-xr-x 9 richardschneeman staff 306 Aug 14 11:47 test
lrwxr-xr-x 1 richardschneeman staff 25 Aug 14 11:56 tmp -> ../sprockets-96-copy/tmp/
drwxr-xr-x 3 richardschneeman staff 102 Aug 14 11:47 vendor
# environments/production.rb
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests.
config.cache_classes = true
# Eager load code on boot. This eager loads most of Rails and
# your application in memory, allowing both threaded web servers
# and those relying on copy on write to perform better.
# Rake tasks automatically ignore this option for performance.
config.eager_load = true
# Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Enable Rack::Cache to put a simple HTTP cache in front of your application
# Add `rack-cache` to your Gemfile before enabling this.
# For large-scale production use, consider using a caching reverse proxy like
# NGINX, varnish or squid.
# config.action_dispatch.rack_cache = true
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
# config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = true
# Asset digests allow you to set far-future HTTP expiration dates on all assets,
# yet still be able to expire them through the digest params.
config.assets.digest = true
# `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
# Use the lowest log level to ensure availability of diagnostic information
# when problems arise.
config.log_level = :debug
# Prepend all log lines with the following tags.
# config.log_tags = [ :subdomain, :uuid ]
# Use a different logger for distributed setups.
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = 'http://assets.example.com'
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
end
Started with
$ env SECRET_KEY_BASE=foo RAILS_ENV=production RAILS_SERVE_STATIC_FILES=1 rails s
Result
$ curl http://localhost:3000/assets/application-3698babcae573d7cf113fedbe38437ec3bc009e54bc20ccfe4a69d94b7a17732.js -I
HTTP/1.1 405 Method Not Allowed
Content-Type: text/plain
Content-Length: 18
Cache-Control: no-cache
X-Request-Id: 8b5bdc1e-d662-408f-a752-bb2c1395f327
X-Runtime: 0.000544
Server: WEBrick/1.3.1 (Ruby/2.2.2/2015-04-13)
Date: Fri, 14 Aug 2015 20:29:33 GMT
Connection: Keep-Alive
The presence of precompiled assets bypasses the cache entirely I believe (right?)
Not quite. First sprockets tries to see if there is a valid asset by first using mtime with the cache to make sure the source file in app/assets
hasn't changed. If it hasn't and the cache returns a value then another cache lookup will be performed that stores all the "dependencies" of that asset. Each dependency is pulled out and "resolved" (i.e. they check the mtime) to make sure none of them has changed. If they're all still the same then it uses a digest of the dependencies to create another cache key, this one is finally used to pull the asset from the cache. Only then will sprockets check the disk to see if the asset with digest is already in public/assets. I'm not 100% sure that's everything, but it's my understanding of how sprockets work, though it wouldn't be the first time i've been surprised by sprockets.
on demand compilation is on
This is a huge performance penalty to your site, I hope you've got some really good CDN in front of your site so each asset only has to be compiled once, otherwise you're in for a world of pain. I would highly recommend against leaving this option on, I would be curious hearing why it's needed but perhaps in a different conversation to keep this thread focused.
For your use case, can you add the physical location of the assets to sprockets load paths? Sprockets can use multiple paths, though i'm not sure how to set this through rails, that might alleviate the issue. Another option could be symlinking the public/assets directory so that you're pointing the cache and the assets directory to the same project/path-structure.
you are doing a HEAD on application.js and it's responding with method not allowed. You are not hitting the asset cache with that.
I tested doing just a GET on application.js and it works. So, as I directed, you need to invoke an action that does a javascript_include_tag or css.
I'll upload, unless you can retry quickly.
That was the wrong paste. It renders correctly when I visit the page and when I curl without -I
created a repo
Please follow these directions exactly:
can you add the physical location of the assets to sprockets load paths?
As I mentioned, sprockets 2 works fine, so that's the workaround we are using. sprockets 2 cache does not use absolute paths for it's keys or anything else (I think there is nothing else in 2.x), which facilitates cache sharing between releases. Sprockets 3 should not be using absolute paths at all either. 3.3 started along this path, but didn't finish.
Got the same error in production this morning, the way we do the deploy is by compiling locally and checking the manifest into the repo and uploading to cloudfront (via asset_sync). Here's the backtrace in case it's useful:
Error message Sprockets::FileOutsidePaths: /home/ubuntu/apps/histreet/releases/20150814095620/app/assets/javascripts/spree/turbolinks.js.coffee is no longer under a load path:
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/gems/opal-0.8.0/opal,
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/gems/opal-0.8.0/stdlib,
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/gems/opal-0.8.0/lib,
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/bundler/gems/opal-jquery-5f6662498a81/lib,
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/gems/opal-activesupport-0.1.0/opal,
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/gems/opal-rspec-0.4.3/opal,
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/gems/opal-rspec-0.4.3/vendor_lib,
/home/ubuntu/apps/histreet/shared/bundle/ruby/2.2.0/bundler/gems/opal-haml-3996e34c67bb/opal,
/home/ubuntu/apps/histreet/releases/20150814102012/app/assets/images,
/home/ubuntu/apps/histreet/releases/20150814102012/app/assets/javascripts,
/home/ubuntu/apps/histreet/releases/20150814102012/app/assets/stylesheets,
/home/ubuntu/apps/histreet/releases/20150814102012/vendor/assets/javascripts,
/home/ubuntu/apps/histreet/releases/20150814102012/vendor/assets/stylesheets,
[cut]
Looks like relative paths are being confused by the changed root (from 20150814095620 to 20150814102012) which is stored in the cache for some reason.
Also I tried the steps above and I can reproduce.
I hit the same error when putting some images in vendor/assets/images
, deploying with capistrano and compiling the assets on the server. Then, the following error occurs:
Sprockets::FileOutsidePaths:
/var/rails/releases/20150814213520/vendor/assets/images/fancybox_sprite.png is no longer under a load path:
/var/rails/releases/20150814225400/app/assets/images,
/var/rails/releases/20150814225400/app/assets/javascripts,
/var/rails/releases/20150814225400/app/assets/stylesheets,
/var/rails/releases/20150814225400/lib/assets/javascripts,
/var/rails/releases/20150814225400/lib/assets/stylesheets,
/var/rails/releases/20150814225400/vendor/assets/images
...
That fancybox_sprite.png
file however, does exist on the server, so it looks like the same sprockets caching bug.
Thanks for the report, I got the example @bughit sent over to reproduce the problem.
Sprockets 3 should not be using absolute paths at all either. 3.3 started along this path, but didn't finish.
Right now if an asset is not inside of your root, then we cache the absolute path.
My original assumption was all your assets would either be relative to your project root or at a static path such as /Users/richardschneeman/.gem/ruby/2.2.2/gems/twitter-bootstrap-rails-3.2.0/
but that doesn't look to be valid.
I'm not sure what other alternatives we have. If we cache the relative path to the asset ../../../../../../.gem/ruby/2.2.2/gems/twitter-bootstrap-rails-3.2.0/
then this would change when you moved or down a level. I don't know how sprockets 2 caching worked, and I don't know why the caching scheme was changed to use absolute paths. I can dig into this deeper on monday.
Right now if an asset is not inside of your root, then we cache the absolute path.
I don't understand what you are saying there. There are only two assets, application.js and application.css and both are inside the project (app/assets). I am assuming that's what you mean by "inside the root".
The cache is generated in the asset_cache_test_copy by precompile. So you appear to be saying there should not be any absolute paths in the cache files, but clearly there are.
Here's the issue. When we try to load the asset via https://github.com/rails/sprockets/blob/de057b4f13deb2bb5e8684cd06b9b687f5d09792/lib/sprockets/loader.rb#L201-L202 we find a cache entry exists, and bingo:
{:uri=>
"file:///Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/application.css?type=text/css&id=362558b6fff2447f73fc9fc4e37a7315119a920a68da221e0c7a8a1b0246e723",
:load_path=>
"/Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets",
:filename=>
"/Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/application.css",
:name=>"application",
:logical_path=>"application.css",
:content_type=>"text/css",
:source=>
"/*\n * This is a manifest file that'll be compiled into application.css, which will include all the files\n * listed below.\n *\n * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,\n * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.\n *\n * You're free to add application-wide styles to this file and they'll appear at the bottom of the\n * compiled file so the styles you add here take precedence over styles defined in any styles\n * defined in the other CSS/SCSS files in this directory. It is generally better to create a new\n * file per style scope.\n *\n\n\n */\n\n",
:metadata=>
{:dependencies=>
#<Set: {"environment-version",
"environment-paths",
"processors:type=text/css&file_type=text/css",
"file-digest:///Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/application.css",
"processors:type=text/css&file_type=text/css&pipeline=self",
"file-digest:///Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets"}>,
:sass_dependencies=>#<Set: {}>,
:links=>#<Set: {}>,
:included=>
["file:///Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/application.css?type=text/css&pipeline=self&id=74e025a9f030d490798b9eb416d5a4451a77bec3e06bdbd0b30a45fd1c455629"],
:charset=>"utf-8",
:digest=>
"\xE8\x0E\x8F#\x18\x04>\x8A\xF9M\xDD\xC2\xAD\xADZO\ts\x9A\x8E\xBB2;:\xB3\x1C\xD7\x1DE\xFD\x91\x13",
:length=>653},
:dependencies_digest=>
"\x81X\xC8\xC7$\xAA|\xF7\x11'\xEB\xC4p\xEBI\xF7\x1F^l\x1Ab\xDC!\xAD?\x8ADN\"\x9B\n\xC7",
:id=>"362558b6fff2447f73fc9fc4e37a7315119a920a68da221e0c7a8a1b0246e723",
:mtime=>1439675961}
The uri
and load_path
both have absolute file paths stored in them. The metadata[:dependencies]
does as well, but I'm already handling that in the resolve_dependencies
method in the same file. It's probably not caught in the test I wrote since the asset already exists on disk in our copy, though i'm not 100% sure why it doesn't try to load the absolute uri to the wrong path https://github.com/rails/sprockets/blob/de057b4f13deb2bb5e8684cd06b9b687f5d09792/test/test_performance.rb#L109-L134.
The uri and load_path both have absolute file paths stored in them. The metadata[:dependencies] does as well
Right, and they shouldn't. There is no good reason to store absolute paths in the cache entry (unless, as you pointed out, the assets is outside the project)
yes
Here's my WIP https://github.com/rails/sprockets/pull/101 needs some major cleanup. Not sure about using presence of a host
to share the absolute/relative information but it seems to work fine. This solution works for your test case but is pretty gnarly
For anyone having that error with vendor/assets (which worked before), I was able to resolve production deploy by adding to config/initializers/assets.rb
the following:
# Add additional assets to the asset load path
Rails.application.config.assets.paths << "#{Rails.root}/vendor/assets"
Not sure if it's a correct fix, but it helped me to deploy my app.
The fix is committed to the 3.x branch and released in https://rubygems.org/gems/sprockets/versions/3.3.2 give it a spin and let me know if you seen any problems
I precompiled a bunch of stuff and I am still seeing absolute paths for relative path requires //= require ./file
My test didn't exercise every possible way to require/load and whatever else sprockets supports, so it's possible things other than require ./file
are also still broken
Thanks for the quick reply. Here's all the keys in the metadata hash generated by running the tests:
[:dependencies, :sass_dependencies, :links, :selector_count, :included, :charset, :digest, :length]
We are already handling :dependencies
and :included
.
I ran tests and it looks like there's two other places where we're storing absolute paths:
@metadata[:links]: #<Set: {"file:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/asset/test-b.js?type=application/javascript&id=5a1727512f031078ee607a7af82cbd42e59740e9fae3558614e5b4d0d342362d"}>
and
@metadata[:sass_dependencies]: #<Set: {"/Users/richardschneeman/Documents/projects/sprockets/test/fixtures/source-maps/sass/main.scss"}>
Ugh.
Luckily the pattern of expanding and compressing in the loader.rb should be a quick fix for those two things, however It looks like any processor can add things to your asset's meta hash. I'm not sure if there's other absolute paths that are stored but not covered in the test suite.
The processor shouldn't have to know about expanding/compressing cache paths, but it shouldn't just arbitrarily be able to break your cache with an absolute path. I wish the original author was around for stuff like this, having some kind of a larger picture is easier than playing wackamole.
Stubbed is also a full URI but not tested at all :( https://github.com/rails/sprockets#metadata
the absolute paths that I noticed are in the :required
section and are a result of relative requires
require ./file
, require_tree .
, whatever else can do relative
I don't think you covered these in the previous post
Yep, the docs also have a :required
key. I'm not able to get :required
or :subbed
to show up in tests.
Here's my fixture:
In test/fixtures/default:
//= stub interpolation.js
//= require ./gallery.js
//= require noreturn.js
//= require_tree ./vendor/gems/jquery-2-0
//= require_self
//= link "logo.svg"
//= require ./+plus.js
var dog = "cinco";
Here's asset:
{:uri=>
"file:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/schneems.js.erb?type=application/javascript&id=ebfe3e03d098268cfbeff01680872d26b5a44dc5e9fab54c68efa1016e6ebf70",
:load_path=>
"/var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/",
:filename=>
"/var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/schneems.js.erb",
:name=>"schneems",
:logical_path=>"schneems.js",
:content_type=>"application/javascript",
:source=>
"var Gallery = {};\nvar Foo;\nvar jQuery;\n\n\n\n\n\n\n\n\n\n\nvar dog = \"cinco\";\nfunction plus(a, b) { return a + b; }\n",
:metadata=>
{:dependencies=>
#<Set: {"environment-version",
"environment-paths",
"processors:type=application/javascript&file_type=application/javascript&engines=.erb",
"file-digest:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/schneems.js.erb",
"processors:type=application/javascript&file_type=application/javascript&pipeline=self",
"file-digest:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/gallery.js",
"file-digest:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/noreturn.js",
"file-digest:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/vendor/gems/jquery-2-0/jquery.js",
"processors:type=application/javascript&file_type=application/javascript&engines=.erb&pipeline=self",
"file-digest:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/",
"file-digest:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/interpolation",
"file-digest:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/interpolation.js",
"file-digest:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/gallery",
"file-digest:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/noreturn",
"file-digest:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/vendor/gems/jquery-2-0",
"file-digest:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/logo",
"file-digest:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/logo.svg",
"processors:type=image/svg+xml&file_type=image/svg+xml",
"file-digest:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/+plus",
"file-digest:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/+plus.js"}>,
:links=>
#<Set: {"file:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/logo.svg?type=image/svg+xml&id=8d727e776f3667b8f6e5f80c817cefbf6176ffa56cd59b2c7c70f2796dfd7859"}>,
:included=>
["file:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/gallery.js?type=application/javascript&pipeline=self&id=0b2248cdbffcf10edf68b9774a77e7c3de3f74fd8d09daa935ee5882d73cb807",
"file:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/noreturn.js?type=application/javascript&pipeline=self&id=d10461e0c8febe9020353af585d4d15121e83bb72d1edac263bedaf99faa5e78",
"file:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/vendor/gems/jquery-2-0/jquery.js?type=application/javascript&pipeline=self&id=8a3e330d77edea6abab64eccb4f3141983fe5b67b91216efe8b2616292260a24",
"file:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/schneems.js.erb?type=application/javascript&pipeline=self&id=70d86c54c75720e5bdf4a57342ba56540ed448b31b487bd8881680e8b8c812af",
"file:///var/folders/ss/lf77xfjd1g7ftznjmk3jzw1w0000gn/T/d20150819-86136-1vj0qjq/+plus.js?type=application/javascript&pipeline=self&id=07036c17f6b3aaf8f2cca3f7ee5713cdf33bbd416f3320633faa09d38b6bae9f"],
:charset=>"utf-8",
:digest=>
"u\xCF\xE0\xE9^\xD0(\x87x\xBE\x12\xBB@\xF8;\xC6\xF7\xF3;x\x1E\xEB|G\xD9\xC0\xC8\xEFa\x9BG\xE1",
:length=>106},
:dependencies_digest=>
"h\n\x95\x15!\x10\xA4\x8C\xAD\x19`/d\xAE\xF7\xBE\x90Oa\x8F\xF0\xDB\x8A\x148K\xB9\xD9B4\xB1P",
:id=>"ebfe3e03d098268cfbeff01680872d26b5a44dc5e9fab54c68efa1016e6ebf70",
:mtime=>1440007863}
I don't know about the tests, but to see it in vivo is easy.
pull my updated sample, do a precompile in the copy and look at the cache files
Thanks, i'm on it.
This is extremely frustrating. I can clearly see a :required
in your example going into the cache
:required=>
#<Set: {"file:///Users/richardschneeman/Documents/projects/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/application.css?type=text/css&pipeline=self"}>,
:stubbed=>#<Set: {}>,
:links=>#<Set: {}>,
But no matter what I do, I can't get
env1 = Sprockets::Environment.new(fixture_path('default')) do |env|
env.append_path(".")
env.cache = @cache
end
To give me an asset that has a :stubbed
or :required
metadata key. I can verify that the DirectiveProcessor is being run, which is what looks like it inserts that key. Maybe @guilleiguaran has some idea of what rails might be doing differently than vanilla sprockets?
On the first asset["schneems.js"]
call i'm getting two different entries put in the cache:
using
//= stubs "schneems.js"
//= require_self
var dog = "cinco";
I'm seeing two different assets being generated
uri: "file:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js?type=application/javascript&pipeline=self&id=87aebf99a63e757ed0c9b987eaa479110b5a48b8a51867e7f77f413c74e8ae03"
metadata: {:dependencies=>#<Set: {"environment-version", "environment-paths", "processors:type=application/javascript&file_type=application/javascript&pipeline=self", "file-digest:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js"}>, :required=>#<Set: {"file:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js?type=application/javascript&pipeline=self"}>, :stubbed=>#<Set: {}>, :links=>#<Set: {}>, :charset=>"utf-8", :digest=>"x\xBA\xD9}\xAA\xD4\xDB\xAC\x00Z\x8D\x8A\xF1Y\xA9\x83\x1E\x12\xFC\xA6\x879\x9D\xD5\x930\xEE\xD8F\xF9\bu", :length=>45}
uri: "file:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js?type=application/javascript&id=9d6310e3ec2c2b6b29d0c8dfda084834e6b652263a681837d139c0d07648eeca"
metadata: {:dependencies=>#<Set: {"environment-version", "environment-paths", "processors:type=application/javascript&file_type=application/javascript", "file-digest:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js", "processors:type=application/javascript&file_type=application/javascript&pipeline=self"}>, :links=>#<Set: {}>, :included=>["file:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js?type=application/javascript&pipeline=self&id=87aebf99a63e757ed0c9b987eaa479110b5a48b8a51867e7f77f413c74e8ae03"], :charset=>"utf-8", :digest=>"x\xBA\xD9}\xAA\xD4\xDB\xAC\x00Z\x8D\x8A\xF1Y\xA9\x83\x1E\x12\xFC\xA6\x879\x9D\xD5\x930\xEE\xD8F\xF9\bu", :length=>45}
One that has required and stubbed, one that doesn't.
@schneems maybe it is sass-rails and the glob importer?
Hi guys,
just wanted to let you know that we are having the same problem.
We make a copy of the old project folder with rsync (including puglic/assets
and tmp/cache/assets
) and then run precompile
on the copied folder. This would result in the following error.
Sprockets::FileOutsidePaths:
/var/local/kenhub/kenhub.d/111/vendor/bundle/ruby/2.2.0/gems/normalize-rails3.0.3/vendor/assets/stylesheets/normalize-rails.scss is no longer under a load path:
/var/local/kenhub/kenhub.d/113/app/assets/audios,
/var/local/kenhub/kenhub.d/113/app/assets/fonts,
/var/local/kenhub/kenhub.d/113/app/assets/images,
/var/local/kenhub/kenhub.d/113/app/assets/javascripts,
/var/local/kenhub/kenhub.d/113/app/assets/stylesheets,
/var/local/kenhub/kenhub.d/113/vendor/assets/images,
/var/local/kenhub/kenhub.d/113/vendor/assets/javascripts,
/var/local/kenhub/kenhub.d/113/vendor/assets/stylesheets,
...
as you can see it uses the older folder 111
not the new folder 113
.
We upgraded sprockets because the precompilation took really long. We were happy to see that this got fixed here https://github.com/rails/sprockets/issues/59 but obviously introduced this new error.
Thanks for your help!
Caution: brain dump. I'm writing this not for you really, but more for me. If I can't explain the process, I don't really understand it. If you want to know the answer to why we couldn't see those things in the tests, skip to the bottom.
Here's the life cycle of an asset. I reduced the complexity of my javascript to only
var dog = "cinco";
To get things started we use the sprockets api to initiate asset lookup:
env['schneems.js']
It's never been loaded before. We've got the cache on. Sprockets calls resolve
on "schneems.js"
and resolves the URI to:
"file:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js?type=application/javascript"
This asset isn't in cache so it calls load_from_unloaded
in the loader.rb
, before it's fully loaded, the we call call_processors
on the asset. The first time the asset is passed in the only processor called is
[Sprockets::Bundle]
The "processors" are pulled based on the pipeline
query param in the asset uri, if there is none :default
will be used:
def processors_for(type, file_type, engine_extnames, pipeline)
pipeline ||= :default
config[:pipelines][pipeline.to_sym].call(self, type, file_type, engine_extnames)
end
Processors are defined like:
register_pipeline :default do |env, type, file_type, engine_extnames|
env.default_processors_for(type, file_type, engine_extnames)
end
When this is called, the file type which is "application/javascript"
will be passed in and the "bundle processor" registered for this type will be returned. This happens to be Sprockets::Bundle
from:
register_bundle_processor 'application/javascript', Bundle
Now we call this processor on our asset. Bundle#call gets executed with :uri=>"file:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js?type=application/javascript"
and :filename=>"/Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js"
We resolve this uri again, after adding an accept
, a compat=false
and a pipeline=self
to the URI. This process also identifies dependencies of the asset. Since this asset is not doing anything else it declares it's own digest as a dependency:
#<Set: {"file-digest:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js"}>
It merges this with existing dependencies
#<Set: {"environment-version", "environment-paths", "processors:type=application/javascript&file_type=application/javascript", "file-digest:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js"}>
Which produces the same set. It uses the processed_uri
which is: "file:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js?type=application/javascript&pipeline=self"
(I think we're adding the pipeline=self here to avoid infinite recursion as we call load on the URI again.
We then use Utils.dfs
to find any "required" files from the processed_uri
. This will pull all entries from metadata[:required]
of the original asset, and iterate over each of those "required" to find their "required" until it's seen the whole tree.
This was the confusing part to me since we're calling env.load(uri)
on the processed_uri
which is almost identical to our original URI minus the pipeline=self. This initiates the whole process again i.e. we're calling Environment#load from within Environment#load.
The difference here is that since we're not using the :default
"pipeline" we're now using the "self" pipeline which include:
[#<Sprockets::DirectiveProcessor:0x007fc8bc176718 @header_pattern=/\A(?:(?m:\s*)(?:(?:\/\/.*\n?)+|(?:\/\*(?m:.*?)\*\/)|(?:\#.*\n?)+|(?:\#\#\#(?m:.*?)\#\#\#)))+/>, Sprockets::FileReader]
The DirectiveProcessor parses and evaluates directives in a file like:
//= require "foo"
The FileReader processor reads in the input filename, it reads the source of the file into memory, and generates a digest of the content. The data is returned and merged back into the original to finish the "loading" of this asset and a fully resolved Asset object is created (and stored in the cache), it's important to not that the Bundle
processor isn't called in this process, otherwise we could never return. Remember that the asset we just generated isn't the one we're looking for, it's an intermediate Asset object, now we rewind our stack all the way back to the Bundle processor where we were when we called this second "load"
.
We use this second asset which was generated in our Utils.dfs
search for all required uris for the original asset loaded.
In this case there is only one "element" in the array. It finds:
required = #<Set: {"file:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js?type=application/javascript&pipeline=self"}>
We then do the same thing but start our search with the "stubbed" set from our pipeline=self asset. Since there's no stubbed assets an empty set is returned.
It's important to note that env.load(uri)
is memoized, so that multiple calls do not do the whole DirectiveProcessor FileReader dance, only the first call.
Now the "stubbed" and "required" Sets are merged into the original "dependencies" set. The final "dependencies ends up being:
dependencies =#<Set: {"environment-version", "environment-paths", "processors:type=application/javascript&file_type=application/javascript", "file-digest:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js", "processors:type=application/javascript&file_type=application/javascript&pipeline=self"}>
Now all of this data is "reduced" the return of this Bundle#call is
{:data=>"\nvar dog = \"cinco\";\n",
:links=>#<Set: {}>,
:dependencies=>
#<Set: {"environment-version",
"environment-paths",
"processors:type=application/javascript&file_type=application/javascript",
"file-digest:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js",
"processors:type=application/javascript&file_type=application/javascript&pipeline=self"}>,
:included=>
["file:///Users/richardschneeman/Documents/projects/sprockets/test/fixtures/default/schneems.js?type=application/javascript&pipeline=self&id=7a785b7a0f2f95b15fd0a1b765d9ee974a85991ed94b433e768f597c26393e83"]}
While the second pipeline=self asset has a "required" set and an (empty) "stubbed" set. The primary asset that we are loading does not have these things. Why? Well we don't pass in "stubbed" or "required" to the reducer, so there's no way they could come out. Those two things are intermediate sets used to fully populate the "dependencies" set.
So stubbed
and required
are both intermediate objects that are stored in the cache but we CANNOT externally access them without going into private APIs
Here's the patch, https://github.com/rails/sprockets/pull/109 I ended up dipping into a private API for the tests. I think it's better to test this functionality and risk a slightly brittle test than to leave it untested.
Can you try my patch on your own apps, so we hopefully don't have to do another merge/patch/release cycle after this.
You can put this in your gemfile:
gem "sprockets", github: "rails/sprockets", branch: "schneems/fix-metadata-relative-3.x"
We are pending merging and cutting a new sprockets version on your feedback.
cc/ @bughit @joker-777
updated sample, there are 4 cache files with absolute paths
Thanks a ton! I see several absolute paths from gems:
file-digest:///Users/richardschneeman/.gem/ruby/2.2.3/gems/jquery-rails-4.0.4/vendor/assets/javascripts/jquery_ujs.js
This is expected, as they're outside of the root, your gem directory shouldn't be changing between builds that frequently. If people really care about it, they can bundle their into a vendor directory inside of their root. I'm also seeing the absolute path of the source scss file written to the contents of another file:
/* line 1, /Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss2.scss */
This is unexpected, I'm not sure where that's being inserted, or what that comment is being used for. I'll have to dig in more.
I wasn't counting the first kind, was greping for project source dir full path, which gave me 4 files, and you?
In addition to the second kind (comment), there was also @file
and @root
(I think, will double check tomorrow) Are you seeing those?
I can confirm i'm also seeing some file:
but these are gems as well
"file:///Users/richardschneeman/.gem/ruby/2.2.3/gems/jquery-rails-4.0.4/vendor/assets/javascripts/jquery.js?type=application/javascript&pipeline=self"
I added a puts to the Cache#set
method and here's everything being written to cache along with its key:
https://gist.github.com/schneems/1f988a4bcbff8b0c0a3a
It does look like sass writes something else other than the comment:
{:version=>"3.4.16 (Selective Steve)", :sha=>"71853c6197a6a7f222db0f1978c7cb232b87c5ee", :contents=>"\x04\bo:\x19Sass::Tree::RootNode\n:\x0E@children[\x00:\x0E@templateI\"\a\n\n\x06:\x06ET:\n@linei\x06:\x12@source_rangeo:\x18Sass::Source::Range\t:\x0F@start_poso:\eSass::Source::Position\a;\ti\x06:\f@offseti\x06:\r@end_poso;\r\a;\ti\x06;\x0Ei\x06:\n@fileI\"|/Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss1.scss\x06;\bT:\x0E@importero:\x1ESass::Rails::SassImporter\b:\n@rootI\"|/Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss1.scss\x06;\bF:\x0F@real_rootI\"|/Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss1.scss\x06;\bF:\x18@same_name_warningso:\bSet\x06:\n@hash{\x00:\r@options{\x00"}
My root is /Users/richardschneeman/Documents/projects/tmp/asset_cache_repo
.
All this sass stuff is new to me. It looks like some (all?) of this is coming from the sass gem directly. If that's the case either Sprockets2 has the same bug, or it ends up not mattering. Not sure without more research.
grep /tmp/asset_cache_repo/asset_cache_test_copy/ *
Binary file BPk82Q0VktG2GPovwSHPysyr27IyWE5kuwo6FWsTccA.cache matches
Binary file lpNenmFa5RbxzW1angUNN_l6blUaixmPjHrpFoafsgE.cache matches
Binary file PmQLT63FTKauRkinZSc5stYiJXhMun10-pUVsy2bhI0.cache matches
Binary file xfzSrEFVSrlGcqb97NgufRAtPlLchvbikTlsBfVZgaA.cache matches
grep -a /tmp/asset_cache_repo/asset_cache_test_copy/ *
BPk82Q0VktG2GPovwSHPysyr27IyWE5kuwo6FWsTccA.cache:{uriI"file://app/assets/stylesheets/application.css?type=text/css&id=13290f4508text/css;T:p/assets/stylesheets/application.css;T::nameI"application;T:logical_pathI"application.css;T:content_typeI"
sourceI"#/* line 1, /tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss2.scss */
lpNenmFa5RbxzW1angUNN_l6blUaixmPjHrpFoafsgE.cache:@file0:@importer0;i;0;i;i;0:@selector_source_rangeo; ;o;;i;i;o;;i;i;I"R/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss2.scss; T;o:Sass::Rails::SassImporte:
lpNenmFa5RbxzW1angUNN_l6blUaixmPjHrpFoafsgE.cache:@rootI"R/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss2.scss; F:@real_rootI"R/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss2.scss; F:@same_name_warningso;;{:
PmQLT63FTKauRkinZSc5stYiJXhMun10-pUVsy2bhI0.cache:@fileI"R/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss1.scssT:@importero:Sass::Rails::SassImporte:
PmQLT63FTKauRkinZSc5stYiJXhMun10-pUVsy2bhI0.cache:@rootI"R/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss1.scssF:@real_rootI"R/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss1.scssF:@same_name_warningsoSet:
text/css;T:qb97NgufRAtPlLchvbikTlsBfVZgaA.cache:scss2;T:logical_pathI"scss2.self.css;T:content_typeI"
sourceI"w/* line 1, /tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss2.scss */
The comment is being inserted by https://github.com/sass/sass/blob/1421c1fb38f56d947d1e0271ab5ed12dee510d48/lib/sass/tree/visitors/to_css.rb#L307 and that other absolute paths come from sass cache.
Sass uses marshal dump: https://github.com/sass/sass/blob/stable/lib/sass/cache_stores/base.rb#L51
The nodes it dumps contain a "source_range"
[3] pry(#<Sass::Rails::CacheStore>)> root.inspect
=> "Sass::Tree::RootNode"
# ...
[6] pry(#<Sass::Rails::CacheStore>)> root.source_range
=> (1:1 to 1:1 in /Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/app/assets/stylesheets/scss1.scss)
All the absolute paths are coming from sass.
If there's no absolute paths coming from sprockets (that we can find for now) I vote we merge and cut a release. I'll then work with sass to remove the absolute paths there.
Unfortunately sprockets doesn't have a dependency on sass since you can use sprockets without sass, so we can't rev the sass dependency to guarantee no absolute paths. We could check the version and emit a warning perhaps?
I'm testing https://github.com/rails/sprockets/issues/96#issuecomment-13315087 right now and couldn't experience the error again. Looks like as if it is fixed for me.
@joker-777 thanks for testing! I opened an issue with sass https://github.com/sass/sass/issues/1802 hopefully I can get some help there while I work on a patch.
@schneems You are welcome. Unfortunately we just discovered that it somehow doesn't recompile js files which were changed. Investigating further.
@joker-777 let me know. I just tested with @bughit's app and it seems to work
2.2.3 ~/documents/projects/tmp/asset_cache_repo/asset_cache_test_copy (master)
$ bin/rake assets:precompile
2.2.3 ~/documents/projects/tmp/asset_cache_repo/asset_cache_test_copy (master)
$ echo 'var foo = "hello";' >> app/assets/javascripts/asset1.js
2.2.3 ~/documents/projects/tmp/asset_cache_repo/asset_cache_test_copy (master)
$ bin/rake assets:precompile
I, [2015-08-21T10:12:08.152279 #66549] INFO -- : Writing /Users/richardschneeman/Documents/projects/tmp/asset_cache_repo/asset_cache_test_copy/public/assets/application-55ae4c5318c07b2196f18e52480c0563fe6631eb7fd24d38a2e15a5d50a55b59.js
Not a 100% sure, but what seems to be happening is even though cache key generation now uses relative paths, some or all depedency references are absolute.
so if you are sharing the asset cache folder among multiple release, sprockets ends up finding the right cache entry but then tries to load dependencies from an older release absolute path (where the cache was originally constructed) and fails with Sprockets::FileOutsidePaths