fpm-itchio 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELIST +629 -0
  3. data/CONTRIBUTORS +26 -0
  4. data/LICENSE +21 -0
  5. data/bin/fpm +8 -0
  6. data/lib/fpm.rb +18 -0
  7. data/lib/fpm/command.rb +642 -0
  8. data/lib/fpm/errors.rb +4 -0
  9. data/lib/fpm/namespace.rb +4 -0
  10. data/lib/fpm/package.rb +524 -0
  11. data/lib/fpm/package/cpan.rb +378 -0
  12. data/lib/fpm/package/deb.rb +887 -0
  13. data/lib/fpm/package/dir.rb +207 -0
  14. data/lib/fpm/package/empty.rb +13 -0
  15. data/lib/fpm/package/gem.rb +224 -0
  16. data/lib/fpm/package/npm.rb +120 -0
  17. data/lib/fpm/package/osxpkg.rb +164 -0
  18. data/lib/fpm/package/p5p.rb +124 -0
  19. data/lib/fpm/package/pacman.rb +397 -0
  20. data/lib/fpm/package/pear.rb +117 -0
  21. data/lib/fpm/package/pkgin.rb +35 -0
  22. data/lib/fpm/package/puppet.rb +120 -0
  23. data/lib/fpm/package/pyfpm/__init__.py +1 -0
  24. data/lib/fpm/package/pyfpm/get_metadata.py +104 -0
  25. data/lib/fpm/package/python.rb +317 -0
  26. data/lib/fpm/package/rpm.rb +583 -0
  27. data/lib/fpm/package/sh.rb +69 -0
  28. data/lib/fpm/package/solaris.rb +95 -0
  29. data/lib/fpm/package/tar.rb +74 -0
  30. data/lib/fpm/package/virtualenv.rb +145 -0
  31. data/lib/fpm/package/zip.rb +63 -0
  32. data/lib/fpm/rake_task.rb +59 -0
  33. data/lib/fpm/util.rb +253 -0
  34. data/lib/fpm/version.rb +3 -0
  35. data/templates/deb.erb +52 -0
  36. data/templates/deb/changelog.erb +5 -0
  37. data/templates/deb/ldconfig.sh.erb +13 -0
  38. data/templates/deb/postinst_upgrade.sh.erb +62 -0
  39. data/templates/deb/postrm_upgrade.sh.erb +46 -0
  40. data/templates/deb/preinst_upgrade.sh.erb +41 -0
  41. data/templates/deb/prerm_upgrade.sh.erb +39 -0
  42. data/templates/osxpkg.erb +11 -0
  43. data/templates/p5p_metadata.erb +12 -0
  44. data/templates/pacman.erb +47 -0
  45. data/templates/pacman/INSTALL.erb +41 -0
  46. data/templates/puppet/package.pp.erb +34 -0
  47. data/templates/puppet/package/remove.pp.erb +13 -0
  48. data/templates/rpm.erb +261 -0
  49. data/templates/rpm/filesystem_list +14514 -0
  50. data/templates/sh.erb +367 -0
  51. data/templates/solaris.erb +15 -0
  52. metadata +265 -0
