Created
January 8, 2012 07:45
-
-
Save ErinCall/1577622 to your computer and use it in GitHub Desktop.
Annotated the rvm install script https://raw.github.com/gist/323731
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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