Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/versions'
Browse files Browse the repository at this point in the history
  • Loading branch information
hone committed Feb 22, 2012
2 parents 4a8eff9 + 1d061d0 commit a5ee40a
Show file tree
Hide file tree
Showing 14 changed files with 1,756 additions and 36 deletions.
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,44 @@ Example usage:

The buildpack will detect your app as Node.js if it has the file `package.json` in the root. It will use NPM to install your dependencies, and vendors a version of the Node.js runtime into your slug. The `node_modules` directory will be cached between builds to allow for faster NPM install time.

Node.js and npm versions
------------------------

You can specify the versions of Node.js and npm your application requires using `package.json`

{
"name": "myapp",
"engines": {
"node": ">=0.4.7 <0.7.0",
"npm": ">=1.0.0"
}
}

To list the available versions of Node.js and npm, see these manifests:

http://heroku-buildpack-nodejs.s3.amazonaws.com/manifest.nodejs
http://heroku-buildpack-nodejs.s3.amazonaws.com/manifest.npm

Hacking
-------

To use this buildpack, fork it on Github. Push up changes to your fork, then create a test app with `--buildpack <your-github-url>` and push to it.

To change the vendored binaries for Node.js, NPM, and SCons, use the helper scripts in the `support/` subdirectory. You'll need an S3-enabled AWS account and a bucket to store your binaries in.

For example, you can change the vendored version of Node.js to v0.5.8.
For example, you can change the default version of Node.js to v0.6.7.

First you'll need to build a Heroku-compatible version of Node.js:

$ export AWS_ID=xxx AWS_SECRET=yyy S3_BUCKET=zzz
$ s3 create $S3_BUCKET
$ support/package_node 0.5.8
$ support/package_nodejs 0.6.7

Open `bin/compile` in your editor, and change the following lines:

NODE_VERSION="0.5.8"

DEFAULT_NODE_VERSION="0.6.7"
S3_BUCKET=zzz

Commit and push the changes to your buildpack to your Github fork, then push your sample app to Heroku to test. You should see:

-----> Vendoring node 0.5.8
-----> Vendoring node 0.6.7
129 changes: 104 additions & 25 deletions bin/compile
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
#!/usr/bin/env bash
# bin/compile <build-dir> <cache-dir>

