fpm-itchio 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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