Skip to content

Commit

Permalink
Build a Rake task for building Apple Packages
Browse files Browse the repository at this point in the history
The goal is to have our release managers build all Facter packages
within Rake, and so this commit adds a rake task (apple_package) that
will build a DMG-encapsulated package for OS X and put it into the
pkg/apple directory off of the Facter Root.

To accomplish this goal, a folder structure is created in
/tmp/facter/facter-#{version} that mirrors the structure needed for
Apple's Packagemaker CLI tool to build a package for OS X. Next, the
necessary files are copied from the Puppet source into the structure in
/tmp/facter/facter-#{version} and then packagemaker is run
(targeting that folder structure) to build an initial package. Hdiutil
then encapsulates that package into a DMG, and the file is finally
copied into the pkg/apple directory.
  • Loading branch information
Gary Larizza committed Jan 10, 2012
1 parent 8a7dfc9 commit 14cad7e
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 0 deletions.
166 changes: 166 additions & 0 deletions tasks/rake/apple.rake
@@ -0,0 +1,166 @@
# Title: Rake task to build Apple packages for Facter.
# Author: Gary Larizza
# Date: 12/5/2011
# Description: This task will create a DMG-encapsulated package that will
# install Facter on OS X systems. This happens by building
# a directory tree of files that will then be fed to the
# packagemaker binary (can be installed by installing the
# XCode Tools) which will create the .pkg file.
#
require 'fileutils'
require 'erb'
require 'find'
require 'pathname'

# Path to Binaries (Constants)
TAR = '/usr/bin/tar'
CP = '/bin/cp'
INSTALL = '/usr/bin/install'
DITTO = '/usr/bin/ditto'
PACKAGEMAKER = '/Developer/usr/bin/packagemaker'
SED = '/usr/bin/sed'

# Setup task to populate all the variables
task :setup do
@version = `git describe`.chomp
@title = "puppet-#{@version}"
@reverse_domain = 'com.puppetlabs.puppet'
@package_major_version = @version.split('.')[0]
@package_minor_version = @version.split('.')[1] +
@version.split('.')[2].split('-')[0].split('rc')[0]
@pm_restart = 'None'
@build_date = Time.new.strftime("%Y-%m-%dT%H:%M:%SZ")
end

# method: make_directory_tree
# description: This method sets up the directory structure that packagemaker
# needs to build a package. A prototype.plist file (holding
# package-specific options) is built from an ERB template located
# in the tasks/rake/templates directory.
def make_directory_tree
facter_tmp = '/tmp/facter'
@scratch = "#{facter_tmp}/#{@title}"
@working_tree = {
'scripts' => "#{@scratch}/scripts",
'resources' => "#{@scratch}/resources",
'working' => "#{@scratch}/root",
'payload' => "#{@scratch}/payload",
}
puts "Cleaning Tree: #{facter_tmp}"
FileUtils.rm_rf(facter_tmp)
@working_tree.each do |key,val|
puts "Creating: #{val}"
FileUtils.mkdir_p(val)
end
File.open("#{@scratch}/#{'prototype.plist'}", "w+") do |f|
f.write(ERB.new(File.read('tasks/rake/templates/prototype.plist.erb')).result())
end
end

# method: build_dmg
# description: This method builds a package from the directory structure in
# /tmp/facter and puts it in the
# /tmp/facter/facter-#{version}/payload directory. A DMG is
# created, using hdiutil, based on the contents of the
# /tmp/facter/facter-#{version}/payload directory. The resultant
# DMG is placed in the pkg/apple directory.
#
def build_dmg
# Local Variables
dmg_format_code = 'UDZO'
zlib_level = '9'
dmg_format_option = "-imagekey zlib-level=#{zlib_level}"
dmg_format = "#{dmg_format_code} #{dmg_format_option}"
dmg_file = "#{@title}.dmg"
package_file = "#{@title}.pkg"
pm_extra_args = '--verbose --no-recommend --no-relocate'
package_target_os = '10.4'

# Build .pkg file
system("sudo #{PACKAGEMAKER} --root #{@working_tree['working']} \
--id #{@reverse_domain} \
--filter DS_Store \
--target #{package_target_os} \
--title #{@title} \
--info #{@scratch}/prototype.plist \
--scripts #{@working_tree['scripts']} \
--resources #{@working_tree['resources']} \
--version #{@version} \
#{pm_extra_args} --out #{@working_tree['payload']}/#{package_file}")

# Build .dmg file
system("sudo hdiutil create -volname #{@title} \
-srcfolder #{@working_tree['payload']} \
-uid 99 \
-gid 99 \
-ov \
-format #{dmg_format} \
#{dmg_file}")

