fpm-itchio 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELIST +629 -0
- data/CONTRIBUTORS +26 -0
- data/LICENSE +21 -0
- data/bin/fpm +8 -0
- data/lib/fpm.rb +18 -0
- data/lib/fpm/command.rb +642 -0
- data/lib/fpm/errors.rb +4 -0
- data/lib/fpm/namespace.rb +4 -0
- data/lib/fpm/package.rb +524 -0
- data/lib/fpm/package/cpan.rb +378 -0
- data/lib/fpm/package/deb.rb +887 -0
- data/lib/fpm/package/dir.rb +207 -0
- data/lib/fpm/package/empty.rb +13 -0
- data/lib/fpm/package/gem.rb +224 -0
- data/lib/fpm/package/npm.rb +120 -0
- data/lib/fpm/package/osxpkg.rb +164 -0
- data/lib/fpm/package/p5p.rb +124 -0
- data/lib/fpm/package/pacman.rb +397 -0
- data/lib/fpm/package/pear.rb +117 -0
- data/lib/fpm/package/pkgin.rb +35 -0
- data/lib/fpm/package/puppet.rb +120 -0
- data/lib/fpm/package/pyfpm/__init__.py +1 -0
- data/lib/fpm/package/pyfpm/get_metadata.py +104 -0
- data/lib/fpm/package/python.rb +317 -0
- data/lib/fpm/package/rpm.rb +583 -0
- data/lib/fpm/package/sh.rb +69 -0
- data/lib/fpm/package/solaris.rb +95 -0
- data/lib/fpm/package/tar.rb +74 -0
- data/lib/fpm/package/virtualenv.rb +145 -0
- data/lib/fpm/package/zip.rb +63 -0
- data/lib/fpm/rake_task.rb +59 -0
- data/lib/fpm/util.rb +253 -0
- data/lib/fpm/version.rb +3 -0
- data/templates/deb.erb +52 -0
- data/templates/deb/changelog.erb +5 -0
- data/templates/deb/ldconfig.sh.erb +13 -0
- data/templates/deb/postinst_upgrade.sh.erb +62 -0
- data/templates/deb/postrm_upgrade.sh.erb +46 -0
- data/templates/deb/preinst_upgrade.sh.erb +41 -0
- data/templates/deb/prerm_upgrade.sh.erb +39 -0
- data/templates/osxpkg.erb +11 -0
- data/templates/p5p_metadata.erb +12 -0
- data/templates/pacman.erb +47 -0
- data/templates/pacman/INSTALL.erb +41 -0
- data/templates/puppet/package.pp.erb +34 -0
- data/templates/puppet/package/remove.pp.erb +13 -0
- data/templates/rpm.erb +261 -0
- data/templates/rpm/filesystem_list +14514 -0
- data/templates/sh.erb +367 -0
- data/templates/solaris.erb +15 -0
- 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
|