mktmpdir() {
# fail fast
set -e

# debug
# set -x

# clean up leaking environment
unset GIT_DIR

# config
SCONS_VERSION="1.2.0"
S3_BUCKET="heroku-buildpack-nodejs"

# parse and derive params
BUILD_DIR=$1
CACHE_DIR=$2
LP_DIR=`cd $(dirname $0); cd ..; pwd`
CACHE_STORE_DIR="$CACHE_DIR/node_modules/$NPM_VERSION"
CACHE_TARGET_DIR="$BUILD_DIR/node_modules"

function error() {
echo " ! $*"
exit 1
}

function mktmpdir() {
dir=$(mktemp -t node-$1-XXXX)
rm -rf $dir
mkdir -p $dir
Expand All @@ -16,8 +41,10 @@ function indent() {
esac
}

run_npm() {
function run_npm() {
command="$1"

cd $BUILD_DIR
HOME="$BUILD_DIR" $VENDORED_NODE/bin/node $VENDORED_NPM/cli.js $command 2>&1 | indent

if [ "${PIPESTATUS[*]}" != "0 0" ]; then
Expand All @@ -26,21 +53,76 @@ run_npm() {
fi
}

# clean up leaking environment
unset GIT_DIR
function manifest_versions() {
curl "http://${S3_BUCKET}.s3.amazonaws.com/manifest.${1}" -s -o - | tr -s '\n' ' '
}

# config
NODE_VERSION="0.4.7"
NPM_VERSION="1.0.94"
SCONS_VERSION="1.2.0"
S3_BUCKET="heroku-buildpack-nodejs"
function resolve_version() {
available_versions="$1"
requested_version="$2"
default_version="$3"

if [ "$2" == "" ]; then
echo $3
else
args=""
for version in $available_versions; do args="${args} -v \"${version}\""; done
for version in $requested_version; do args="${args} -r \"${version}\""; done
eval $bootstrap_node/bin/node $LP_DIR/vendor/node-semver/bin/semver ${args} | tail -n1
fi
}

# parse and derive params
BUILD_DIR=$1
CACHE_DIR=$2
LP_DIR=`cd $(dirname $0); cd ..; pwd`
CACHE_STORE_DIR="$CACHE_DIR/node_modules/$NPM_VERSION"
CACHE_TARGET_DIR="$BUILD_DIR/node_modules"
function package_engine_version() {
version=$(cat $BUILD_DIR/package.json | $bootstrap_node/bin/node $LP_DIR/vendor/json/json engines.$1 2>/dev/null)
if [ $? == 0 ]; then
echo $version
fi
}

function package_resolve_version() {
engine="$1"
resolved_version=$(resolve_version "${engine_versions[$engine]}" "${engine_requests[$engine]}" "${engine_defaults[$engine]}")

if [ "${resolved_version}" == "" ]; then
error "Requested engine $engine version ${engine_requests[$engine]} does not match available versions: ${engine_versions[$engine]}"
else
echo $resolved_version
fi
}

function package_download() {
engine="$1"
version="$2"
location="$3"

mkdir -p $location
package="http://${S3_BUCKET}.s3.amazonaws.com/$engine-$version.tgz"
curl $package -s -o - | tar xzf - -C $location
}

bootstrap_node=$(mktmpdir bootstrap_node)
package_download "nodejs" "0.4.7" $bootstrap_node

# make some associative arrays
declare -A engine_versions
declare -A engine_defaults
declare -A engine_requests

engine_defaults["node"]="0.4.7"
engine_defaults["npm"]="1.0.94"

engine_versions["node"]=$(manifest_versions "nodejs")
engine_requests["node"]=$(package_engine_version "node")

engine_versions["npm"]=$(manifest_versions "npm")
engine_requests["npm"]=$(package_engine_version "npm")

echo "-----> Resolving engine versions"
NODE_VERSION=$(package_resolve_version "node")
echo "Using Node.js version: ${NODE_VERSION}" | indent

NPM_VERSION=$(package_resolve_version "npm")
echo "Using npm version: ${NPM_VERSION}" | indent

# s3 packages
NODE_PACKAGE="http://${S3_BUCKET}.s3.amazonaws.com/nodejs-${NODE_VERSION}.tgz"
Expand All @@ -54,13 +136,13 @@ VENDORED_SCONS="$(mktmpdir scons)"

# download and unpack packages
echo "-----> Fetching Node.js binaries"
mkdir -p $VENDORED_NODE && curl $NODE_PACKAGE -s -o - | tar xzf - -C $VENDORED_NODE
mkdir -p $VENDORED_NPM && curl $NPM_PACKAGE -s -o - | tar xzf - -C $VENDORED_NPM
mkdir -p $VENDORED_SCONS && curl $SCONS_PACKAGE -s -o - | tar xzf - -C $VENDORED_SCONS
package_download "nodejs" "${NODE_VERSION}" "${VENDORED_NODE}"
package_download "npm" "${NPM_VERSION}" "${VENDORED_NPM}"
package_download "scons" "${SCONS_VERSION}" "${VENDORED_SCONS}"

# vendor node into the slug
PATH="$BUILD_DIR/bin:$PATH"
echo "-----> Vendoring node $NODE_VERSION"
echo "-----> Vendoring node into slug"
mkdir -p "$BUILD_DIR/bin"
cp "$VENDORED_NODE/bin/node" "$BUILD_DIR/bin/node"

Expand Down Expand Up @@ -93,12 +175,9 @@ if [ -d $CACHE_STORE_DIR ]; then
fi

# install dependencies with npm
echo "-----> Installing dependencies with npm $NPM_VERSION"

cd $BUILD_DIR
run_npm install
run_npm rebuild

echo "-----> Installing dependencies with npm"
run_npm "install"
run_npm "rebuild"
echo "Dependencies installed" | indent

# repack cache with new assets
Expand Down
11 changes: 9 additions & 2 deletions support/aws/s3
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,17 @@ s3_curl() {
# $2 = remote bucket.
# $3 = remote name
# $4 = local name.
# $5 = mime type
local bucket remote date sig md5 arg inout headers
# header handling is kinda fugly, but it works.
bucket="${2:+/${2}}/" # slashify the bucket
remote="$(urlenc "${3}")" # if you don't, strange things may happen.
stdopts="--connect-timeout 10 --fail --silent"
mime="${5}"
[[ $CURL_S3_DEBUG == true ]] && stdopts="${stdopts} --show-error --fail"
case "${1}" in
GET) arg="-o" inout="${4:--}" # stdout if no $4
headers[${#headers[@]}]="x-amz-acl: public-read"
;;
PUT) [[ ${2} ]] || die "PUT can has bucket?"
if [[ ! ${3} ]]; then
Expand All @@ -135,6 +138,9 @@ s3_curl() {
arg="-T" inout="${4}"
headers[${#headers[@]}]="x-amz-acl: public-read"
headers[${#headers[@]}]="Expect: 100-continue"
if [ "$mime" != "" ]; then
headers[${#headers[@]}]="Content-Type: $mime"
fi
else
die "Cannot write non-existing file ${4}"
fi
Expand All @@ -145,7 +151,7 @@ s3_curl() {
*) die "Unknown verb ${1}. It probably would not have worked anyways." ;;
esac
date="$(TZ=UTC date '+%a, %e %b %Y %H:%M:%S %z')"
sig=$(s3_signature_string ${1} "${date}" "${bucket}" "${remote}" "${md5}" "" "x-amz-acl:public-read")
sig=$(s3_signature_string ${1} "${date}" "${bucket}" "${remote}" "${md5}" "${mime}" "x-amz-acl:public-read")

headers[${#headers[@]}]="Authorization: AWS ${AWS_ID}:${sig}"
headers[${#headers[@]}]="Date: ${date}"
Expand All @@ -159,7 +165,8 @@ s3_put() {
# $1 = remote bucket to put it into
# $2 = remote name to put
# $3 = file to put. This must be present if $2 is.
s3_curl PUT "${1}" "${2}" "${3:-${2}}"
# $4 = mime type
s3_curl PUT "${1}" "${2}" "${3:-${2}}" "${4}"
return $?
}

Expand Down
44 changes: 44 additions & 0 deletions support/manifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/sh

set -e

manifest_type="$1"

if [ "$manifest_type" == "" ]; then
echo "usage: $0 <nodejs|npm>"
exit 1
fi

if [ "$AWS_ID" == "" ]; then
echo "must set AWS_ID"
exit 1
fi

if [ "$AWS_SECRET" == "" ]; then
echo "must set AWS_SECRET"
exit 1
fi

if [ "$S3_BUCKET" == "" ]; then
echo "must set S3_BUCKET"
exit 1
fi

basedir="$( cd -P "$( dirname "$0" )" && pwd )"

# make a temp directory
tempdir="$( mktemp -t node_XXXX )"
rm -rf $tempdir
mkdir -p $tempdir
pushd $tempdir

# generate manifest
$basedir/aws/s3 ls $S3_BUCKET \
| grep "^${manifest_type}" \
| sed -e "s/${manifest_type}-\([0-9.]*\)\\.tgz/\\1/" \
| awk 'BEGIN {FS="."} {printf("%03d.%03d.%03d %s\n",$1,$2,$3,$0)}' | sort -r | cut -d" " -f2 \
> manifest.${manifest_type}

# upload manifest to s3
$basedir/aws/s3 put $S3_BUCKET \
manifest.${manifest_type} "" "text/plain"
11 changes: 7 additions & 4 deletions support/package_node → support/package_nodejs
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ basedir="$( cd -P "$( dirname "$0" )" && pwd )"
tempdir="$( mktemp -t node_XXXX )"
rm -rf $tempdir
mkdir -p $tempdir
pushd $tempdir
cd $tempdir

# download and extract node
curl http://nodejs.org/dist/node-v${node_version}.tar.gz -o node.tgz
curl http://nodejs.org/dist/v${node_version}/node-v${node_version}.tar.gz -o node.tgz
tar xzvf node.tgz

# go into node dir
pushd node-v${node_version}
cd node-v${node_version}

# build and package nodejs for heroku
vulcan build -v -o $tempdir/node-${node_version}.tgz
Expand All @@ -47,7 +47,7 @@ $basedir/aws/s3 put $S3_BUCKET \
nodejs-${node_version}.tgz $tempdir/node-${node_version}.tgz

# go into scons
pushd tools/scons
cd tools/scons

# package scons
scons_version=$(ls | grep "scons-local" | cut -d- -f3)
Expand All @@ -56,3 +56,6 @@ tar czvf $tempdir/scons-${scons_version}.tgz *
# upload scons to s3
$basedir/aws/s3 put $S3_BUCKET \
scons-${scons_version}.tgz $tempdir/scons-${scons_version}.tgz

# generate manifest
$basedir/manifest nodejs
3 changes: 3 additions & 0 deletions support/package_npm
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ tar czvf $tempdir/npm-${npm_version}.tgz *
# upload npm to s3
$basedir/aws/s3 put $S3_BUCKET \
npm-${npm_version}.tgz $tempdir/npm-${npm_version}.tgz

# generate manifest
$basedir/manifest npm
2 changes: 2 additions & 0 deletions vendor/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
json: https://github.com/trentm/json
node-semver: http://github.com/isaacs/node-semver
Loading

0 comments on commit a5ee40a

Please sign in to comment.