Skip to content

Instantly share code, notes, and snippets.

@ErinCall
Created January 8, 2012 07:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ErinCall/1577622 to your computer and use it in GitHub Desktop.
Save ErinCall/1577622 to your computer and use it in GitHub Desktop.
Annotated the rvm install script https://raw.github.com/gist/323731
#!/usr/bin/ruby
# This script installs to /usr/local only. To install elsewhere you can just
# untar https://github.com/mxcl/homebrew/tarball/master anywhere you like.
#the first thing it does is define a bunch of methods to make things easier further on.
#That makes for sorta uncomfortable reading, since sometimes you can see *what* it's doing,
#but not *why* it's doing it. If you're mystified as to the purpose of something, go look
#at how it's used and see if that illuminates matters.
#Terminals use numbered control codes to indicate colors. It is inconvenient to try
#and remember what control code numbers correspond to what colors, so this code
#injects color name functions into the "Tty" module so later code can just say,
# for example, "blue" rather than "\033[1;34m".
module Tty extend self
def blue; bold 34; end
def white; bold 39; end
def red; underline 31; end
def reset; escape 0; end
def bold n; escape "1;#{n}" end
def underline n; escape "4;#{n}" end
def escape n; "\033[#{n}m" if STDOUT.tty? end
end
#This injects an instance method called shell_s into the Array module.
#It looks like it's a simple shell-escape.
#The injected method will be available on all array objects
class Array
def shell_s
#create a local variable "cp" that is a copy of the object (so the next steps don't change the original object)
cp = dup
#create a local variable "first" that is the first element of the array,
#and remove that element from the array
first = cp.shift
#there's a few things going on here:
#map applies a block to each member of the array and returns the transformed array.
###the code inside the {} will be applied to each element of the array in turn
#arg.gsub ", " "\\ " backslash-escapes spaces
#unshift(first) puts "first" back on the front of the list without escaping it.
#finally, * " " takes all the elements of the array and joins them together
#with a space between each one.
cp.map{ |arg| arg.gsub " ", "\\ " }.unshift(first) * " "
end
end
#this just prints to the screen, at "o hai" logging level
#now we start seeing those injected methods come up
# This construct: " #{foo} " inserts the variable foo into a string.
def ohai *args
#puts prints to the screen. This creates a string that has a blue "==>" followed by the
#arguments passed into this method
puts "#{Tty.blue}==>#{Tty.white} #{args.shell_s}#{Tty.reset}"
end
#Same as ohai, but a warning logging level
def warn warning
puts "#{Tty.red}Warning#{Tty.reset}: #{warning.chomp}"
end
#This line sorta executes right-to-left:
#unless Kernel.system *args returns true, it'll print an error message and exit
#Kernel.system is a method that executes its arguments as though you had typed
#them at the terminal
def system *args
abort "Failed during: #{args.shell_s}" unless Kernel.system *args
end
#this function is a wrapper around the 'sudo' command
#it has an interesting construct: notice that it assigns the result of an if...else statement
#to the variable "args"
def sudo *args
args = if args.length > 1
args.unshift "/usr/bin/sudo"
else
"/usr/bin/sudo #{args.first}"
end
#print to the screen what it's about to do
ohai *args
#run the command
system *args
end
#this will read a single key of input from the terminal, without printing that key to the screen
def getc
#this line tells the terminal not to print letters to the screen when you hit a key
system "/bin/stty raw -echo"
#it looks like the method for getting a single character from STDIN changed in version 1.8.7
#either way, it returns a single byte of input from the user
if RUBY_VERSION >= '1.8.7'
STDIN.getbyte
else
STDIN.getc
end
#the code in the "ensure" block will run even if there was an error in the main part of the function
#it returns the terminal to its normal state, so an error won't leave the user's terminal in noecho mode
ensure
system "/bin/stty -raw echo"
end
#there's too much going on here for me to feel motivated about breaking it down in detail,
#but it's looking anything that matches all these conditions:
#it's in /usr/local/lib/ and is called *.dylib
#it's a file (not a directory or device)
#it's not a symlink
#the `file` utility reports that it's a shared library
#'badlibs' is not a play on 'madlibs' as I first thought. You can see below
#that it's looking for "bad libraries"
def badlibs
@badlibs ||= begin
Dir['/usr/local/lib/*.dylib'].select do |dylib|
ENV['dylib'] = dylib
File.file? dylib and not File.symlink? dylib and `/usr/bin/file "$dylib"` =~ /shared library/
end
end
end
#this runs `/usr/bin/sw_vers -productVersion` to get the current macos version,
#applies a regex to it to strip out only the major version, gets the first capture group,
#and converts that to a floating-point number
#note that the stuff in backticks `` will be executed in a shell and the output of that command
#will return as a string. Those two little ticks are doing quite a lot
def macos_version
@macos_version ||= /(10\.\d+)(\.\d+)?/.match(`/usr/bin/sw_vers -productVersion`).captures.first.to_f
end
#Now it's done setting up convenience methods, and the actual doin' of stuff begins
#this changes the current working directory to "/usr/". The comment below is in the original gist:
# The block form of Dir.chdir fails later if Dir.CWD doesn't exist which I
# guess is fair enough. Also sudo prints a warning message for no good reason
Dir.chdir "/usr"
####################################################################### script
#here're a bunch of sanity checks to make sure everything is ok to go forward
#note that it's some more "do the clause on the left if the clause on the right is true" constructions
abort "MacOS too old, see: https://gist.github.com/1144389" if macos_version < 10.5
abort "/usr/local/.git already exists!" unless Dir["/usr/local/.git/*"].empty?
abort "Don't run this as root!" if Process.uid == 0
#<<-EOABORT signals ruby that there's a multi-line string coming.
#The string doesn't start until the next line and it doesn't end until there's
#a line with nothing on it but another EOABORT. You could use any identifier;
#there's no special meaning to "EOABORT". This is really nice when you want to
#include quote marks, for example, since you'd have to escape them otherwise.
abort <<-EOABORT unless `groups`.split.include? "admin"
This script requires the user #{ENV['USER']} to be an Administrator. If this
sucks for you then you can install Homebrew in your home directory or however
you please; please refer to the website. If you still want to use this script
set your user to be an Administrator in System Preferences or `su'.
EOABORT
#print some info to the screen
#note that the first line uses the "ohai" logging function defined above,
#while subsequent lines use a raw "puts" to print without the terminal colors
ohai "This script will install:"
puts "/usr/local/bin/brew"
puts "/usr/local/Library/Formula/..."
puts "/usr/local/Library/Homebrew/..."
#%w() is some "syntactic sugar" for creating an array of strings. it's the same as
#[ '.', 'bin', 'etc', 'include', 'lib', .......] but a lot easier to type
chmods = %w( . bin etc include lib lib/pkgconfig Library sbin share var var/log share/locale share/man
share/man/man1 share/man/man2 share/man/man3 share/man/man4
share/man/man5 share/man/man6 share/man/man7 share/man/man8
share/info share/doc share/aclocal ).
#here's another map. This one puts "/usr/local/" on the front of every directory name in the list above.
map{ |d| "/usr/local/#{d}" }.
#here's a select, which applies the stuff in {} just like map. Instead of transforming the list the way
#map does, though, it filters out the list elements for which the code in {} returns false.
#so this takes the list of paths and returns just the ones that are directories and aren't writeable
select{ |d| File.directory? d and not File.writable? d }
#this creates a new "chgrps" array that is a subset of the "chmods" array.
#reject works just like select, but opposite.
#this rejects any path that's already owned by the user's group
chgrps = chmods.reject{ |d| File.stat(d).grpowned? }
#this section will only execute if there are elements in the "chmods" array
unless chmods.empty?
ohai "The following directories will be made group writable:"
puts *chmods
end
#this section will only execute if there are elements in the "chgrps" array
unless chgrps.empty?
ohai "The following directories will have their group set to #{Tty.underline 39}admin#{Tty.reset}:"
puts *chgrps
end
#this checks to see if STDIN is a terminal. A thorough explanation of file-handles and ttys is outside the scope
#of this annotation but basically it'll only execute the stuff below if you're running this script from the prompt.
#otherwise (if it's running from inside an automated system-setup script, say), there isn't anyone around to press enter
if STDIN.tty?
#print a blank line
puts
puts "Press enter to continue"
#exit unless the user pressed enter (carriage return is ascii 13)
abort unless getc == 13
end
#remember earlier, when it built up that big list of directories inside /usr/local/ ?
#now it checks to see if /usr/local/ even exists
if File.directory? "/usr/local"
#call that sudo function defined earlier on, applying the chmod command to all of the
#paths in the chmods array
sudo "/bin/chmod", "g+rwx", *chmods unless chmods.empty?
#same as above, except with chgrp
sudo "/usr/bin/chgrp", "admin", *chgrps unless chgrps.empty?
else
sudo "/bin/mkdir /usr/local"
sudo "/bin/chmod g+rwx /usr/local"
sudo "/usr/bin/chgrp admin /usr/local"
end
#the command in this block will be run with a current working directory of /usr/local,
#instead of the one set above
#after the 'end' line below, further commands will run in the previous working directory
Dir.chdir "/usr/local" do
ohai "Downloading and Installing Homebrew..."
#these comments are in the original gist
# -m to stop tar erroring out if it can't modify the mtime for root owned directories
# pipefail to cause the exit status from curl to propogate if it fails
# we use -k because OS X curl has a bunch of bad SSL certificates
# you may want to remove the -k flag from your fork!
# ok here is where it actually installs perlbrew. It downloads the tarball from github and untars it in /usr/local/
system "/bin/bash -o pipefail -c '/usr/bin/curl -skSfL https://github.com/mxcl/homebrew/tarball/master | /usr/bin/tar xz -m --strip 1'"
end
#These are just some more checks to make sure everything is hunky-dory
#ENV['PATH'] is the PATH environment variable. like `echo $PATH`
#.split(':') takes the colon-separated PATH string and splits it up into an array of strings (with no colons in them)
#.include? '/usr/local/bin' checks to see if that array has '/usr/local/bin/' in it
warn "/usr/local/bin is not in your PATH." unless ENV['PATH'].split(':').include? '/usr/local/bin'
warn "Now install Xcode: http://developer.apple.com/technologies/xcode.html" unless Kernel.system "/usr/bin/which -s gcc"
#if there's anything in badlibs, warn about them
#I don't know what makes these libs bad
unless badlibs.empty?
warn "The following *evil* dylibs exist in /usr/local/lib"
puts "They may break builds or worse. You should consider deleting them:"
puts *badlibs
end
ohai "Installation successful!"
puts "Now type: brew help"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment