fpm 0.4.32 → 0.4.35

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELIST CHANGED
@@ -1,3 +1,19 @@
1
+ 0.4.35 (May 8, 2013)
2
+ - Fix dependencies listed in the gem.
3
+
4
+ 0.4.34 (May 7, 2013)
5
+ - Now supports CPAN - Perl mongers rejoice! For example:
6
+ 'fpm -s cpan -t deb DBI'
7
+ - deb: fixed some additional complaints by lintian (#420, patch by Pranay
8
+ Kanwar)
9
+ - rpm: add flags --rpm-autoreqprov, --rpm-autoreq, and --rpm-autoprov
10
+ to tell rpm to enable that feature in the rpm spec. (#416, patch by Adam
11
+ Stephens)
12
+
13
+ 0.4.33 (April 9, 2013)
14
+ - Now supports npm, the node package manager. For example:
15
+ 'fpm -s npm -t deb express'
16
+
1
17
  0.4.32 (April 9, 2013)
2
18
  - COMPATIBILITY WARNING: rpm: The default epoch is now nothing because this
3
19
  aligns more closely with typical rpm packages in the real world. This
@@ -18,6 +18,7 @@ Thomas Meson (github: zllak)
18
18
  Oliver Hookins (github: ohookins)
19
19
  llasram
20
20
  sbuss
21
+ Brett Gailey (github: dnbert)
21
22
 
22
23
  If you have contributed (bug reports, feature requests, help in IRC, blog
23
24
  posts, code, etc) and aren't listed here, please let me know if you wish to be
@@ -77,52 +77,34 @@ class FPM::Command < Clamp::Command
77
77
  option ["-d", "--depends"], "DEPENDENCY",
78
78
  "A dependency. This flag can be specified multiple times. Value is " \
79
79
  "usually in the form of: -d 'name' or -d 'name > version'",
80
- :default => [], :attribute_name => :dependencies do |val|
81
- # Clamp doesn't support multivalue flags (ie; specifying -d multiple times)
82
- # so we can hack around it with this trickery.
83
- @dependencies ||= []
84
- @dependencies << val
85
- end # -d / --depends
80
+ :multivalued => true, :attribute_name => :dependencies
86
81
 
87
82
  option "--no-depends", :flag, "Do not list any dependencies in this package",
88
83
  :default => false
89
84
 
90
- option "--no-auto-depends", :flag, "Do not list any dependencies in this package automatically",
91
- :default => false
85
+ option "--no-auto-depends", :flag, "Do not list any dependencies in this" \
86
+ "package automatically", :default => false
92
87
 
93
88
  option "--provides", "PROVIDES",
94
89
  "What this package provides (usually a name). This flag can be "\
95
- "specified multiple times." do |val|
96
- @provides ||= []
97
- @provides << val
98
- end # --provides
90
+ "specified multiple times.", :multivalued => true,
91
+ :attribute_name => :provides
99
92
  option "--conflicts", "CONFLICTS",
100
93
  "Other packages/versions this package conflicts with. This flag can " \
101
- "specified multiple times." do |val|
102
- @conflicts ||= []
103
- @conflicts << val
104
- end # --conflicts
94
+ "specified multiple times.", :multivalued => true,
95
+ :attribute_name => :conflicts
105
96
  option "--replaces", "REPLACES",
106
97
  "Other packages/versions this package replaces. This flag can be "\
107
- "specified multiple times." do |val|
108
- @replaces ||= []
109
- @replaces << val
110
- end # --replaces
98
+ "specified multiple times.", :multivalued => true,
99
+ :attribute_name => :replaces
100
+
111
101
  option "--config-files", "CONFIG_FILES",
112
102
  "Mark a file in the package as being a config file. This uses 'conffiles'" \
113
103
  " in debs and %config in rpm. If you have multiple files to mark as " \
114
- "configuration files, specify this flag multiple times." do |val|
115
- #You can specify a directory to have it scanned marking all files found as
116
- #config files. If you have multiple "
117
- @config_files ||= []
118
- @config_files << val
119
- end # --config-files
120
- option "--directories", "DIRECTORIES",
121
- "Mark a directory as being owned by the package" \
122
- do |val|
123
- @directories ||= []
124
- @directories << val
125
- end # directories
104
+ "configuration files, specify this flag multiple times.",
105
+ :multivalued => true, :attribute_name => :config_files
106
+ option "--directories", "DIRECTORIES", "Mark a directory as being owned " \
107
+ "by the package", :multivalued => true, :attribute_name => :directories
126
108
  option ["-a", "--architecture"], "ARCHITECTURE",
