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

⌈⌋ branch:  cross package maker


Check-in [d2b00ed9ae]

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

Overview
SHA1:d2b00ed9ae8c0aa2e8832bf7874689856e87cf3a
Date: 2013-01-09 14:37:27
User: me
Comment:Merge branch 'master' of git://github.com/jordansissel/fpm into sshhh
Tags And Properties
  • sym-trunk inherited from [2b439be7d6]
Context
2013-01-10
22:16
[827aecc1cb] Merge pull request #333 from hellysmile/master remove unused imports, some code style (user: jls, tags: trunk)
2013-01-09
14:38
[9bdf3ead74] updated CHANGELIST to include logging changes (user: me, tags: trunk)
14:37
[d2b00ed9ae] Merge branch 'master' of git://github.com/jordansissel/fpm into sshhh (user: me, tags: trunk)
14:36
[ff6813ab96] use structured logging for python package (user: me, tags: trunk)
Changes

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 CHANGELIST.













1
2
3
4
5
6
7












0.4.26 (December 27, 2012)
  - rpm: add --rpm-sign flag to sign packages using the 'rpmbuild --sign' flag.
    (#311, Patch by Pranay Kanwar)
  - rpm: fix flag ordering when calling rpmbuild (#309, #315, patch by Trotter
    Cashion)
  - deb: re-enable "Predepends" support (#319, #320, patch by Pranay Kanwar)
  - rpm: fix default 'rpm os' value (#321, 314, 309)
>
>
>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0.4.27 (???)
  - New package type: osxpkg, for building packages installable on OS X. (#332,
    patch by Timothy Sutton)
  - Fix crash bug when converting rpms to something else (#316, #325; patch by
    rtucker-mozilla)
  - deb: Add --deb-recommends and --deb-suggests (#285, #310; patch by Pranay
    Kanwar)
  - python to rpm: convert "!=" dependency operators in python to "Conflicts"
    in rpm. (#263, #312; patch by Pranay Kanwar)
  - python: fix bug - ignore blank lines in requirements.txt (#312, patch by
    Pranay Kanwar)

0.4.26 (December 27, 2012)
  - rpm: add --rpm-sign flag to sign packages using the 'rpmbuild --sign' flag.
    (#311, Patch by Pranay Kanwar)
  - rpm: fix flag ordering when calling rpmbuild (#309, #315, patch by Trotter
    Cashion)
  - deb: re-enable "Predepends" support (#319, #320, patch by Pranay Kanwar)
  - rpm: fix default 'rpm os' value (#321, 314, 309)

Changes to lib/fpm.rb.

2
3
4
5
6
7
8


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








>
2
3
4
5
6
7
8
9

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

Changes to lib/fpm/package/pyfpm/get_metadata.py.

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
from distutils.core import Command
import re
import time
import pkg_resources
try:
    import json
except ImportError:
    import simplejson as json


# Note, the last time I coded python daily was at Google, so it's entirely
# possible some of my techniques below are outdated or bad.
# If you have fixes, let me know.


class get_metadata(Command):
  description = "get package metadata"
  user_options = []

  def initialize_options(self):
    pass
  # def initialize_options

  def finalize_options(self):
    pass
  # def finalize_options

  def run(self):
    #print type(self.distribution)
    #for i in sorted(dir(self.distribution)):
      #if i.startswith("_"):
        #continue
      ###print "%s: %r" % (i, self.__getattr__(i))
      #print "%s" % i

    data = {
      "name": self.distribution.get_name(),
      "version": self.distribution.get_version(),

      "author": "%s <%s>" % (self.distribution.get_author(),
        self.distribution.get_author_email()),

      "description": self.distribution.get_description(),
      "license": self.distribution.get_license(),
      "url": self.distribution.get_url(),
    }

    # If there are python C/extension modules, we'll want to build a native
    # arch package.
    if self.distribution.has_ext_modules():
      data["architecture"] = "native"
    else:
      data["architecture"] = "all"
    # end if

    final_deps = []
    if getattr(self.distribution, 'install_requires', None):
        for dep in pkg_resources.parse_requirements(self.distribution.install_requires):

            # add all defined specs to the dependecy list separately.
            if dep.specs:
                for operator, version in dep.specs:
                    final_deps.append("%s %s %s" % (
                        dep.project_name,
                        "=" if operator == "==" else operator,

                        version
                    ))
            else:
                final_deps.append(dep.project_name)            

    data["dependencies"] = final_deps

    #print json.dumps(data, indent=2)
    if hasattr(json, 'dumps'):
        print(json.dumps(data, indent=2))
    else:
        # For Python 2.5 and Debian's python-json
        print(json.write(data))
  # def run
# class list_dependencies

<
<





>





<

|
|

|
|
<

|
|
<

|
<
<
<
<
<
<
<
|
|
|
>
|
|
>
|
|
|
|

<
<
|
|
|
|
<

|
|
|
>
|
|
|
|
|
<
>
|
|
|
|

|

<
|
|
|
|
|
<
<
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


from distutils.core import Command


import pkg_resources
try:
    import json
except ImportError:
    import simplejson as json


# Note, the last time I coded python daily was at Google, so it's entirely
# possible some of my techniques below are outdated or bad.
# If you have fixes, let me know.


class get_metadata(Command):
    description = "get package metadata"
    user_options = []

    def initialize_options(self):
        pass


    def finalize_options(self):
        pass


    def run(self):







        data = {
            "name": self.distribution.get_name(),
            "version": self.distribution.get_version(),
            "author": "%s <%s>" % (
                self.distribution.get_author(),
                self.distribution.get_author_email()
            ),
            "description": self.distribution.get_description(),
            "license": self.distribution.get_license(),
            "url": self.distribution.get_url(),
        }



        if self.distribution.has_ext_modules():
            data["architecture"] = "native"
        else:
            data["architecture"] = "all"


        final_deps = []
        if getattr(self.distribution, 'install_requires', None):
            for dep in pkg_resources.parse_requirements(
                    self.distribution.install_requires):
                # add all defined specs to the dependecy list separately.
                if dep.specs:
                    for operator, version in dep.specs:
                        final_deps.append("%s %s %s" % (
                            dep.project_name,

                            (lambda x: "=" if x == "==" else x)(operator),
                            version
                        ))
                else:
                    final_deps.append(dep.project_name)

        data["dependencies"] = final_deps


        if hasattr(json, 'dumps'):
            print(json.dumps(data, indent=2))
        else:
            # For Python 2.5 and Debian's python-json
            print(json.write(data))


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
require "spec_setup"
require "fpm" # local
require "fpm/package/osxpkg" # local

platform_is_darwin = (%x{uname -s}.chomp == "Darwin")
if !platform_is_darwin
  Cabin::Channel.get("rspec").warn("Skipping OS X pkg tests requiring 'pkgbuild', " \
      "which requires a Darwin platform.")
end

describe FPM::Package::OSXpkg do
  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", :if => platform_is_darwin 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>