Merge pull request #332 from timsutton/4df9617b8a3e71ac82b4dadb8cad28cded66159f
OS X package support
This commit is contained in:
commit
78e5d168e3
|
|
@ -5,6 +5,7 @@
|
|||
build-*/*
|
||||
fpm.wiki
|
||||
*.gem
|
||||
*.pkg
|
||||
|
||||
# python
|
||||
*.pyc
|
||||
|
|
@ -16,3 +17,6 @@ fpm.wiki
|
|||
coverage
|
||||
test/tmp
|
||||
Gemfile.lock
|
||||
|
||||
# OS X
|
||||
.DS_Store
|
||||
|
|
|
|||
|
|
@ -6,3 +6,4 @@ require "fpm/package/gem"
|
|||
require "fpm/package/deb"
|
||||
require "fpm/package/rpm"
|
||||
require "fpm/package/python"
|
||||
require "fpm/package/osxpkg"
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
require "fpm/package"
|
||||
require "fpm/util"
|
||||
require "fileutils"
|
||||
require "fpm/package/dir"
|
||||
require 'tempfile' # stdlib
|
||||
require 'pathname' # stdlib
|
||||
require 'rexml/document' # stdlib
|
||||
|
||||
# Use an OS X pkg built with pkgbuild.
|
||||
#
|
||||
# Supports input and output. Requires pkgbuild and (for input) pkgutil, part of a
|
||||
# standard OS X install in 10.7 and higher.
|
||||
class FPM::Package::OSXpkg < FPM::Package
|
||||
|
||||
# Map of what scripts are named.
|
||||
SCRIPT_MAP = {
|
||||
:before_install => "preinstall",
|
||||
:after_install => "postinstall",
|
||||
} unless defined?(SCRIPT_MAP)
|
||||
|
||||
POSTINSTALL_ACTIONS = [ "logout", "restart", "shutdown" ]
|
||||
OWNERSHIP_OPTIONS = ["recommended", "preserve", "preserve-other"]
|
||||
|
||||
option "--identifier-prefix", "IDENTIFIER_PREFIX",
|
||||
"Reverse domain prefix prepended to package identifier, " \
|
||||
"ie. 'org.great.my'. If this is omitted, the identifer " \
|
||||
"will be the package name."
|
||||
option "--payload-free", :flag, "Define no payload, assumes use of script options.",
|
||||
:default => false
|
||||
option "--ownership", "OWNERSHIP",
|
||||
"--ownership option passed to pkgbuild. Defaults to 'recommended'. " \
|
||||
"See pkgbuild(1).", :default => 'recommended' do |value|
|
||||
if !OWNERSHIP_OPTIONS.include?(value)
|
||||
raise ArgumentError, "osxpkg-ownership value of '#{value}' is invalid. " \
|
||||
"Must be one of #{OWNERSHIP_OPTIONS.join(", ")}"
|
||||
end
|
||||
value
|
||||
end
|
||||
|
||||
option "--postinstall-action", "POSTINSTALL_ACTION",
|
||||
"Post-install action provided in package metadata. " \
|
||||
"Optionally one of '#{POSTINSTALL_ACTIONS.join("', '")}'." do |value|
|
||||
if !POSTINSTALL_ACTIONS.include?(value)
|
||||
raise ArgumentError, "osxpkg-postinstall-action value of '#{value}' is invalid. " \
|
||||
"Must be one of #{POSTINSTALL_ACTIONS.join(", ")}"
|
||||
end
|
||||
value
|
||||
end
|
||||
|
||||
dont_obsolete_paths = []
|
||||
option "--dont-obsolete", "DONT_OBSOLETE_PATH",
|
||||
"A file path for which to 'dont-obsolete' in the built PackageInfo. " \
|
||||
"Can be specified multiple times." do |path|
|
||||
dont_obsolete_paths << path
|
||||
end
|
||||
|
||||
private
|
||||
# return the identifier by prepending the reverse-domain prefix
|
||||
# to the package name, else return just the name
|
||||
def identifier
|
||||
identifier = name.dup
|
||||
if self.attributes[:osxpkg_identifier_prefix]
|
||||
identifier.insert(0, "#{self.attributes[:osxpkg_identifier_prefix]}.")
|
||||
end
|
||||
identifier
|
||||
end # def identifier
|
||||
|
||||
# scripts_path and write_scripts cribbed from deb.rb
|
||||
def scripts_path(path=nil)
|
||||
@scripts_path ||= build_path("Scripts")
|
||||
FileUtils.mkdir(@scripts_path) if !File.directory?(@scripts_path)
|
||||
|
||||
if path.nil?
|
||||
return @scripts_path
|
||||
else
|
||||
return File.join(@scripts_path, path)
|
||||
end
|
||||
end # def scripts_path
|
||||
|
||||
def write_scripts
|
||||
SCRIPT_MAP.each do |scriptname, filename|
|
||||
next unless script?(scriptname)
|
||||
|
||||
with(scripts_path(filename)) do |pkgscript|
|
||||
@logger.info("Writing pkg script", :source => filename, :target => pkgscript)
|
||||
File.write(pkgscript, script(scriptname))
|
||||
# scripts are required to be executable
|
||||
File.chmod(0755, pkgscript)
|
||||
end
|
||||
end
|
||||
end # def write_scripts
|
||||
|
||||
# Returns path of a processed template PackageInfo given to 'pkgbuild --info'
|
||||
# note: '--info' is undocumented:
|
||||
# http://managingosx.wordpress.com/2012/07/05/stupid-tricks-with-pkgbuild
|
||||
def pkginfo_template_path
|
||||
pkginfo_template = Tempfile.open("fpm-PackageInfo")
|
||||
pkginfo_data = template("osxpkg.erb").result(binding)
|
||||
pkginfo_template.write(pkginfo_data)
|
||||
pkginfo_template.close
|
||||
pkginfo_template.path
|
||||
end # def write_pkginfo_template
|
||||
|
||||
# Extract name and version from PackageInfo XML
|
||||
def extract_info(package)
|
||||
with(build_path("expand")) do |path|
|
||||
doc = REXML::Document.new File.open(File.join(path, "PackageInfo"))
|
||||
pkginfo_elem = doc.elements["pkg-info"]
|
||||
identifier = pkginfo_elem.attribute("identifier").value
|
||||
self.version = pkginfo_elem.attribute("version").value
|
||||
# set name to the last dot element of the identifier
|
||||
self.name = identifier.split(".").last
|
||||
@logger.info("inferring name #{self.name} from pkg-id #{identifier}")
|
||||
end
|
||||
end # def extract_info
|
||||
|
||||
# Take a flat package as input
|
||||
def input(input_path)
|
||||
# TODO: Fail if it's a Distribution pkg or old-fashioned
|
||||
expand_dir = File.join(build_path, "expand")
|
||||
# expand_dir must not already exist for pkgutil --expand
|
||||
safesystem("pkgutil --expand #{input_path} #{expand_dir}")
|
||||
|
||||
extract_info(input_path)
|
||||
|
||||
# extract Payload
|
||||
safesystem("tar -xz -f #{expand_dir}/Payload -C #{staging_path}")
|
||||
end # def input
|
||||
|
||||
# Output a pkgbuild pkg.
|
||||
def output(output_path)
|
||||
output_check(output_path)
|
||||
raise FileAlreadyExists.new(output_path) if File.exists?(output_path)
|
||||
|
||||
temp_info = pkginfo_template_path
|
||||
|
||||
args = ["--identifier", identifier,
|
||||
"--info", temp_info,
|
||||
"--version", version.to_s,
|
||||
"--ownership", attributes[:osxpkg_ownership]]
|
||||
|
||||
if self.attributes[:osxpkg_payload_free?]
|
||||
args << "--nopayload"
|
||||
else
|
||||
args += ["--root", staging_path]
|
||||
end
|
||||
|
||||
if attributes[:before_install_given?] or attributes[:after_install_given?]
|
||||
write_scripts
|
||||
args += ["--scripts", scripts_path]
|
||||
end
|
||||
args << output_path
|
||||
|
||||
safesystem("pkgbuild", *args)
|
||||
FileUtils.remove_file(temp_info)
|
||||
end # def output
|
||||
|
||||
def to_s(format=nil)
|
||||
return super("NAME-VERSION.pkg") if format.nil?
|
||||
return super(format)
|
||||
end # def to_s
|
||||
|
||||
public(:input, :output, :identifier, :to_s)
|
||||
|
||||
end # class FPM::Package::OSXpkg
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
require "spec_setup"
|
||||
require "fpm" # local
|
||||
require "fpm/package/osxpkg" # local
|
||||
|
||||
describe FPM::Package::OSXpkg do
|
||||
|
||||
if %x{uname -s}.chomp != "Darwin"
|
||||
Cabin::Channel.get("rspec").warn("Skipping OS X tests because " \
|
||||
"this system is #{%x{uname -s}.chomp}, Darwin required")
|
||||
end
|
||||
|
||||
describe "#identifier" do
|
||||
it "should be of the form reverse.domain.pkgname" do
|
||||
subject.name = "name"
|
||||
subject.attributes[:osxpkg_identifier_prefix] = "org.great"
|
||||
insist { subject.identifier } == \
|
||||
"#{subject.attributes[:osxpkg_identifier_prefix]}.#{subject.name}"
|
||||
end
|
||||
|
||||
it "should be the name only if a prefix was not given" do
|
||||
subject.name = "name"
|
||||
subject.attributes[:osxpkg_identifier_prefix] = nil
|
||||
insist { subject.identifier } == subject.name
|
||||
end
|
||||
end
|
||||
|
||||
describe "#to_s" do
|
||||
it "should have a default output usable as a filename" do
|
||||
subject.name = "name"
|
||||
subject.version = "123"
|
||||
|
||||
# We like the format 'name-version.pkg'
|
||||
insist { subject.to_s } == "name-123.pkg"
|
||||
end
|
||||
end
|
||||
|
||||
describe "#output" do
|
||||
before :all do
|
||||
# output a package, use it as the input, set the subject to that input
|
||||
# package. This helps ensure that we can write and read packages
|
||||
# properly.
|
||||
tmpfile = Tempfile.new("fpm-test-osxpkg")
|
||||
@target = tmpfile.path
|
||||
# The target file must not exist.
|
||||
tmpfile.unlink
|
||||
|
||||
@original = FPM::Package::OSXpkg.new
|
||||
@original.name = "name"
|
||||
@original.version = "123"
|
||||
@original.attributes[:osxpkg_identifier_prefix] = "org.my"
|
||||
@original.output(@target)
|
||||
|
||||
@input = FPM::Package::OSXpkg.new
|
||||
@input.input(@target)
|
||||
end
|
||||
|
||||
after :all do
|
||||
@original.cleanup
|
||||
@input.cleanup
|
||||
end # after
|
||||
|
||||
context "package attributes" do
|
||||
it "should have the correct name" do
|
||||
insist { @input.name } == @original.name
|
||||
end
|
||||
|
||||
it "should have the correct version" do
|
||||
insist { @input.version } == @original.version
|
||||
end
|
||||
end # package attributes
|
||||
end # #output
|
||||
|
||||
end # describe FPM::Package:OSXpkg
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<pkg-info
|
||||
<% if !attributes[:osxpkg_postinstall_action].nil? -%>postinstall-action="<%= attributes[:osxpkg_postinstall_action] %>"<% end -%>
|
||||
>
|
||||
<% if !attributes[:osxpkg_dont_obsolete].nil? -%>
|
||||
<dont-obsolete>
|
||||
<% attributes[:osxpkg_dont_obsolete].each do |filepath| -%>
|
||||
<file path="<%= filepath %>"/>
|
||||
<% end -%>
|
||||
</dont-obsolete>
|
||||
<% end -%>
|
||||
</pkg-info>
|
||||
Loading…
Reference in New Issue