127
109
  "The architecture name. Usually matches 'uname -m'. For automatic values," \
128
110
  " you can use '-a all' or '-a native'. These two strings will be " \
@@ -134,12 +116,14 @@ class FPM::Command < Clamp::Command
134
116
  "a name suffix to append to package and dependencies."
135
117
  option ["-e", "--edit"], :flag,
136
118
  "Edit the package spec before building.", :default => false
119
+
120
+ excludes = []
137
121
  option ["-x", "--exclude"], "EXCLUDE_PATTERN",
138
122
  "Exclude paths matching pattern (shell wildcard globs valid here). " \
139
123
  "If you have multiple file patterns to exclude, specify this flag " \
140
124
  "multiple times.", :attribute_name => :excludes do |val|
141
- @excludes ||= []
142
- @excludes << val
125
+ excludes << val
126
+ next excludes
143
127
  end # -x / --exclude
144
128
  option "--description", "DESCRIPTION", "Add a description for this package." \
145
129
  " You can include '\n' sequences to indicate newline breaks.",
@@ -199,10 +183,10 @@ class FPM::Command < Clamp::Command
199
183
 
200
184
  option "--template-value", "KEY=VALUE",
201
185
  "Make 'key' available in script templates, so <%= key %> given will be " \
202
- "the provided value. Implies --template-scripts" do |kv|
186
+ "the provided value. Implies --template-scripts",
187
+ :multivalued => true do |kv|
203
188
  @template_scripts = true
204
- @template_values ||= []
205
- @template_values << kv.split("=", 2)
189
+ next kv.split("=", 2)
206
190
  end
207
191
 
208
192
  option "--workdir", "WORKDIR",
@@ -220,16 +204,6 @@ class FPM::Command < Clamp::Command
220
204
  klass.apply_options(self)
221
205
  end
222
206
 
223
- # TODO(sissel): expose 'option' and 'parameter' junk to FPM::Package and subclasses.
224
- # Apply those things to this command.
225
- #
226
- # Add extra flags from plugins
227
- #FPM::Package::Gem.flags(FPM::Flags.new(opts, "gem", "gem only"), @settings)
228
- #FPM::Package::Python.flags(FPM::Flags.new(opts, "python", "python only"),
229
- #@settings)
230
- #FPM::Package::Deb.flags(FPM::Flags.new(opts, "deb", "deb only"), @settings)
231
- #FPM::Package::Rpm.flags(FPM::Flags.new(opts, "rpm", "rpm only"), @settings)
232
-
233
207
  # A new FPM::Command
234
208
  def initialize(*args)
235
209
  super(*args)
@@ -239,7 +213,6 @@ class FPM::Command < Clamp::Command
239
213
  @dependencies = []
240
214
  @config_files = []
241
215
  @directories = []
242
- @excludes = []
243
216
  end # def initialize
244
217
 
245
218
  # Execute this command. See Clamp::Command#execute and Clamp's documentation
@@ -406,8 +379,8 @@ class FPM::Command < Clamp::Command
406
379
  output = input.convert(output_class)
407
380
 
408
381
  # Provide any template values as methods on the package.
409
- if !@template_values.nil?
410
- @template_values.each do |key, value|
382
+ if template_scripts?
383
+ template_value_list.each do |key, value|
411
384
  (class << output; self; end).send(:define_method, key) { value }
412
385
  end
413
386
  end
@@ -93,9 +93,6 @@ class FPM::Package
93
93
  # (Not all packages support this)
94
94
  attr_accessor :replaces
95
95
 
96
- # Array of glob patterns to exclude from this package
97
- attr_accessor :excludes
98
-
99
96
  # a summary or description of the package
100
97
  attr_accessor :description
101
98
 
@@ -315,8 +312,11 @@ class FPM::Package
315
312
  .collect { |path| path[staging_path.length + 1.. -1] }
316
313
  end # def files
317
314
 
315
+ def template_dir
316
+ File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "templates"))
317
+ end
318
+
318
319
  def template(path)
319
- template_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "templates"))
320
320
  template_path = File.join(template_dir, path)
321
321
  template_code = File.read(template_path)
322
322
  @logger.info("Reading template", :path => template_path)
