Cross package maker. DEB/RPM generation or conversion. Derived from jordansissel/fpm.

⌈⌋ ⎇ branch:  cross package maker


Check-in [a80b102103]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Initial OS X package support, #317. Basic support for OS X flat packages (.pkg) - input/output - output supports scripts, postinstall actions (using --info option), ownership option and bundle-id-prefix option - requires pkgbuild (therefore OS X 10.7+ only), pkgutil for input - no tests yet ignore .DS_Store ignore .pkg identifier gets its own method first few osxpkg spec tests osxpkg: extract name and version from PackageInfo on input osxpkg: rename option bundle-id-prefix to identifier-prefix - 'bundle-id' is ambiguous, given the various 'bundle'-related logic possible with OS X packages osxpkg: fix old 'osx_' attribute prefix to 'osxpkg' in osxpkg.erb New option: --osxpkg-dont-obsolete, to add files to 'dont-obsolete' element in PackageInfo. osxpkg: Define public/private methods. osxpkg_spec: Tests for basic attributes through input/output
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: a80b102103fa26aeb13701bb858b4c11dd11497b
User & Date: tim@synthist.net 2013-01-07 17:28:03
Context
2013-01-07
18:06
Merge pull request #332 from timsutton/4df9617b8a3e71ac82b4dadb8cad28cded66159f OS X package support check-in: d4fcb347a0 user: jls@semicomplete.com tags: trunk
17:28
Initial OS X package support, #317. Basic support for OS X flat packages (.pkg) - input/output - output supports scripts, postinstall actions (using --info option), ownership option and bundle-id-prefix option - requires pkgbuild (therefore OS X 10.7+ only), pkgutil for input - no tests yet ignore .DS_Store ignore .pkg identifier gets its own method first few osxpkg spec tests osxpkg: extract name and version from PackageInfo on input osxpkg: rename option bundle-id-prefix to identifier-prefix - 'bundle-id' is ambiguous, given the various 'bundle'-related logic possible with OS X packages osxpkg: fix old 'osx_' attribute prefix to 'osxpkg' in osxpkg.erb New option: --osxpkg-dont-obsolete, to add files to 'dont-obsolete' element in PackageInfo. osxpkg: Define public/private methods. osxpkg_spec: Tests for basic attributes through input/output check-in: a80b102103 user: tim@synthist.net tags: trunk
2012-12-10
21:29
Merge pull request #315 from mashion/target-os-fix Respect rpmbuild's argument ordering for #309 check-in: 9904d60a54 user: jls@semicomplete.com tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to .gitignore.

1
2
3
4
5
6
7

8
9
10
11
12
13
14
15
16
17
18



# vim
.*.sw[a-z]

# build byproducts
build-*/*
fpm.wiki
*.gem


# python
*.pyc

# RVM
.rvmrc

.yardoc
coverage
test/tmp
Gemfile.lock










>











>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# vim
.*.sw[a-z]

# build byproducts
build-*/*
fpm.wiki
*.gem
*.pkg

# python
*.pyc

# RVM
.rvmrc

.yardoc
coverage
test/tmp
Gemfile.lock

# OS X
.DS_Store

Changes to lib/fpm.rb.

1
2
3
4
5
6
7
8

require "fpm/namespace"

require "fpm/package"
require "fpm/package/dir"
require "fpm/package/gem"
require "fpm/package/deb"
require "fpm/package/rpm"
require "fpm/package/python"









>
1
2
3
4
5
6
7
8
9
require "fpm/namespace"

require "fpm/package"
require "fpm/package/dir"
require "fpm/package/gem"
require "fpm/package/deb"
require "fpm/package/rpm"
require "fpm/package/python"
require "fpm/package/osxpkg"

Added lib/fpm/package/osxpkg.rb.











































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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

Added spec/fpm/package/osxpkg_spec.rb.



















































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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

Added templates/osxpkg.erb.























>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
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>