From 14cad7e4c8008f24a669b178f1669c45529d4d86 Mon Sep 17 00:00:00 2001 From: Gary Larizza Date: Tue, 10 Jan 2012 14:57:40 -0800 Subject: [PATCH] Build a Rake task for building Apple Packages 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. --- tasks/rake/apple.rake | 166 +++++++++++++++++++++++ tasks/rake/templates/prototype.plist.erb | 38 ++++++ 2 files changed, 204 insertions(+) create mode 100644 tasks/rake/apple.rake create mode 100644 tasks/rake/templates/prototype.plist.erb diff --git a/tasks/rake/apple.rake b/tasks/rake/apple.rake new file mode 100644 index 0000000000..0e6e9eb6a6 --- /dev/null +++ b/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 diff --git a/tasks/rake/templates/prototype.plist.erb b/tasks/rake/templates/prototype.plist.erb new file mode 100644 index 0000000000..e56659a6bc --- /dev/null +++ b/tasks/rake/templates/prototype.plist.erb @@ -0,0 +1,38 @@ + + + + + CFBundleIdentifier + <%= @title %> + CFBundleShortVersionString + <%= @version %> + IFMajorVersion + <%= @package_major_version %> + IFMinorVersion + <%= @package_minor_version %> + IFPkgBuildDate + <%= @build_date %> + IFPkgFlagAllowBackRev + + IFPkgFlagAuthorizationAction + RootAuthorization + IFPkgFlagDefaultLocation + / + IFPkgFlagFollowLinks + + IFPkgFlagInstallFat + + IFPkgFlagIsRequired + + IFPkgFlagOverwritePermissions + + IFPkgFlagRelocatable + + IFPkgFlagRestartAction + <%= @pm_restart %> + IFPkgFlagRootVolumeOnly + + IFPkgFlagUpdateInstalledLanguages + + +