@@ -0,0 +1,190 @@
1
+ require "fpm/namespace"
2
+ require "fpm/package"
3
+ require "fpm/util"
4
+ require "fileutils"
5
+ require "find"
6
+
7
+ class FPM::Package::CPAN < FPM::Package
8
+ # Flags '--foo' will be accessable as attributes[:npm_foo]
9
+ option "--perl-bin", "PERL_EXECUTABLE",
10
+ "The path to the perl executable you wish to run.", :default => "perl"
11
+ option "--cpanm-bin", "CPANM_EXECUTABLE",
12
+ "The path to the cpanm executable you wish to run.", :default => "cpanm"
13
+ option "--package-name-prefix", "NAME_PREFIX", "Name to prefix the package " \
14
+ "name with.", :default => "perl"
15
+ option "--test", :flag, "Run the tests before packaging?", :default => true
16
+
17
+ private
18
+ def input(package)
19
+ require "ftw" # for http access
20
+ require "json"
21
+
22
+ agent = FTW::Agent.new
23
+ result = search(package, agent)
24
+ tarball = download(result, agent)
25
+ moduledir = unpack(tarball)
26
+
27
+ # Read package metadata (name, version, etc)
28
+ if File.exists?(File.join(moduledir, "META.json"))
29
+ metadata = JSON.parse(File.read(File.join(moduledir, ("META.json"))))
30
+ elsif File.exists?(File.join(moduledir, ("META.yml")))
31
+ require "yaml"
32
+ metadata = YAML.load_file(File.join(moduledir, ("META.yml")))
33
+ else
34
+ raise FPM::InvalidPackageConfiguration,
35
+ "Could not find package metadata. Checked for META.json and META.yml"
36
+ end
37
+ self.version = metadata["version"]
38
+ self.description = metadata["abstract"]
39
+
40
+ self.license = case metadata["license"]
41
+ when Array; metadata["license"].first
42
+ else; metadata["license"]
43
+ end
44
+
45
+ if metadata.include?("distribution")
46
+ @logger.info("Setting package name from 'distribution'",
47
+ :distribution => metadata["distribution"])
48
+ self.name = fix_name(metadata["distribution"])
49
+ else
50
+ @logger.info("Setting package name from 'name'",
51
+ :name => metadata["name"])
52
+ self.name = fix_name(metadata["name"])
53
+ end
54
+
55
+ # Not all things have 'author' listed.
56
+ self.vendor = metadata["author"].join(", ") if metadata.include?("author")
57
+ self.url = metadata["resources"]["homepage"] rescue "unknown"
58
+
59
+ # TODO(sissel): figure out if this perl module compiles anything
60
+ # and set the architecture appropriately.
61
+ self.architecture = "all"
62
+
63
+ # Install any build/configure dependencies with cpanm.
64
+ # We'll install to a temporary directory.
65
+ @logger.info("Installing any build or configure dependencies")
66
+ safesystem(attributes[:cpan_cpanm_bin], "-L", build_path("cpan"), moduledir)
67
+
68
+ if !attributes[:no_auto_depends?]
69
+ if metadata.include?("requires")
70
+ metadata["requires"].each do |dep_name, version|
71
+ # Special case for representing perl core as a version.
72
+ if dep_name == "perl"
73
+ self.dependencies << "#{dep_name} >= #{version}"
74
+ next
75
+ end
76
+ dep = search(dep_name, agent)
77
+
78
+ if dep.include?("distribution")
79
+ name = fix_name(dep["distribution"])
80
+ else
81
+ name = fix_name(dep_name)
82
+ end
83
+
84
+ if version.to_s == "0"
85
+ # Assume 'Foo = 0' means any version?
86
+ self.dependencies << "#{name}"
87
+ else
88
+ self.dependencies << "#{name} = #{version}"
89
+ end
90
+ end
91
+ end
92
+ end #no_auto_depends
93
+
94
+ ::Dir.chdir(moduledir) do
95
+ # TODO(sissel): install build and config dependencies to resolve
96
+ # build/configure requirements.
97
+ # META.yml calls it 'configure_requires' and 'build_requires'
98
+ # META.json calls it prereqs/build and prereqs/configure
99
+
100
+ prefix = attributes[:prefix] || "/usr/local"
101
+ # TODO(sissel): Set default INSTALL path?
102
+ # perl -e 'use Config; print "$Config{sitelib}"'
103
+ safesystem(attributes[:cpan_perl_bin],
104
+ "-Mlocal::lib=#{build_path("cpan")}",
105
+ "Makefile.PL",
106
+ "PREFIX=#{prefix}",
107
+ # Have to specify INSTALL_BASE as empty otherwise
108
+ # Makefile.PL lies and claims we've set both PREFIX and
109
+ # INSTALL_BASE.
110
+ "INSTALL_BASE="
111
+ )
112
+
113
+ make = [ "make" ]
114
+
115
+ safesystem(*make)
116
+ safesystem(*(make + ["test"])) if attributes[:cpan_test?]
117
+ safesystem(*(make + ["DESTDIR=#{staging_path}", "install"]))
118
+ end
119
+
120
+ # TODO(sissel): figure out if this perl module compiles anything
121
+ # and set the architecture appropriately.
122
+ self.architecture = "all"
123
+
124
+ # Find any shared objects in the staging directory to set architecture as
125
+ # native if found; otherwise keep the 'all' default.
126
+ Find.find(staging_path) do |path|
127
+ if path =~ /\.so$/
128
+ @logger.info("Found shared library, setting architecture=native",
129
+ :path => path)
130
+ self.architecture = "native"
131
+ end
132
+ end
133
+ end
134
+
135
+ def unpack(tarball)
136
+ directory = build_path("module")
137
+ ::Dir.mkdir(directory)
138
+ args = [ "-C", directory, "-zxf", tarball,
139
+ "--strip-components", "1" ]
140
+ safesystem("tar", *args)
141
+ return directory
142
+ end
143
+
144
+ def download(metadata, agent)
145
+ distribution = metadata["distribution"]
146
+ author = metadata["author"]
147
+ @logger.info("Downloading perl module",
148
+ :distribution => distribution,
149
+ :version => version)
150
+
151
+ # default to latest versionunless we specify one
152
+ version = metadata["version"] if version.nil?
153
+
154
+ tarball = "#{distribution}-#{version}.tar.gz"
155
+ url = "http://search.cpan.org/CPAN/authors/id/#{author[0,1]}/#{author[0,2]}/#{author}/#{tarball}"
156
+ response = agent.get!(url)
157
+ if response.error?
158
+ @logger.error("Download failed", :error => response.status_line,
159
+ :url => url)
160
+ raise FPM::InvalidPackageConfiguration, "metacpan query failed"
161
+ end
162
+
163
+ File.open(build_path(tarball), "w") do |fd|
164
+ response.read_body { |c| fd.write(c) }
165
+ end
166
+ return build_path(tarball)
167
+ end # def download
168
+
169
+ def search(package, agent)
170
+ @logger.info("Asking metacpan about a module", :module => package)
171
+ metacpan_url = "http://api.metacpan.org/v0/module/" + package
172
+ response = agent.get!(metacpan_url)
173
+ if response.error?
174
+ @logger.error("metacpan query failed.", :error => response.status_line,
175
+ :module => package, :url => metacpan_url)
176
+ raise FPM::InvalidPackageConfiguration, "metacpan query failed"
177
+ end
178
+
179
+ data = ""
180
+ response.read_body { |c| data << c }
181
+ metadata = JSON.parse(data)
182
+ return metadata
183
+ end # def metadata
184
+
185
+ def fix_name(name)
186
+ return [attributes[:cpan_package_name_prefix], name].join("-").gsub("::", "-")
187
+ end # def fix_name
188
+
189
+ public(:input)
190
+ end # class FPM::Package::NPM
@@ -126,9 +126,14 @@ class FPM::Package::Deb < FPM::Package
126
126
  @architecture = %x{uname -m}.chomp