@@ -0,0 +1,117 @@
1
+ require "fpm/namespace"
2
+ require "fpm/package"
3
+ require "fileutils"
4
+ require "fpm/util"
5
+
6
+ # This provides PHP PEAR package support.
7
+ #
8
+ # This provides input support, but not output support.
9
+ class FPM::Package::PEAR < FPM::Package
10
+ option "--package-name-prefix", "PREFIX",
11
+ "Name prefix for pear package", :default => "php-pear"
12
+
13
+ option "--channel", "CHANNEL_URL",
14
+ "The pear channel url to use instead of the default."
15
+
16
+ option "--channel-update", :flag,
17
+ "call 'pear channel-update' prior to installation"
18
+
19
+ option "--bin-dir", "BIN_DIR",
20
+ "Directory to put binaries in"
21
+
22
+ option "--php-bin", "PHP_BIN",
23
+ "Specify php executable path if differs from the os used for packaging"
24
+
25
+ option "--php-dir", "PHP_DIR",
26
+ "Specify php dir relative to prefix if differs from pear default (pear/php)"
27
+
28
+ option "--data-dir", "DATA_DIR",
29
+ "Specify php dir relative to prefix if differs from pear default (pear/data)"
30
+
31
+ # Input a PEAR package.
32
+ #
33
+ # The parameter is a PHP PEAR package name.
34
+ #
35
+ # Attributes that affect behavior here:
36
+ # * :prefix - changes the install root, default is /usr/share
37
+ # * :pear_package_name_prefix - changes the
38
+ def input(input_package)
39
+ if !program_in_path?("pear")
40
+ raise ExecutableNotFound.new("pear")
41
+ end
42
+
43
+ # Create a temporary config file
44
+ logger.debug("Creating pear config file")
45
+ config = File.expand_path(build_path("pear.config"))
46
+ installroot = attributes[:prefix] || "/usr/share"
47
+ safesystem("pear", "config-create", staging_path(installroot), config)
48
+
49
+ if attributes[:pear_php_dir]
50
+ logger.info("Setting php_dir", :php_dir => attributes[:pear_php_dir])
51
+ safesystem("pear", "-c", config, "config-set", "php_dir", "#{staging_path(installroot)}/#{attributes[:pear_php_dir]}")
52
+ end
53
+
54
+ if attributes[:pear_data_dir]
55
+ logger.info("Setting data_dir", :data_dir => attributes[:pear_data_dir])
56
+ safesystem("pear", "-c", config, "config-set", "data_dir", "#{staging_path(installroot)}/#{attributes[:pear_data_dir]}")
57
+ end
58
+
59
+ bin_dir = attributes[:pear_bin_dir] || "usr/bin"
60
+ logger.info("Setting bin_dir", :bin_dir => bin_dir)
61
+ safesystem("pear", "-c", config, "config-set", "bin_dir", bin_dir)
62
+
63
+ php_bin = attributes[:pear_php_bin] || "/usr/bin/php"
64
+ logger.info("Setting php_bin", :php_bin => php_bin)
65
+ safesystem("pear", "-c", config, "config-set", "php_bin", php_bin)
66
+
67
+ # do channel-discover if required
68
+ if !attributes[:pear_channel].nil?
69
+ logger.info("Custom channel specified", :channel => attributes[:pear_channel])
70
+ channel_list = safesystemout("pear", "-c", config, "list-channels")
71
+ if channel_list !~ /#{Regexp.quote(attributes[:pear_channel])}/
72
+ logger.info("Discovering new channel", :channel => attributes[:pear_channel])
73
+ safesystem("pear", "-c", config, "channel-discover", attributes[:pear_channel])
74
+ end
75
+ input_package = "#{attributes[:pear_channel]}/#{input_package}"
76
+ logger.info("Prefixing package name with channel", :package => input_package)
77
+ end
78
+
79
+ # do channel-update if requested
80
+ if attributes[:pear_channel_update?]
81
+ channel = attributes[:pear_channel] || "pear"
82
+ logger.info("Updating the channel", :channel => channel)
83
+ safesystem("pear", "-c", config, "channel-update", channel)
84
+ end
85
+
86
+ logger.info("Installing pear package", :package => input_package,
87
+ :directory => staging_path)
88
+ ::Dir.chdir(staging_path) do
89
+ safesystem("pear", "-c", config, "install", "-n", "-f", input_package)
90
+ end
91
+
92
+ pear_cmd = "pear -c #{config} remote-info #{input_package}"
93
+ logger.info("Fetching package information", :package => input_package, :command => pear_cmd)
94
+ name = %x{#{pear_cmd} | sed -ne '/^Package\s*/s/^Package\s*//p'}.chomp
95
+ self.name = "#{attributes[:pear_package_name_prefix]}-#{name}"
96
+ self.version = %x{#{pear_cmd} | sed -ne '/^Installed\s*/s/^Installed\s*//p'}.chomp
97
+ self.description = %x{#{pear_cmd} | sed -ne '/^Summary\s*/s/^Summary\s*//p'}.chomp
98
+ logger.debug("Package info", :name => self.name, :version => self.version,
99
+ :description => self.description)
100
+
101
+ # Remove the stuff we don't want
102
+ delete_these = [".depdb", ".depdblock", ".filemap", ".lock", ".channel", "cache", "temp", "download", ".channels", ".registry"]
103
+ Find.find(staging_path) do |path|
104
+ if File.file?(path)
105
+ logger.info("replacing staging_path in file", :replace_in => path, :staging_path => staging_path)
106
+ begin
107
+ content = File.read(path).gsub(/#{Regexp.escape(staging_path)}/, "")
108
+ File.write(path, content)
109
+ rescue ArgumentError => e
110
+ logger.warn("error replacing staging_path in file", :replace_in => path, :error => e)
111
+ end
112
+ end
113
+ FileUtils.rm_r(path) if delete_these.include?(File.basename(path))
114
+ end
115
+
116
+ end # def input
117
+ end # class FPM::Package::PEAR
@@ -0,0 +1,35 @@
1
+ class FPM::Package::Pkgin < FPM::Package
2
+
3
+ def output(output_path)
4
+ output_check(output_path)
5
+
6
+ File.write(build_path("build-info"), `pkg_info -X pkg_install | egrep '^(MACHINE_ARCH|OPSYS|OS_VERSION|PKGTOOLS_VERSION)'`)
7
+
8
+ cwd = ::Dir.pwd
9
+ ::Dir.chdir(staging_path)
10
+
11
+ files = []
12
+ Find.find(".") do |path|
13
+ stat = File.lstat(path)
14
+ next unless stat.symlink? or stat.file?
15
+ files << path
16
+ end
17
+ ::Dir.chdir(cwd)
18
+
19
+ File.write(build_path("packlist"), files.sort.join("\n"))
20
+
21
+ File.write(build_path("comment"), self.description + "\n")
22
+
23
+ File.write(build_path("description"), self.description + "\n")
24
+
25
+ args = [ "-B", build_path("build-info"), "-c", build_path("comment"), "-d", build_path("description"), "-f", build_path("packlist"), "-I", "/opt/local", "-p", staging_path, "-U", "#{cwd}/#{name}-#{self.version}-#{iteration}.tgz" ]
26
+ safesystem("pkg_create", *args)
27
+
28
+ end
29
+
30
+ def iteration
31
+ return @iteration ? @iteration : 1
32
+ end
33
+
34
+ end
35
+
@@ -0,0 +1,120 @@
1
+ require "erb"
2
+ require "fpm/namespace"
3
+ require "fpm/package"
4
+ require "fpm/errors"
5
+ require "etc"
6
+ require "fileutils"
7
+
8
+ class FPM::Package::Puppet < FPM::Package
9
+ def architecture
10
+ case @architecture
11
+ when nil, "native"
12
+ @architecture = %x{uname -m}.chomp
13
+ end
14
+ return @architecture
15
+ end # def architecture
16
+
17
+ # Default specfile generator just makes one specfile, whatever that is for
18
+ # this package.
19
+ def generate_specfile(builddir)
20
+ paths = []
21
+ logger.info("PWD: #{File.join(builddir, unpack_data_to)}")
22
+ fileroot = File.join(builddir, unpack_data_to)
23
+ Dir.chdir(fileroot) do
24
+ Find.find(".") do |p|
25
+ next if p == "."
26
+ paths << p
27
+ end
28
+ end
29
+ logger.info(paths[-1])
30
+ manifests = %w{package.pp package/remove.pp}
31
+
32
+ ::Dir.mkdir(File.join(builddir, "manifests"))
33
+ manifests.each do |manifest|
34
+ dir = File.join(builddir, "manifests", File.dirname(manifest))
35
+ logger.info("manifests targeting: #{dir}")
36
+ ::Dir.mkdir(dir) if !File.directory?(dir)
37
+
38
+ File.open(File.join(builddir, "manifests", manifest), "w") do |f|
39
+ logger.info("manifest: #{f.path}")
40
+ template = template(File.join("puppet", "#{manifest}.erb"))
41
+ ::Dir.chdir(fileroot) do
42
+ f.puts template.result(binding)
43
+ end
44
+ end
45
+ end
46
+ end # def generate_specfile
47
+
48
+ def unpack_data_to
49
+ "files"
50
+ end
51
+
52
+ def build!(params)
53
+ # TODO(sissel): Support these somehow, perhaps with execs and files.
54
+ self.scripts.each do |name, path|
55
+ case name
56
+ when "pre-install"
57
+ when "post-install"
58
+ when "pre-uninstall"
59
+ when "post-uninstall"
60
+ end # case name
61
+ end # self.scripts.each
62
+
63
+ if File.exists?(params[:output])
64
+ # TODO(sissel): Allow folks to choose output?
65
+ logger.error("Puppet module directory '#{params[:output]}' already " \
66
+ "exists. Delete it or choose another output (-p flag)")
67
+ end
68
+
69
+ ::Dir.mkdir(params[:output])
70
+ builddir = ::Dir.pwd
71
+
72
+ # Copy 'files' from builddir to :output/files
73
+ Find.find("files", "manifests") do |path|
74
+ logger.info("Copying path: #{path}")
75
+ if File.directory?(path)
76
+ ::Dir.mkdir(File.join(params[:output], path))
77
+ else
78
+ FileUtils.cp(path, File.join(params[:output], path))
79
+ end
80
+ end
81
+ end # def build!
82
+
83
+ # The directory we create should just be the name of the package as the
84
+ # module name
85
+ def default_output
86
+ name
87
+ end # def default_output
88
+
89
+ # This method is used by the puppet manifest template
90
+ def puppetsort(hash)
91
+ # TODO(sissel): Implement sorting that follows the puppet style guide
92
+ # Such as, 'ensure' goes first, etc.
93
+ return hash.to_a
94
+ end # def puppetsort
95
+
96
+ # Helper for user lookup
97
+ def uid2user(uid)
98
+ begin
99
+ pwent = Etc.getpwuid(uid)
100
+ return pwent.name
101
+ rescue ArgumentError => e
102
+ # Invalid user id? No user? Return the uid.
103
+ logger.warn("Failed to find username for uid #{uid}")
104
+ return uid.to_s
105
+ end
106
+ end # def uid2user
107
+
108
+ # Helper for group lookup
109
+ def gid2group(gid)
110
+ begin
111
+ grent = Etc.getgrgid(gid)
112
+ return grent.name
113
+ rescue ArgumentError => e
114
+ # Invalid user id? No user? Return the uid.
115
+ logger.warn("Failed to find group for gid #{gid}")
116
+ return gid.to_s
117
+ end
118
+ end # def uid2user
119
+ end # class FPM::Target::Puppet
120
+
@@ -0,0 +1 @@
1
+ __all__ = [ "get_metadata" ]
@@ -0,0 +1,104 @@
1
+ from distutils.core import Command
2
+ import os
3
+ import sys
4
+ import pkg_resources
5
+ try:
6
+ import json
7
+ except ImportError:
8
+ import simplejson as json
9
+
10
+ PY3 = sys.version_info[0] == 3
11
+
12
+ if PY3:
13
+ def u(s):
14
+ return s
15
+ else:
16
+ def u(s):
17
+ if isinstance(u, unicode):
18
+ return u
19
+ return s.decode('utf-8')
20
+
21
+
22
+ # Note, the last time I coded python daily was at Google, so it's entirely
23
+ # possible some of my techniques below are outdated or bad.
24
+ # If you have fixes, let me know.
25
+
26
+
27
+ class get_metadata(Command):
28
+ description = "get package metadata"
29
+ user_options = [
30
+ ('load-requirements-txt', 'l',
31
+ "load dependencies from requirements.txt"),
32
+ ("output=", "o", "output destination for metadata json")
33
+ ]
34
+ boolean_options = ['load-requirements-txt']
35
+
36
+ def initialize_options(self):
37
+ self.load_requirements_txt = False
38
+ self.cwd = None
39
+ self.output = None
40
+
41
+ def finalize_options(self):
42
+ self.cwd = os.getcwd()
43
+ self.requirements_txt = os.path.join(self.cwd, "requirements.txt")
44
+ # make sure we have a requirements.txt
45
+ if self.load_requirements_txt:
46
+ self.load_requirements_txt = os.path.exists(self.requirements_txt)
47
+
48
+ def process_dep(self, dep):
49
+ deps = []
50
+ if dep.specs:
51
+ for operator, version in dep.specs:
52
+ deps.append("%s %s %s" % (dep.project_name,
53
+ operator, version))
54
+ else:
55
+ deps.append(dep.project_name)
56
+
57
+ return deps
58
+
59
+ def run(self):
60
+ data = {
61
+ "name": self.distribution.get_name(),
62
+ "version": self.distribution.get_version(),
63
+ "author": u("%s <%s>") % (
64
+ u(self.distribution.get_author()),
65
+ u(self.distribution.get_author_email()),
66
+ ),
67
+ "description": self.distribution.get_description(),
68
+ "license": self.distribution.get_license(),
69
+ "url": self.distribution.get_url(),
70
+ }
71
+
72
+ if self.distribution.has_ext_modules():
73
+ data["architecture"] = "native"
74
+ else:
75
+ data["architecture"] = "all"
76
+
77
+ final_deps = []
78
+
79
+ if self.load_requirements_txt:
80
+ requirement = open(self.requirements_txt).readlines()
81
+ for dep in pkg_resources.parse_requirements(requirement):
82
+ final_deps.extend(self.process_dep(dep))
83
+ else:
84
+ if getattr(self.distribution, 'install_requires', None):
85
+ for dep in pkg_resources.parse_requirements(
86
+ self.distribution.install_requires):
87
+ final_deps.extend(self.process_dep(dep))
88
+
89
+ data["dependencies"] = final_deps
90
+
91
+ output = open(self.output, "w")
92
+ if hasattr(json, 'dumps'):
93
+ def default_to_str(obj):
94
+ """ Fall back to using __str__ if possible """
95
+ # This checks if the class of obj defines __str__ itself,
96
+ # so we don't fall back to an inherited __str__ method.
97
+ if "__str__" in type(obj).__dict__:
98
+ return str(obj)
99
+ return json.JSONEncoder.default(self, obj)
100
+
101
+ output.write(json.dumps(data, indent=2, default=default_to_str))
102
+ else:
103
+ # For Python 2.5 and Debian's python-json
104
+ output.write(json.write(data))
@@ -0,0 +1,317 @@
1
+ require "fpm/namespace"
2
+ require "fpm/package"
3
+ require "fpm/util"
4
+ require "rubygems/package"
5
+ require "rubygems"
6
+ require "fileutils"
7
+ require "tmpdir"
8
+ require "json"
9
+
10
+ # Support for python packages.
11
+ #
12
+ # This supports input, but not output.
13
+ #
14
+ # Example:
15
+ #
16
+ # # Download the django python package:
17
+ # pkg = FPM::Package::Python.new
18
+ # pkg.input("Django")
19
+ #
20
+ class FPM::Package::Python < FPM::Package
21
+ # Flags '--foo' will be accessable as attributes[:python_foo]
22
+ option "--bin", "PYTHON_EXECUTABLE",
23
+ "The path to the python executable you wish to run.", :default => "python"
24
+ option "--easyinstall", "EASYINSTALL_EXECUTABLE",
25
+ "The path to the easy_install executable tool", :default => "easy_install"
26
+ option "--pip", "PIP_EXECUTABLE",
27
+ "The path to the pip executable tool. If not specified, easy_install " \
28
+ "is used instead", :default => nil
29
+ option "--pypi", "PYPI_URL",
30
+ "PyPi Server uri for retrieving packages.",
31
+ :default => "https://pypi.python.org/simple"
32
+ option "--package-prefix", "NAMEPREFIX",
33
+ "(DEPRECATED, use --package-name-prefix) Name to prefix the package " \
34
+ "name with." do |value|
35
+ logger.warn("Using deprecated flag: --package-prefix. Please use " \
36
+ "--package-name-prefix")
37
+ value
38
+ end
39
+ option "--package-name-prefix", "PREFIX", "Name to prefix the package " \
40
+ "name with.", :default => "python"
41
+ option "--fix-name", :flag, "Should the target package name be prefixed?",
42
+ :default => true
43
+ option "--fix-dependencies", :flag, "Should the package dependencies be " \
44
+ "prefixed?", :default => true
45
+
46
+ option "--downcase-name", :flag, "Should the target package name be in " \
47
+ "lowercase?", :default => true
48
+ option "--downcase-dependencies", :flag, "Should the package dependencies " \
49
+ "be in lowercase?", :default => true
50
+
51
+ option "--install-bin", "BIN_PATH", "The path to where python scripts " \
52
+ "should be installed to."
53
+ option "--install-lib", "LIB_PATH", "The path to where python libs " \
54
+ "should be installed to (default depends on your python installation). " \
55
+ "Want to find out what your target platform is using? Run this: " \
56
+ "python -c 'from distutils.sysconfig import get_python_lib; " \
57
+ "print get_python_lib()'"
58
+ option "--install-data", "DATA_PATH", "The path to where data should be " \
59
+ "installed to. This is equivalent to 'python setup.py --install-data " \
60
+ "DATA_PATH"
61
+ option "--dependencies", :flag, "Include requirements defined in setup.py" \
62
+ " as dependencies.", :default => true
63
+ option "--obey-requirements-txt", :flag, "Use a requirements.txt file " \
64
+ "in the top-level directory of the python package for dependency " \
65
+ "detection.", :default => false
66
+ option "--scripts-executable", "PYTHON_EXECUTABLE", "Set custom python " \
67
+ "interpreter in installing scripts. By default distutils will replace " \
68
+ "python interpreter in installing scripts (specified by shebang) with " \
69
+ "current python interpreter (sys.executable). This option is equivalent " \
70
+ "to appending 'build_scripts --executable PYTHON_EXECUTABLE' arguments " \
71
+ "to 'setup.py install' command."
72
+ option "--disable-dependency", "python_package_name",
73
+ "The python package name to remove from dependency list",
74
+ :multivalued => true, :attribute_name => :python_disable_dependency,
75
+ :default => []
76
+
77
+ private
78
+
79
+ # Input a package.
80
+ #
81
+ # The 'package' can be any of:
82
+ #
83
+ # * A name of a package on pypi (ie; easy_install some-package)
84
+ # * The path to a directory containing setup.py
85
+ # * The path to a setup.py
86
+ def input(package)
87
+ path_to_package = download_if_necessary(package, version)
88
+
89
+ if File.directory?(path_to_package)
90
+ setup_py = File.join(path_to_package, "setup.py")
91
+ else
92
+ setup_py = path_to_package
93
+ end
94
+
95
+ if !File.exist?(setup_py)
96
+ logger.error("Could not find 'setup.py'", :path => setup_py)
97
+ raise "Unable to find python package; tried #{setup_py}"
98
+ end
99
+
100
+ load_package_info(setup_py)
101
+ install_to_staging(setup_py)
102
+ end # def input
103
+
104
+ # Download the given package if necessary. If version is given, that version
105
+ # will be downloaded, otherwise the latest is fetched.
106
+ def download_if_necessary(package, version=nil)
107
+ # TODO(sissel): this should just be a 'download' method, the 'if_necessary'
108
+ # part should go elsewhere.
109
+ path = package
110
+ # If it's a path, assume local build.
111
+ if File.directory?(path) or (File.exist?(path) and File.basename(path) == "setup.py")
112
+ return path
113
+ end
114
+
115
+ logger.info("Trying to download", :package => package)
116
+
117
+ if version.nil?
118
+ want_pkg = "#{package}"
119
+ else
120
+ want_pkg = "#{package}==#{version}"
121
+ end
122
+
123
+ target = build_path(package)
124
+ FileUtils.mkdir(target) unless File.directory?(target)
125
+
126
+ if attributes[:python_pip].nil?
127
+ # no pip, use easy_install
128
+ logger.debug("no pip, defaulting to easy_install", :easy_install => attributes[:python_easyinstall])
129
+ safesystem(attributes[:python_easyinstall], "-i",
130
+ attributes[:python_pypi], "--editable", "-U",
131
+ "--build-directory", target, want_pkg)
132
+ else
133
+ logger.debug("using pip", :pip => attributes[:python_pip])
134
+ safesystem(attributes[:python_pip], "install", "--no-deps", "--no-install", "--no-use-wheel", "-i", attributes[:python_pypi], "-U", "--build", target, want_pkg)
135
+ end
136
+
137
+ # easy_install will put stuff in @tmpdir/packagename/, so find that:
138
+ # @tmpdir/somepackage/setup.py
139
+ dirs = ::Dir.glob(File.join(target, "*"))
140
+ if dirs.length != 1
141
+ raise "Unexpected directory layout after easy_install. Maybe file a bug? The directory is #{build_path}"
142
+ end
143
+ return dirs.first
144
+ end # def download
145
+
146
+ # Load the package information like name, version, dependencies.
147
+ def load_package_info(setup_py)
148
+ if !attributes[:python_package_prefix].nil?
149
+ attributes[:python_package_name_prefix] = attributes[:python_package_prefix]
150
+ end
151
+
152
+ begin
153
+ json_test_code = [
154
+ "try:",
155
+ " import json",
156
+ "except ImportError:",
157
+ " import simplejson as json"
158
+ ].join("\n")
159
+ safesystem("#{attributes[:python_bin]} -c '#{json_test_code}'")
160
+ rescue FPM::Util::ProcessFailed => e
161
+ logger.error("Your python environment is missing json support (either json or simplejson python module). I cannot continue without this.", :python => attributes[:python_bin], :error => e)
162
+ raise FPM::Util::ProcessFailed, "Python (#{attributes[:python_bin]}) is missing simplejson or json modules."
163
+ end
164
+
165
+ begin
166
+ safesystem("#{attributes[:python_bin]} -c 'import pkg_resources'")
167
+ rescue FPM::Util::ProcessFailed => e
168
+ logger.error("Your python environment is missing a working setuptools module. I tried to find the 'pkg_resources' module but failed.", :python => attributes[:python_bin], :error => e)
169
+ raise FPM::Util::ProcessFailed, "Python (#{attributes[:python_bin]}) is missing pkg_resources module."
170
+ end
171
+
172
+ # Add ./pyfpm/ to the python library path
173
+ pylib = File.expand_path(File.dirname(__FILE__))
174
+
175
+ # chdir to the directory holding setup.py because some python setup.py's assume that you are
176
+ # in the same directory.
177
+ setup_dir = File.dirname(setup_py)
178
+
179
+ output = ::Dir.chdir(setup_dir) do
180
+ tmp = build_path("metadata.json")
181
+ setup_cmd = "env PYTHONPATH=#{pylib} #{attributes[:python_bin]} " \
182
+ "setup.py --command-packages=pyfpm get_metadata --output=#{tmp}"
183
+
184
+ if attributes[:python_obey_requirements_txt?]
185
+ setup_cmd += " --load-requirements-txt"
186
+ end
187
+
188
+ # Capture the output, which will be JSON metadata describing this python
189
+ # package. See fpm/lib/fpm/package/pyfpm/get_metadata.py for more
190
+ # details.
191
+ logger.info("fetching package metadata", :setup_cmd => setup_cmd)
192
+
193
+ success = safesystem(setup_cmd)
194
+ #%x{#{setup_cmd}}
195
+ if !success
196
+ logger.error("setup.py get_metadata failed", :command => setup_cmd,
197
+ :exitcode => $?.exitstatus)
198
+ raise "An unexpected error occurred while processing the setup.py file"
199
+ end
200
+ File.read(tmp)
201
+ end
202
+ logger.debug("result from `setup.py get_metadata`", :data => output)
203
+ metadata = JSON.parse(output)
204
+ logger.info("object output of get_metadata", :json => metadata)
205
+
206
+ self.architecture = metadata["architecture"]
207
+ self.description = metadata["description"]
208
+ # Sometimes the license field is multiple lines; do best-effort and just
209
+ # use the first line.
210
+ self.license = metadata["license"].split(/[\r\n]+/).first
211
+ self.version = metadata["version"]
212
+ self.url = metadata["url"]
213
+
214
+ # name prefixing is optional, if enabled, a name 'foo' will become
215
+ # 'python-foo' (depending on what the python_package_name_prefix is)
216
+ if attributes[:python_fix_name?]
217
+ self.name = fix_name(metadata["name"])
218
+ else
219
+ self.name = metadata["name"]
220
+ end
221
+
222
+ # convert python-Foo to python-foo if flag is set
223
+ self.name = self.name.downcase if attributes[:python_downcase_name?]
224
+
225
+ if !attributes[:no_auto_depends?] and attributes[:python_dependencies?]
226
+ metadata["dependencies"].each do |dep|
227
+ dep_re = /^([^<>!= ]+)\s*(?:([<>!=]{1,2})\s*(.*))?$/
228
+ match = dep_re.match(dep)
229
+ if match.nil?
230
+ logger.error("Unable to parse dependency", :dependency => dep)
231
+ raise FPM::InvalidPackageConfiguration, "Invalid dependency '#{dep}'"
232
+ end
233
+ name, cmp, version = match.captures
234
+
235
+ next if attributes[:python_disable_dependency].include?(name)
236
+
237
+ # convert == to =
238
+ if cmp == "=="
239
+ logger.info("Converting == dependency requirement to =", :dependency => dep )
240
+ cmp = "="
241
+ end
242
+
243
+ # dependency name prefixing is optional, if enabled, a name 'foo' will
244
+ # become 'python-foo' (depending on what the python_package_name_prefix
245
+ # is)
246
+ name = fix_name(name) if attributes[:python_fix_dependencies?]
247
+
248
+ # convert dependencies from python-Foo to python-foo
249
+ name = name.downcase if attributes[:python_downcase_dependencies?]
250
+
251
+ self.dependencies << "#{name} #{cmp} #{version}"
252
+ end
253
+ end # if attributes[:python_dependencies?]
254
+ end # def load_package_info
255
+
256
+ # Sanitize package name.
257
+ # Some PyPI packages can be named 'python-foo', so we don't want to end up
258
+ # with a package named 'python-python-foo'.
259
+ # But we want packages named like 'pythonweb' to be suffixed
260
+ # 'python-pythonweb'.
261
+ def fix_name(name)
262
+ if name.start_with?("python")
263
+ # If the python package is called "python-foo" strip the "python-" part while
264
+ # prepending the package name prefix.
265
+ return [attributes[:python_package_name_prefix], name.gsub(/^python-/, "")].join("-")
266
+ else
267
+ return [attributes[:python_package_name_prefix], name].join("-")
268
+ end
269
+ end # def fix_name
270
+
271
+ # Install this package to the staging directory
272
+ def install_to_staging(setup_py)
273
+ project_dir = File.dirname(setup_py)
274
+
275
+ prefix = "/"
276
+ prefix = attributes[:prefix] unless attributes[:prefix].nil?
277
+
278
+ # Some setup.py's assume $PWD == current directory of setup.py, so let's
279
+ # chdir first.
280
+ ::Dir.chdir(project_dir) do
281
+ flags = [ "--root", staging_path ]
282
+ if !attributes[:python_install_lib].nil?
283
+ flags += [ "--install-lib", File.join(prefix, attributes[:python_install_lib]) ]
284
+ elsif !attributes[:prefix].nil?
285
+ # setup.py install --prefix PREFIX still installs libs to
286
+ # PREFIX/lib64/python2.7/site-packages/
287
+ # but we really want something saner.
288
+ #
289
+ # since prefix is given, but not python_install_lib, assume PREFIX/lib
290
+ flags += [ "--install-lib", File.join(prefix, "lib") ]
291
+ end
292
+
293
+ if !attributes[:python_install_data].nil?
294
+ flags += [ "--install-data", File.join(prefix, attributes[:python_install_data]) ]
295
+ elsif !attributes[:prefix].nil?
296
+ # prefix given, but not python_install_data, assume PREFIX/data
297
+ flags += [ "--install-data", File.join(prefix, "data") ]
298
+ end
299
+
300
+ if !attributes[:python_install_bin].nil?
301
+ flags += [ "--install-scripts", File.join(prefix, attributes[:python_install_bin]) ]
302
+ elsif !attributes[:prefix].nil?
303
+ # prefix given, but not python_install_bin, assume PREFIX/bin
304
+ flags += [ "--install-scripts", File.join(prefix, "bin") ]
305
+ end
306
+
307
+ if !attributes[:python_scripts_executable].nil?
308
+ # Overwrite installed python scripts shebang binary with provided executable
309
+ flags += [ "build_scripts", "--executable", attributes[:python_scripts_executable] ]
310
+ end
311
+
312
+ safesystem(attributes[:python_bin], "setup.py", "install", *flags)
313
+ end
314
+ end # def install_to_staging
315
+
316
+ public(:input)
317
+ end # class FPM::Package::Python