if File.directory?("#{Pathname.pwd}/pkg/apple")
FileUtils.mv("#{Pathname.pwd}/#{dmg_file}", "#{Pathname.pwd}/pkg/apple/#{dmg_file}")
puts "moved: #{dmg_file} has been moved to #{Pathname.pwd}/pkg/apple/#{dmg_file}"
else
FileUtils.mkdir_p("#{Pathname.pwd}/pkg/apple")
FileUtils.mv(dmg_file, "#{Pathname.pwd}/pkg/apple/#{dmg_file}")
puts "moved: #{dmg_file} has been moved to #{Pathname.pwd}/pkg/apple/#{dmg_file}"
end
end

# method: pack_facter_source
# description: This method copies the facter source into a directory
# structure in /tmp/facter/facter-#{version}/root mirroring the
# structure on the target system for which the package will be
# installed. Anything installed into /tmp/facter/root will be
# installed as the package's payload.
#
def pack_facter_source
work = "#{@working_tree['working']}"
facter_source = Pathname.pwd

# Make all necessary directories
directories = ["#{work}/usr/bin",
"#{work}/usr/share/doc/facter",
"#{work}/usr/lib/ruby/site_ruby/1.8/facter"]
FileUtils.mkdir_p(directories)

# Install necessary files
system("#{DITTO} #{facter_source}/bin/ #{work}/usr/bin")
system("#{DITTO} #{facter_source}/lib/ #{work}/usr/lib/ruby/site_ruby/1.8/")

# Setup a preflight script and replace variables in the files with
# the correct paths.
system("#{INSTALL} -o root -g wheel -m 644 #{facter_source}/conf/osx/preflight #{@working_tree['scripts']}")
system("#{SED} -i '' \"s\#{SITELIBDIR}\#/usr/lib/ruby/site_ruby/1.8\#g\" #{@working_tree['scripts']}/preflight")
system("#{SED} -i '' \"s\#{BINDIR}\#/usr/bin\#g\" #{@working_tree['scripts']}/preflight")

# Install documentation (matching for files with capital letters)
Dir.foreach("#{facter_source}") do |file|
system("#{INSTALL} -o root -g wheel -m 644 #{facter_source}/#{file} #{work}/usr/share/doc/facter") if file =~ /^[A-Z][A-Z]/
end

# Set Permissions
executable_directories = [ "#{work}/usr/bin", ]
FileUtils.chmod_R(0755, executable_directories)
FileUtils.chown_R('root', 'wheel', directories)
FileUtils.chmod_R(0644, "#{work}/usr/lib/ruby/site_ruby/1.8/")
FileUtils.chown_R('root', 'wheel', "#{work}/usr/lib/ruby/site_ruby/1.8/")
Find.find("#{work}/usr/lib/ruby/site_ruby/1.8/") do |dir|
FileUtils.chmod(0755, dir) if File.directory?(dir)
end
end

namespace :package do
desc "Task for building an Apple Package"
task :apple => [:setup] do
# Test for Root and Packagemaker binary
raise "Please run rake as root to build Apple Packages" unless Process.uid == 0
raise "Packagemaker must be installed. Please install XCode Tools" unless \
File.exists?('/Developer/usr/bin/packagemaker')

make_directory_tree
pack_facter_source
build_dmg
FileUtils.chmod_R(0775, "#{Pathname.pwd}/pkg")
end
end
38 changes: 38 additions & 0 deletions tasks/rake/templates/prototype.plist.erb
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string><%= @title %></string>
<key>CFBundleShortVersionString</key>
<string><%= @version %></string>
<key>IFMajorVersion</key>
<integer><%= @package_major_version %></integer>
<key>IFMinorVersion</key>
<integer><%= @package_minor_version %></integer>
<key>IFPkgBuildDate</key>
<date><%= @build_date %></date>
<key>IFPkgFlagAllowBackRev</key>
<false/>
<key>IFPkgFlagAuthorizationAction</key>
<string>RootAuthorization</string>
<key>IFPkgFlagDefaultLocation</key>
<string>/</string>
<key>IFPkgFlagFollowLinks</key>
<true/>
<key>IFPkgFlagInstallFat</key>
<false/>
<key>IFPkgFlagIsRequired</key>
<false/>
<key>IFPkgFlagOverwritePermissions</key>
<false/>
<key>IFPkgFlagRelocatable</key>
<false/>
<key>IFPkgFlagRestartAction</key>
<string><%= @pm_restart %></string>
<key>IFPkgFlagRootVolumeOnly</key>
<true/>
<key>IFPkgFlagUpdateInstalledLanguages</key>
<false/>
</dict>
</plist>

0 comments on commit 14cad7e

Please sign in to comment.