127
127
  end
128
128
  end
129
- if @architecture == "x86_64"
129
+
130
+ case @architecture
131
+ when "x86_64"
130
132
  # Debian calls x86_64 "amd64"
131
133
  @architecture = "amd64"
134
+ when "noarch"
135
+ # Debian calls noarch "all"
136
+ @architecture = "all"
132
137
  end
133
138
  return @architecture
134
139
  end # def architecture
@@ -432,7 +437,7 @@ class FPM::Package::Deb < FPM::Package
432
437
  # Make the control.tar.gz
433
438
  with(build_path("control.tar.gz")) do |controltar|
434
439
  @logger.info("Creating", :path => controltar, :from => control_path)
435
- safesystem(tar_cmd, "--numeric-owner", "--owner=0", "--group=0", "-zcf",
440
+ safesystem(tar_cmd, "--owner=root", "--group=root", "-zcf",
436
441
  controltar, "-C", control_path, ".")
437
442
  end
438
443
 
@@ -4,33 +4,95 @@ require "fpm/util"
4
4
  require "fileutils"
5
5
 
6
6
  class FPM::Package::NPM < FPM::Package
7
- def get_source(params)
8
- @npm = @paths.first
9
- end # def get_source
10
-
11
- def download(npm_name, version=nil)
12
- end # def download
13
-
14
- def get_metadata
15
- # set self[:...] values
16
- # :name
17
- # :maintainer
18
- # :url
19
- # :category
20
- # :dependencies
21
- end # def get_metadata
22
-
23
- def make_tarball!(tar_path, builddir)
24
- tmpdir = "#{tar_path}.dir"
25
- installdir = "#{tmpdir}/#{::Gem::dir}"
26
- ::FileUtils.mkdir_p(installdir)
27
- args = ["gem", "install", "--quiet", "--no-ri", "--no-rdoc",
28
- "--install-dir", installdir, "--ignore-dependencies", @paths.first]
29
- safesystem(*args)
30
- tar(tar_path, ".", tmpdir)
31
-
32
- # TODO(sissel): Make a helper method.
33
- safesystem(*["gzip", "-f", tar_path])
7
+ # Flags '--foo' will be accessable as attributes[:npm_foo]
8
+ option "--bin", "NPM_EXECUTABLE",
9
+ "The path to the npm executable you wish to run.", :default => "npm"
10
+
11
+ option "--package-name-prefix", "PREFIX", "Name to prefix the package " \
12
+ "name with.", :default => "node"
13
+
14
+ private
15
+ def input(package)
16
+ # Notes:
17
+ # * npm respects PREFIX
18
+ settings = {
19
+ "cache" => build_path("npm_cache"),
20
+ "loglevel" => "warn",
21
+ "global" => "true"
22
+ }
23
+
24
+ if attributes.include?(:prefix) && !attributes[:prefix].nil?
25
+ settings["prefix"] = staging_path(attributes[:prefix])
26
+ else
27
+ @logger.info("Setting default npm install prefix",
28
+ :prefix => "/usr/local")
29
+ settings["prefix"] = staging_path("/usr/local")
30
+ end
31
+
32
+ FileUtils.mkdir_p(settings["prefix"])
33
+
34
+ npm_flags = []
35
+ settings.each do |key, value|
36
+ # npm lets you specify settings in a .npmrc but the same key=value settings
37
+ # are valid as '--key value' command arguments to npm. Woo!
38
+ @logger.debug("Configuring npm", key => value)
39
+ npm_flags += [ "--#{key}", value ]
40
+ end
41
+
42
+ install_args = [
43
+ attributes[:npm_bin],
44
+ "install",
45
+ # use 'package' or 'package@version'
46
+ (version ? "#{package}@#{version}" : package)
47
+ ]
48
+
49
+ # The only way to get npm to respect the 'prefix' setting appears to
50
+ # be to set the --global flag.
51
+ #install_args << "--global"
52
+ install_args += npm_flags
53
+
54
+ safesystem(*install_args)
55
+
56
+ # Query details about our now-installed package.
57
+ # We do this by using 'npm ls' with json + long enabled to query details
58
+ # about the installed package.
59
+ npm_ls_cmd = [attributes[:npm_bin], "ls", "--json", "--long", package] + npm_flags
60
+ npm_ls_fd = IO.popen(npm_ls_cmd)
61
+ npm_ls_out = npm_ls_fd.read()
62
+ pid, status = Process.waitpid2(npm_ls_fd.pid)
63
+ if !status.success?
64
+ raise FPM::Util::ProcessFailed.new("#{npm_ls_cmd.first} failed (exit " \
65
+ "code #{status.exitstatus}). " \
66
+ "Full command was: #{npm_ls_cmd.inspect}")
67
+ end
68
+ npm_ls = JSON.parse(npm_ls_out)
69
+ name, info = npm_ls["dependencies"].first
70
+
71
+ self.name = [attributes[:npm_package_name_prefix], name].join("-")
72
+ self.version = info["version"]
73
+
74
+ if info.include?("repository")
75
+ self.url = info["repository"]["url"]
76
+ else
77
+ self.url = "https://npmjs.org/package/#{self.name}"
78
+ end
79
+
80
+ self.description = info["description"]
81
+ self.vendor = sprintf("%s <%s>", info["author"]["name"],
82
+ info["author"]["email"])
83
+
84
+ # npm installs dependencies in the module itself, so if you do
85
+ # 'npm install express' it installs dependencies (like 'connect')
86
+ # to: node_modules/express/node_modules/connect/...
87
+ #
88
+ # To that end, I don't think we necessarily need to include
89
+ # any automatic dependency information since every 'npm install'
90
+ # is fully self-contained. That's why you don't see any bother, yet,
91
+ # to include the package's dependencies in here.
92
+ #
93
+ # It's possible someone will want to decouple that in the future,
94
+ # but I will wait for that feature request.
34
95
  end
35
96
 
97
+ public(:input)
36
98
  end # class FPM::Package::NPM