14

I ran into problems with asset pre-compilation during deploys, so I opted to go for local pre-compilation and check in the resulting files to my source tree. I don't really have any problems with that approach, except that sometimes I forget to run the precompile task and release without precompiling assets! :(

I'm wondering if anyone has come across some sort of way to check to see if any asset changes have occurred? Ideally I'd like to run some sort of check on my CI server and fail the build if there's asset changes that haven't been committed.

I had a couple of thoughts:

  1. Run RAILS_ENV=production bundle exec rake assets:precompile on the CI server and see if there's any output. (The command appears to not output anything if assets are up-to-date.) However, it seems as though the command's output is tied somehow to the environment it runs in, because after running the command locally, committing the results, and then running the command on the CI server, there is still output from the command! I'd like to know why this is tied to the environment, but I can't even find the source for rake assets:precompile in the rails github repo. Does anyone know where the source for that is?

  2. Somehow write a command that can look through the git history and determine if any assets have changed within my assets/ folder since the last precompilation. Not really sure how that would work...

This has bit me more than a couple times, and sometimes I don't catch it when co-workers commit asset changes - plus it really seems like this is something a computer should be able to catch for a human. I guess a somewhat reasonable third alternative would be to have the CI server simply run the command, and commit the generated files to the source tree automatically, but I don't like the idea of my CI server making commits.

Any thoughts? Thanks.

Mark G.
  • 3,176
  • 1
  • 27
  • 29
  • Well I've discovered that the `rake assets:precompile` task is defined in the [sprockets-rails](https://github.com/rails/sprockets-rails) project. Still haven't made any progress on this yet though. I'm hopeful I can deduce what's going on by digging through that code. – Mark G. Mar 17 '15 at 07:08
  • 1
    I've logged an issue on the sprockets-rails project around this - https://github.com/rails/sprockets-rails/issues/264 If you're having this issue I suggest you upvote. – Jaco Pretorius Jul 20 '15 at 12:30
  • Is there a public project where the environment-variability in rake assets:precompile could be demonstrated (provided of course one could find the key required difference in the environments). – javabrett Jul 20 '15 at 13:36

4 Answers4

8

You can find the source for the precompile task in sprockets-rails. The reason why you're not seeing any changes locally may be due to fingerprinting, which is enabled by default in production and disabled in the other environments (as debugging with fingerprints can be a hassle). You can enable it using config.assets.digest as described in the assets guide

As you mentioned, it's easy to forget the precompilation step. An elegant automation would be to remove the compiled assets from your repo and to add a capistrano (or CI) task to precompile assets with each deployment. The precompilation would ideally happen on the CI server (as opposed to running it on each production server). This approach also alleviates the need to keep scanning the compiled assets for changes (which you really shouldn't have to care about).

Automatically committing anything to your repo is a bad idea - in addition to inadvertent commits, you'd end up convoluting your commit history.

Musannif Zahir
  • 3,001
  • 1
  • 21
  • 31
  • It's kind of crazy that there isn't a gem or something packaged up that handles all of this for you. Seems like something that most people would run into at some point??? – Mark G. Jul 22 '15 at 23:15
  • 1
    There are some gem solutions out there. Take a look at Heroku's `rails_12factor`, which addresses this along with a bunch of other standards/optimizations https://github.com/heroku/rails_12factor – Musannif Zahir Jul 23 '15 at 06:25
  • I want to accept this answer because I feel like this has really directed me to using CloudFront, which really seems like more of a 'best practice' and gets away from precompiling assets at all (awesome!). However, I know that not all people may be in the position of using AWS services for their rails deployment. If someone is able to determine how to get this task to run consistently across environments, I might switch answers, but I feel like this is really a better approach. – Mark G. Jul 23 '15 at 18:15
  • @MarkG glad you found a good alternative. If fingerprinting the assets in dev is enabled, the rake task will be consistent across all environments – Musannif Zahir Jul 23 '15 at 20:02
3

My answer is Git-based, an implementation of the OP's thought 2: can we fail the build based on Git-metadata telling-us the assets/ source is newer than the assets-precompile/ currently checked-in. The answer might be able to be improved based on conditions in the check-out environment, or some knowledge of the Rails build.

  1. git log -1 --format=%ct -- assets/ will give you the timestamp of the latest source commit.
  2. git log -1 --format=%ct -- assets-precompile/ will give you the timestamp of the most recent pre-compile check-in.

Add a condition early in the build which compares these two numbers, and if 1>=2, fail any release build. Actually, you may want to build in a grace period there, in-case someone slips-in with a commit in-between when you checkout and run the pre-compile, and when you commit.

Alternatively, to be stricter with this, you would store the commit-hash of {{assets/}} in a file, and if it changes and differs from that at the last pre-compile, again, fail any release build, until it is again pre-compiled and checked-in:

git log --format=%H -- assets/

... and compare that with a commit-hash you update with your pre-commit. You might also use %T for the tree-hash instead of the commit-hash, which might simplify branching.

javabrett
  • 7,020
  • 4
  • 51
  • 73
  • 1
    This doesn't solve the original problem though - why does running `rake assets:precompile` on one developer's machine result in no changes, but then running the exact same command on another developer's machine results in changes. – Jaco Pretorius Jul 20 '15 at 12:29
2

I'm going through the same effort, but I haven't been able to sort out this problem though. A first step is to use a git pre-commit hook to compile assets (which will only do something if there are modified assets).

http://jimneath.org/2012/05/05/precompile-assets-using-a-git-hook.html

Edit: Looks like that link got nuked, here's the archived version: https://web.archive.org/web/20161022195654/http://jimneath.org/2012/05/05/precompile-assets-using-a-git-hook.html

Jaco Pretorius
  • 24,380
  • 11
  • 62
  • 94
0

Here is a Bash script that can be used as the basis for precompiling assets locally if there are any changes on master in (app|lib|vendor)/assets. If changes are present, the assets are precompiled locally and you would rsync with your production server. It is based on the premise that there will be a deploy on every commit to the production branch. Based on an article to speed up deployment 5 steps to cut your deploy time in half by Michael Trojanek.

It assumes use of rvm gemsets.

#!/bin/sh
cd /Users/my_user/Rails_Projects/myrailsapp
branch="master"
git checkout "$branch" &> /dev/null
curbranch=`git branch | grep \* | cut -d ' ' -f2`
if [ "$curbranch" == "$branch" ]; then
  echo "You are on the $curbranch branch"
  changes=`git diff --name-only HEAD~ HEAD | grep -E "(app|lib|vendor)/assets"`
  if [ -z "$changes" ]; then
    echo "No assets need to be precompiled"
  else
    echo "Here are files that have changes:  $changes"
    echo "Beginning precompile of Rails assets..."
    source /Users/my_user/.rvm/environments/ruby-2.4.1@Rails-4.2.8
    bundle exec rake assets:precompile RAILS_ENV=production
  fi
else
  echo "Unable to checkout "$branch" branch"
fi

Jet
  • 49
  • 1
  • 6