Download NetBeans nightly build for Mac

a unix shell command-line program

This shell script

It also renames the previously-installed NetBeans-nightly.app to a name that includes its date.

If someone would like to modify this script for Cygwin and/or Linux, I will post those versions as well. (Should be trivial to do.)

Example:

1 Z% /Applications/NetBeans/get-netbeans-nightly
[ Current nightly build is 201001170201 ]
[ Latest installed date is 200909011401 ]
[ Running subshell that uses 'sudo' -- may need YOUR password (not root's). ]
Password:
[ Start downloading netbeans-trunk-nightly-201001170201-src ]
[ Start downloading netbeans-trunk-nightly-201001170201 ]
[ Download the NetBeans Mac installer ]
[ Launch the installer; waiting for you to quit it ]
[ Disk /Volumes/NetBeans Dev 201001170201 ejected ]
[ Wait for add-in downloading and unzipping to finish ]
[ Start unzipping netbeans-trunk-nightly-201001170201 ]
[ Unzipped netbeans-trunk-nightly-201001170201.zip ]
[ Start unzipping netbeans-trunk-nightly-201001170201-src ]
[ Unzipped netbeans-trunk-nightly-201001170201-src.zip ]
[ Merge the downloaded add-ons into the installed app ]
[ Keep NetBeansDev200909011401.app ]
[ Rename to NetBeans-nightly.app and make symlink NetBeansDev201001170201.app ]
[ Empty the download dir ]
[ Removing old versions: NetBeansDev200808201401.app ]
[ Quitting ]
2 Z% /Applications/NetBeans/get-netbeans-nightly
[ Current nightly build is 201001170201 ]
[ Latest installed date is 200909011401 ]
[ Current nightly build is already installed ]
[ Quitting ]
3 Z% 

Here’s the get-netbeans-nightly program:

#!/bin/zsh

# http://Yost.com/computers/netbeans-nightly
# See bug list at end.
# See notice of copyright and license at end.


# REQUIREMENTS: Mac OS X 10.5.x
# Mac OS X 10.4 can't do "open -W".
# Anyone care to provide a workaround?

#-------------------------------------------------------------------------------
# Constants

commandName=${0##*/}
commandDir=${0%/*}
if [[ "$commandDir"    == "$commandName" ]] ; then commandDir=`pwd`             ; fi
if [[ ${commandDir##/} == "$commandDir"  ]] ; then commandDir=`pwd`/$commandDir ; fi
commandDir=$commandDir/

usageText="
Usage: $commandName

Get the latest Mac version of the nightly build of NetBeans Java SE edition 
plus some additional NetBeans packs, then install the app and merge in 
the additional NetBeans packs.

2008-07-26 Dave@Yost.com
"

# A parenthesized value assigned to a variable is
# a list of space-separated values.

# suffixes of zip files to download and unzip, for example:
#   netbeans-trunk-nightly-200808111401-src
suffixesUnzipped=(src)
# suffixes of zip files to download and unzip, for example:
#   netbeans-trunk-nightly-200808111401-javadoc
suffixesZipped=(javadoc)
# NetBeans packs to extract from netbeans-trunk-nightly-[datetime].zip
# For example: (cnd2 ruby2 php1 groovy1)
nbPacks=(cnd3)

# Local names
nbdir=/Applications/NetBeans
appname=NetBeans-nightly.app

# NetBeans names
urlBase=http://bits.netbeans.org/dev/nightly/latest
zipUrlBase=$urlBase/zip
baseBaseName=netbeans-trunk-nightly
appNamePrefix=NetBeansDev

# derived names
dldir=$nbdir/downloads
appNightly=$nbdir/$appname

numberOfOldVersionsToKeep=2

suffixes=($suffixesUnzipped $suffixesZipped)

#-------------------------------------------------------------------------------

main() {
  setopt ERR_RETURN
  getOpt $@
  setVars
  if [[ $isGetNetBeansSubshell != true ]] {
    reportVersions
    quitIfNotNew
    requireOpenMinusW
    execRootSubshell
  }
  doSubshellWork
}

getOpt() {
  if [[ $# != 0 ]] {
    echo2 $usageText
    exit 2
  }
}

setVars() {
  date=$(getBuildDate)
  baseName=${baseBaseName}-$date
  dlbase=$dldir/$baseName
  appNameAsInstalled="NetBeans Dev $date.app"
  appNameWithDate=$(rmSpacesFromName $appNameAsInstalled)
  appdest=$nbdir/$appNameWithDate/Contents/Resources/NetBeans
  jobs=()
}

reportVersions() {
  echo2 "[ Current nightly build is $date ]"
  cd $nbdir
  local current=$(echo ${appNamePrefix}*(N) | fmt -1 | tail -1)
  if [[ $current != "" ]] {
    local currentDate=$(extractDate $current)
    echo2 "[ Latest installed date is $currentDate ]"
  }
}

quitIfNotNew() {
  # If $appNameWithDate is not a symlink, the it could be incomplete.
  if [[ -h $appNameWithDate ]] {
    echo2 "[ Current nightly build is already installed ]"
    exit 1
  }
}

requireOpenMinusW() {
  # Mac OS X 10.4 can't do "open -W".
  # Anyone care to provide a workaround?
  if uname -r | grep '^9\.' > /dev/null ; then
    echo2 "[ Sorry, requires Mac OS X 10.5.x ]"
    exit 2
  else
    return 0
  fi
}

execRootSubshell() {
  export isGetNetBeansSubshell=true
  echo2 "[ Running subshell that uses 'sudo' -- may need YOUR password (not root's). ]"
  exec sudo -E zsh -c $commandDir$commandName
  echo2 "${commandName}: can't exec subshell"
  exit 2
}

doSubshellWork() {
  ensureIsRoot
  mkdir -p $dldir
  # Each function (recursively) from now on must do its own cd.
  startGettingAddIns
  rmOldStuffFromDownloadDir
  getAppInstaller
  runAppInstaller
  finishGettingAddIns
  mergeAddIns
  doAppRenames
  emptyDownloadDir
  removeExcessOldVersions
}

ensureIsRoot() {
  if [[ $UID != 0 ]] {
    echo "$commandName bug: subshell must run as root."
    exit 2
  }
}

startGettingAddIns() {
  if [[ $#nbPacks != 0 ]] {
    if [[ -e $dldir/$baseName ]] {
      mv -f $dldir/$baseName $dldir/tmp # Salvage gutted hierarchy.
    }
    getAddInUnzipped ""
  }
  for suffix in $suffixesUnzipped ; {
    getAddInUnzipped $suffix
  }
  for suffix in $suffixesZipped ; {
    getAddInZipped $suffix
  }
}

rmOldStuffFromDownloadDir() {
  #echo2 "[ Remove old stuff from the download dir ]"
  cd $dldir
  rmOldStuff
  rmOldStuff tmp/
}

getAppInstaller() {
  cd $dldir
  file=${baseName}-javase-macosx.dmg
  if [[ ! -r $file ]] {
    mkdir -p tmp
    cd tmp
    local what
    if [[ -e $file ]] then what="Resume downloading"
    else what=Download
    fi
    echo2 "[ $what the NetBeans Mac installer ]"
    doCurl $urlBase/bundles/${baseName}-javase-macosx.dmg
    mv $file ..
  } else {
    echo2 "[ Already downloaded the NetBeans Mac installer ]"
  }
}

runAppInstaller() {
  cd $nbdir
  rmbg $appNameAsInstalled
  open -W ${dlbase}-javase-macosx.dmg
  echo "[ Launch the installer; waiting for you to quit it ]"
  open -W /Volumes/NetBeans\ Dev\ $date/NetBeans\ Dev\ $date.mpkg
  echo "[ $(diskutil eject /Volumes/NetBeans\ Dev\ $date) ]" &
  if [[ ! -r $appNameAsInstalled ]] {
    echo2 "[ Install failed ]"
    # BUG! If you get here, background processes are not killed.
    exit 2
  }
  rmbg $appNameWithDate
  mv $appNameAsInstalled $appNameWithDate
}

finishGettingAddIns() {
  waitIfThereAreJobs "add-in downloading and unzipping"
}

mergeAddIns() {
  if [[ $#suffixes != 0 || $#nbPacks != 0 ]] {
    cd $nbdir
    echo2 "[ Merge the downloaded add-ons into the installed app ]"
    for suffix in $suffixesUnzipped ; {
      mve ${dlbase}-$suffix $appdest/$suffix
    }
    for suffix in $suffixesZipped ; {
      mve ${dlbase}-${suffix}.zip $appdest/${suffix}.zip
    }
    for x in $nbPacks ; {
      mve $dlbase/netbeans/$x $appdest/$x
    }
  }
}

doAppRenames() {
  cd $nbdir
  local oldAppNameWithDate=$(
    ls -l $nbdir \
    | sed -n "s,.* \(${appNamePrefix}.*\) ->.*,\1,p"
  )
  if [[ -n $oldAppNameWithDate ]] {
    echo2 "[ Keep $oldAppNameWithDate ]"
    rmbg              $oldAppNameWithDate
    mv $appname       $oldAppNameWithDate
  } else {
    # There was no symlink so we don't know what to name it.
    echo2 "[ Delete old $appname ]"
    rmbg $appname
  }

  echo2  "[ Rename to $appname and make symlink $appNameWithDate ]"
  mv $appNameWithDate $appname
  ln -s               $appname                  $appNameWithDate
}

emptyDownloadDir() {
  echo2 "[ Empty the download dir ]"
  cd $dldir
  rmDownloads
  rmDownloads tmp/
}

removeExcessOldVersions() {
  cd $nbdir
  local versions="$(echo ${appNamePrefix}*(N) | fmt -1 | grep -v $appNameWithDate)"
  numberToDelete=$(( $(echo "$versions" | wc -l) - $numberOfOldVersionsToKeep ))
  if [[ $numberToDelete > 0 ]] {
    local -a files
    files=($(
      echo "$versions" \
      | sed -n 1,${numberToDelete}p
    ))
    echo2 "[ Removing old versions: $files ]"   
    rmbg $files
  }
}

#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

getBuildDate() {
  curl -l $zipUrlBase/ 2> /dev/null \
  | sed -n 's,.*trunk-nightly-\([0-9]*\)\.zip.*,\1,p'
}

rmSpacesFromName() {
  echo $1 \
  | sed 's/ //g'
}

extractDate() {
  echo $1 | sed "s,$appNamePrefix\(.*\).app,\1,"
}

rmOldStuff() {
  toRemove=($(echo $1${baseBaseName}*(N) | fmt -1 | grep -v $date)) || true
  for x in $toRemove ; {
    #echo2 "[ Removing $x ]"   
    rmbg $x
  }
}

getAddInUnzipped() {
  local suffix
  if [[ -n $1 ]] {
    suffix=-$1
  } else {
    suffix=
  }
  (
    trap - INT QUIT TERM ZERR EXIT
    cd $dldir
    mkdir -p tmp
    local zipfile=$baseName$suffix.zip
    local dest=$baseName$suffix
    if needsUnzipping $dest ; then
      if [[ ! -r $zipfile ]] {
        cd tmp
        downloadAndUnzipFile $zipfile $dest
      } else {
        cd tmp
        local what
        if [[ -e $dest ]] then what=Resume
        else what=Start
        fi
        echo2  "[ $what unzipping previously-downloaded $dest ]"
        unzipFile $zipfile $dest
      }
    else
      echo2 "[ Already downloaded and unzipped $zipfile ]"
    fi
  ) &
  jobs+=$!
}

getAddInZipped() {
  local suffix
  if [[ -n $1 ]] {
    suffix=-$1
  } else {
    suffix=
  }
  (
    trap - INT QUIT TERM ZERR EXIT
    cd $dldir
    mkdir -p tmp
    local zipfile=$baseName$suffix.zip
    if [[ ! -r $zipfile ]] then
      cd tmp
      downloadAndUnzipFile $zipfile ''
    else
      echo2 "[ Already downloaded $zipfile ]"
    fi
  ) &
  jobs+=$!
}

needsUnzipping() {
  if [[ ! -e $1 ]] return 0
  if [[ $1 = $baseName ]] {
    dlbaseNeedsRefreshing
  } else {
    false
  }
}

dlbaseNeedsRefreshing() {
  for x in $nbPacks ; {
    if [[ ! -e $dlbase/netbeans/$x ]] {
      return 0
    }
  }
  return 1
}

waitIfThereAreJobs() {
  if [[ -n $jobstates ]] {
    echo2  "[ Wait for $1 to finish ]"
    wait
  }
}

downloadAndUnzipFile() {
  zipfile=$1
  dest=$2
  # If unzip would take stdin, we would
  #   curl | tee $zipfile | unzip --stdin $dest
  local what
  if [[ -e $zipfile ]] then what=Resume
  else what=Start
  fi
  if [[ -n $dest ]] then
    target=$dest
  else
    target=$zipfile
  fi
  echo2  "[ $what downloading $target ]"
  if downloadZipFile $zipfile ; then
    if [[ -n $dest ]] {
      echo2  "[ Start unzipping $dest ]"
      unzipFile $zipfile $dest
    }
  fi
}

downloadZipFile() {
  doCurl $zipUrlBase/$1
  if file $1 | grep -i 'zip:.*zip' > /dev/null ; then
    mv $1 ..
  else
    echo2 "**** ERROR problem with $1"
    return 1
  fi
}

unzipFile() {
  doUnzip $2
  mv $2 ..
  echo2 "[ Unzipped $1 ]"
}

doCurl() {
  local curlQuiet=-s
  curl --continue-at - -O $curlQuiet $1
  result=$?
  # echo "[ curl result $result $1 ]"
  return $result
}

doUnzip() {
  export zipname=$1
  mkdir -p $zipname
  (
    cd $zipname
    unzip -q -u -o ../../$zipname.zip
  )
}

mve() {
  if ! mv $1 $2 ; then
    echo2 "**** ERROR problem with $1"
  fi
}

rmDownloads() {
  toRemove=($1${baseBaseName}*(N)) || true
  if [[ $#toRemove != 0 ]] {
    for x in "$toRemove[@]" ; {
      #echo2 "[ Removing $x ]"   
      rmbg $x
    }
  }
}

rmbg() {
  for x in $@ ; {
    if [[ $x != "" ]] {
      if [[ -d $x ]] {
        mv $x  ${x}-DELETING
        rm -rf ${x}-DELETING &!
      } else {
        rm -f $x
      }
    }
  }
  return 0
}

echo2() {
  echo 1>&2 "$@"
}

trap '
  print 1>&2 "\n[ Killed ]"
' INT QUIT TERM

trap '
  print 1>&2 "\n[ Aborting ]"
  killJobs
  exit 2
' ZERR

trap '
  print 1>&2 "[ Quitting ]"
  killJobs
' EXIT

killJobs() {
  #print 1>&2 "[ killing $jobs ]"
  kill $jobs 2> /dev/null
}

#-------------------------------------------------------------------------------

main $@

exit

# BUGS

# If you quit the installer before it's done,
# the script exits, but background curl and/or unzip commands
# are not killed as they should be.

# Killing with ^C tries to clean up but fails in odd ways.
# If anyone wants to fix this, be my guest.

#===============================================================================
# Copyright 2008 Dave Yost <Dave@Yost.com>
# All rights reserved.
# This version is
#   get-netbeans-nightly 2.2 2010-01-17
# which at time of this publication can be found at:
#   http://Yost.com/computers/netbeans-nightly
# Redistribution and use in the form of source code or derivative data built 
# from the source code, with or without modification, are permitted provided 
# that the following conditions are met:
# 1. THE USER AGREES THAT THERE IS NO WARRANTY.
# 2. If and only if appropriate, the above phrase "This version is" must be 
#    followed by the phrase "a modified form of" or "extracted from" or 
#    "extracted and modified from".
# 3. Redistributions of source code must retain this notice intact.
# 4. Redistributions in the form of derivative data built from the source 
#    code must reproduce this notice intact in the documentation and/or other 
#    materials provided with the distribution, and each file in the derivative 
#    data must reproduce any Yost.com URI included in the original distribution.
# 5. Neither the name of Dave Yost nor the names of its contributors may be 
#    used to endorse or promote products derived from this software without 
#    specific prior written permission.
# 6. Written permission by the author is required for redistribution as part 
#    of a commercial product.
# This notice comprises all text from "Copyright" above through here.

http://Yost.com/computers/netbeans-nightly/index.html - this page
2008-07-26 Created
2008-08-14 1.7 fixes
2008-09-28 2.0 fixes
2008-10-14 2.1 fixes
2010-01-17 2.2 fixes for 10.6(.2): sudo -E, Mac OS X version check