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

⌈⌋ ⎇ branch:  cross package maker


Check-in [d84b9cf55e]

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

Overview
Comment:Move from @logger ivar to logger method. Tests pass. Hope is all we have. And hugs.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: d84b9cf55ea2c2aca1b24ce15ed2c58750d67858
User & Date: jls@semicomplete.com 2014-10-25 05:28:16
Context
2014-10-25
05:36
Add input and output rpm trigger support This adds several rpm-specific flags: --before-install --after-install --before-uninstall --after-target-uninstall Fixes #626 (merged by hand) check-in: 4e74257a17 user: jls@semicomplete.com tags: trunk
05:28
Move from @logger ivar to logger method. Tests pass. Hope is all we have. And hugs. check-in: d84b9cf55e user: jls@semicomplete.com tags: trunk
05:27
Merge pull request #776 from Tapjoy/feature/sh_template_enhancements Enhancements to sh template check-in: 2f31b121c8 user: jls@semicomplete.com tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to lib/fpm/command.rb.

250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
  def execute
    # Short-circuit if someone simply runs `fpm --version`
    if ARGV == [ "--version" ]
      puts FPM::VERSION
      return 0
    end

    @logger.level = :warn
    @logger.level = :info if verbose? # --verbose
    @logger.level = :debug if debug? # --debug
    if log_level
      @logger.level = log_level.to_sym
    end


    if (stray_flags = args.grep(/^-/); stray_flags.any?)
      @logger.warn("All flags should be before the first argument " \
                   "(stray flags found: #{stray_flags}")
    end

    # Some older behavior, if you specify:
    #   'fpm -s dir -t ... -C somepath'
    # fpm would assume you meant to add '.' to the end of the commandline.
    # Let's hack that. https://github.com/jordansissel/fpm/issues/187
    if input_type == "dir" and args.empty? and !chdir.nil?
      @logger.info("No args, but -s dir and -C are given, assuming '.' as input") 
      args << "."
    end

    @logger.info("Setting workdir", :workdir => workdir)
    ENV["TMP"] = workdir

    validator = Validator.new(self)
    if !validator.ok?
      validator.messages.each do |message|
        @logger.warn(message)
      end

      @logger.fatal("Fix the above problems, and you'll be rolling packages in no time!")
      return 1
    end
    input_class = FPM::Package.types[input_type]
    output_class = FPM::Package.types[output_type]

    input = input_class.new








|
|
|

|




|








|



|





|


|







250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
  def execute
    # Short-circuit if someone simply runs `fpm --version`
    if ARGV == [ "--version" ]
      puts FPM::VERSION
      return 0
    end

    logger.level = :warn
    logger.level = :info if verbose? # --verbose
    logger.level = :debug if debug? # --debug
    if log_level
      logger.level = log_level.to_sym
    end


    if (stray_flags = args.grep(/^-/); stray_flags.any?)
      logger.warn("All flags should be before the first argument " \
                   "(stray flags found: #{stray_flags}")
    end

    # Some older behavior, if you specify:
    #   'fpm -s dir -t ... -C somepath'
    # fpm would assume you meant to add '.' to the end of the commandline.
    # Let's hack that. https://github.com/jordansissel/fpm/issues/187
    if input_type == "dir" and args.empty? and !chdir.nil?
      logger.info("No args, but -s dir and -C are given, assuming '.' as input") 
      args << "."
    end

    logger.info("Setting workdir", :workdir => workdir)
    ENV["TMP"] = workdir

    validator = Validator.new(self)
    if !validator.ok?
      validator.messages.each do |message|
        logger.warn(message)
      end

      logger.fatal("Fix the above problems, and you'll be rolling packages in no time!")
      return 1
    end
    input_class = FPM::Package.types[input_type]
    output_class = FPM::Package.types[output_type]

    input = input_class.new

316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
       
        # If the instance variable @{attr} is defined, then
        # it means the flag was given on the command line.
        flag_given = instance_variable_defined?("@#{attr}")
        input.attributes["#{attr}_given?".to_sym] = flag_given
        attr = "#{attr}?" if !respond_to?(attr) # handle boolean :flag cases
        input.attributes[attr.to_sym] = send(attr) if respond_to?(attr)
        @logger.debug("Setting attribute", attr.to_sym => send(attr))
      end
    end

    # Each remaining command line parameter is used as an 'input' argument.
    # For directories, this means paths. For things like gem and python, this
    # means package name or paths to the packages (rails, foo-1.0.gem, django,
    # bar/setup.py, etc)
    args.each do |arg| 
      input.input(arg) 
    end

    # If --inputs was specified, read it as a file.
    if !inputs.nil?
      if !File.exists?(inputs)
        @logger.fatal("File given for --inputs does not exist (#{inputs})")
        return 1
      end

      # Read each line as a path
      File.new(inputs, "r").each_line do |line| 
        # Handle each line as if it were an argument
        input.input(line.strip)
      end
    end

    # Override package settings if they are not the default flag values
    # the below proc essentially does:
    #
    # if someflag != default_someflag
    #   input.someflag = someflag
    # end
    set = proc do |object, attribute|
      # if the package's attribute is currently nil *or* the flag setting for this
      # attribute is non-default, use the value.
      if object.send(attribute).nil? || send(attribute) != send("default_#{attribute}")
        @logger.info("Setting from flags: #{attribute}=#{send(attribute)}")
        object.send("#{attribute}=", send(attribute))
      end
    end
    set.call(input, :architecture)
    set.call(input, :category)
    set.call(input, :description)
    set.call(input, :epoch)







|














|




















|







316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
       
        # If the instance variable @{attr} is defined, then
        # it means the flag was given on the command line.
        flag_given = instance_variable_defined?("@#{attr}")
        input.attributes["#{attr}_given?".to_sym] = flag_given
        attr = "#{attr}?" if !respond_to?(attr) # handle boolean :flag cases
        input.attributes[attr.to_sym] = send(attr) if respond_to?(attr)
        logger.debug("Setting attribute", attr.to_sym => send(attr))
      end
    end

    # Each remaining command line parameter is used as an 'input' argument.
    # For directories, this means paths. For things like gem and python, this
    # means package name or paths to the packages (rails, foo-1.0.gem, django,
    # bar/setup.py, etc)
    args.each do |arg| 
      input.input(arg) 
    end

    # If --inputs was specified, read it as a file.
    if !inputs.nil?
      if !File.exists?(inputs)
        logger.fatal("File given for --inputs does not exist (#{inputs})")
        return 1
      end

      # Read each line as a path
      File.new(inputs, "r").each_line do |line| 
        # Handle each line as if it were an argument
        input.input(line.strip)
      end
    end

    # Override package settings if they are not the default flag values
    # the below proc essentially does:
    #
    # if someflag != default_someflag
    #   input.someflag = someflag
    # end
    set = proc do |object, attribute|
      # if the package's attribute is currently nil *or* the flag setting for this
      # attribute is non-default, use the value.
      if object.send(attribute).nil? || send(attribute) != send("default_#{attribute}")
        logger.info("Setting from flags: #{attribute}=#{send(attribute)}")
        object.send("#{attribute}=", send(attribute))
      end
    end
    set.call(input, :architecture)
    set.call(input, :category)
    set.call(input, :description)
    set.call(input, :epoch)
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
      # 'self.send(scriptname) == self.before_install == --before-install
      # Gets the path to the script
      path = self.send(scriptname)
      # Skip scripts not set
      next if path.nil?

      if !File.exists?(path)
        @logger.error("No such file (for #{scriptname.to_s}): #{path.inspect}")
        script_errors << path
      end

      # Load the script into memory.
      input.scripts[scriptname] = File.read(path)
    end

    setscript.call(:before_install)
    setscript.call(:after_install)
    setscript.call(:before_remove)
    setscript.call(:after_remove)
    setscript.call(:before_upgrade)
    setscript.call(:after_upgrade)

    # Bail if any setscript calls had errors. We don't need to log
    # anything because we've already logged the error(s) above.
    return 1 if script_errors.any?

    # Validate the package
    if input.name.nil? or input.name.empty?
      @logger.fatal("No name given for this package (set name with, " \
                    "for example, '-n packagename')")
      return 1
    end

    # Convert to the output type
    output = input.convert(output_class)








|




















|







395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
      # 'self.send(scriptname) == self.before_install == --before-install
      # Gets the path to the script
      path = self.send(scriptname)
      # Skip scripts not set
      next if path.nil?

      if !File.exists?(path)
        logger.error("No such file (for #{scriptname.to_s}): #{path.inspect}")
        script_errors << path
      end

      # Load the script into memory.
      input.scripts[scriptname] = File.read(path)
    end

    setscript.call(:before_install)
    setscript.call(:after_install)
    setscript.call(:before_remove)
    setscript.call(:after_remove)
    setscript.call(:before_upgrade)
    setscript.call(:after_upgrade)

    # Bail if any setscript calls had errors. We don't need to log
    # anything because we've already logged the error(s) above.
    return 1 if script_errors.any?

    # Validate the package
    if input.name.nil? or input.name.empty?
      logger.fatal("No name given for this package (set name with, " \
                    "for example, '-n packagename')")
      return 1
    end

    # Convert to the output type
    output = input.convert(output_class)

445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
    else
      package_file = output.to_s(package)
    end

    begin
      output.output(package_file)
    rescue FPM::Package::FileAlreadyExists => e
      @logger.fatal(e.message)
      return 1
    rescue FPM::Package::ParentDirectoryMissing => e
      @logger.fatal(e.message)
      return 1
    end

    @logger.log("Created package", :path => package_file)
    return 0
  rescue FPM::Util::ExecutableNotFound => e
    @logger.error("Need executable '#{e}' to convert #{input_type} to #{output_type}")
    return 1
  rescue FPM::InvalidPackageConfiguration => e
    @logger.error("Invalid package configuration: #{e}")
    return 1
  rescue FPM::Util::ProcessFailed => e
    @logger.error("Process failed: #{e}")
    return 1
  ensure
    if debug_workspace?
      # only emit them if they have files
      [input, output].each do |plugin|
        next if plugin.nil?
        [:staging_path, :build_path].each do |pathtype|
          path = plugin.send(pathtype)
          next unless Dir.open(path).to_a.size > 2
          @logger.log("plugin directory", :plugin => plugin.type, :pathtype => pathtype, :path => path)
        end
      end
    else
      input.cleanup unless input.nil?
      output.cleanup unless output.nil?
    end
  end # def execute

  def run(*args)
    @logger = Cabin::Channel.get
    @logger.subscribe(STDOUT)

    # fpm initialization files, note the order of the following array is
    # important, try .fpm in users home directory first and then the current
    # directory
    rc_files = [ ".fpm" ]
    rc_files << File.join(ENV["HOME"], ".fpm") if ENV["HOME"]

    rc_files.each do |rc_file|
      if File.readable? rc_file
        @logger.warn("Loading flags from rc file #{rc_file}")
        File.readlines(rc_file).each do |line|
          # reverse becasue 'unshift' pushes onto the left side of the array.
          Shellwords.shellsplit(line).reverse.each do |arg|
            # Put '.fpm'-file flags *before* the command line flags
            # so that we the CLI can override the .fpm flags
            ARGV.unshift(arg)
          end
        end
      end
    end

    super(*args)
  rescue FPM::Package::InvalidArgument => e
    @logger.error("Invalid package argument: #{e}")
    return 1
  end # def run

  # A simple flag validator
  #
  # The goal of this class is to ensure the flags and arguments given
  # are a valid configuration.







|


|



|


|


|


|









|









<
|









|













|







445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487

488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
    else
      package_file = output.to_s(package)
    end

    begin
      output.output(package_file)
    rescue FPM::Package::FileAlreadyExists => e
      logger.fatal(e.message)
      return 1
    rescue FPM::Package::ParentDirectoryMissing => e
      logger.fatal(e.message)
      return 1
    end

    logger.log("Created package", :path => package_file)
    return 0
  rescue FPM::Util::ExecutableNotFound => e
    logger.error("Need executable '#{e}' to convert #{input_type} to #{output_type}")
    return 1
  rescue FPM::InvalidPackageConfiguration => e
    logger.error("Invalid package configuration: #{e}")
    return 1
  rescue FPM::Util::ProcessFailed => e
    logger.error("Process failed: #{e}")
    return 1
  ensure
    if debug_workspace?
      # only emit them if they have files
      [input, output].each do |plugin|
        next if plugin.nil?
        [:staging_path, :build_path].each do |pathtype|
          path = plugin.send(pathtype)
          next unless Dir.open(path).to_a.size > 2
          logger.log("plugin directory", :plugin => plugin.type, :pathtype => pathtype, :path => path)
        end
      end
    else
      input.cleanup unless input.nil?
      output.cleanup unless output.nil?
    end
  end # def execute

  def run(*args)

    logger.subscribe(STDOUT)

    # fpm initialization files, note the order of the following array is
    # important, try .fpm in users home directory first and then the current
    # directory
    rc_files = [ ".fpm" ]
    rc_files << File.join(ENV["HOME"], ".fpm") if ENV["HOME"]

    rc_files.each do |rc_file|
      if File.readable? rc_file
        logger.warn("Loading flags from rc file #{rc_file}")
        File.readlines(rc_file).each do |line|
          # reverse becasue 'unshift' pushes onto the left side of the array.
          Shellwords.shellsplit(line).reverse.each do |arg|
            # Put '.fpm'-file flags *before* the command line flags
            # so that we the CLI can override the .fpm flags
            ARGV.unshift(arg)
          end
        end
      end
    end

    super(*args)
  rescue FPM::Package::InvalidArgument => e
    logger.error("Invalid package argument: #{e}")
    return 1
  end # def run

  # A simple flag validator
  #
  # The goal of this class is to ensure the flags and arguments given
  # are a valid configuration.

Changes to lib/fpm/package/cpan.rb.

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
    self.license = case metadata["license"]
      when Array; metadata["license"].first
      when nil; "unknown"
      else; metadata["license"]
    end

    unless metadata["distribution"].nil?
      @logger.info("Setting package name from 'distribution'",
                   :distribution => metadata["distribution"])
      self.name = fix_name(metadata["distribution"])
    else
      @logger.info("Setting package name from 'name'",
                   :name => metadata["name"])
      self.name = fix_name(metadata["name"])
    end

    # author is not always set or it may be a string instead of an array
    self.vendor = case metadata["author"]
      when String; metadata["author"]
      when Array; metadata["author"].join(", ")
      else
        raise FPM::InvalidPackageConfiguration, "Unexpected CPAN 'author' field type: #{metadata["author"].class}. This is a bug."
    end if metadata.include?("author")

    self.url = metadata["resources"]["homepage"] rescue "unknown"

    # TODO(sissel): figure out if this perl module compiles anything
    # and set the architecture appropriately.
    self.architecture = "all"

    # Install any build/configure dependencies with cpanm.
    # We'll install to a temporary directory.
    @logger.info("Installing any build or configure dependencies")

    cpanm_flags = ["-L", build_path("cpan"), moduledir]
    cpanm_flags += ["-n"] if attributes[:cpan_test?]
    cpanm_flags += ["--mirror", "#{attributes[:cpan_mirror]}"] if !attributes[:cpan_mirror].nil?
    cpanm_flags += ["--mirror-only"] if attributes[:cpan_mirror_only?] && !attributes[:cpan_mirror].nil?

    safesystem(attributes[:cpan_cpanm_bin], *cpanm_flags)







|



|




















|







69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
    self.license = case metadata["license"]
      when Array; metadata["license"].first
      when nil; "unknown"
      else; metadata["license"]
    end

    unless metadata["distribution"].nil?
      logger.info("Setting package name from 'distribution'",
                   :distribution => metadata["distribution"])
      self.name = fix_name(metadata["distribution"])
    else
      logger.info("Setting package name from 'name'",
                   :name => metadata["name"])
      self.name = fix_name(metadata["name"])
    end

    # author is not always set or it may be a string instead of an array
    self.vendor = case metadata["author"]
      when String; metadata["author"]
      when Array; metadata["author"].join(", ")
      else
        raise FPM::InvalidPackageConfiguration, "Unexpected CPAN 'author' field type: #{metadata["author"].class}. This is a bug."
    end if metadata.include?("author")

    self.url = metadata["resources"]["homepage"] rescue "unknown"

    # TODO(sissel): figure out if this perl module compiles anything
    # and set the architecture appropriately.
    self.architecture = "all"

    # Install any build/configure dependencies with cpanm.
    # We'll install to a temporary directory.
    logger.info("Installing any build or configure dependencies")

    cpanm_flags = ["-L", build_path("cpan"), moduledir]
    cpanm_flags += ["-n"] if attributes[:cpan_test?]
    cpanm_flags += ["--mirror", "#{attributes[:cpan_mirror]}"] if !attributes[:cpan_mirror].nil?
    cpanm_flags += ["--mirror-only"] if attributes[:cpan_mirror_only?] && !attributes[:cpan_mirror].nil?

    safesystem(attributes[:cpan_cpanm_bin], *cpanm_flags)
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324

      # Fix any files likely to cause conflicts that are duplicated
      # across packages.
      # https://github.com/jordansissel/fpm/issues/443
      # https://github.com/jordansissel/fpm/issues/510
      glob_prefix = attributes[:cpan_perl_lib_path] || prefix
      ::Dir.glob(File.join(staging_path, glob_prefix, "**/perllocal.pod")).each do |path|
        @logger.debug("Removing useless file.",
                      :path => path.gsub(staging_path, ""))
        File.unlink(path)
      end
    end


    # TODO(sissel): figure out if this perl module compiles anything
    # and set the architecture appropriately.
    self.architecture = "all"

    # Find any shared objects in the staging directory to set architecture as
    # native if found; otherwise keep the 'all' default.
    Find.find(staging_path) do |path|
      if path =~ /\.so$/  
        @logger.info("Found shared library, setting architecture=native",
                     :path => path)
        self.architecture = "native" 
      end
    end
  end

  def unpack(tarball)
    directory = build_path("module")
    ::Dir.mkdir(directory)
    args = [ "-C", directory, "-zxf", tarball,
      "--strip-components", "1" ]
    safesystem("tar", *args)
    return directory
  end

  def download(metadata, cpan_version=nil)
    distribution = metadata["distribution"]
    author = metadata["author"]

    @logger.info("Downloading perl module",
                 :distribution => distribution,
                 :version => cpan_version)

    # default to latest versionunless we specify one
    if cpan_version.nil?
      self.version = metadata["version"]
    else
      if metadata["version"] =~ /^v\d/
        self.version = "v#{cpan_version}"
      else
        self.version = cpan_version
      end
    end

    metacpan_release_url = "http://api.metacpan.org/v0/release/#{author}/#{distribution}-#{self.version}"
    begin
      release_response = httpfetch(metacpan_release_url)
    rescue Net::HTTPServerException => e
      @logger.error("metacpan release query failed.", :error => e.message,
                    :module => package, :url => metacpan_release_url)
      raise FPM::InvalidPackageConfiguration, "metacpan release query failed"
    end

    data = release_response.body
    release_metadata = JSON.parse(data)
    archive = release_metadata["archive"]

    # should probably be basepathed from the url 
    tarball = File.basename(archive)

    url_base = "http://www.cpan.org/"
    url_base = "#{attributes[:cpan_mirror]}" if !attributes[:cpan_mirror].nil?

    #url = "http://www.cpan.org/CPAN/authors/id/#{author[0,1]}/#{author[0,2]}/#{author}/#{tarball}"
    url = "#{url_base}/authors/id/#{author[0,1]}/#{author[0,2]}/#{author}/#{archive}"
    @logger.debug("Fetching perl module", :url => url)
    
    begin
      response = httpfetch(url)
    rescue Net::HTTPServerException => e
      #@logger.error("Download failed", :error => response.status_line,
                    #:url => url)
      @logger.error("Download failed", :error => e, :url => url)
      raise FPM::InvalidPackageConfiguration, "metacpan query failed"
    end

    File.open(build_path(tarball), "w") do |fd|
      #response.read_body { |c| fd.write(c) }
      fd.write(response.body)
    end
    return build_path(tarball)
  end # def download

  def search(package)
    @logger.info("Asking metacpan about a module", :module => package)
    metacpan_url = "http://api.metacpan.org/v0/module/" + package
    begin
      response = httpfetch(metacpan_url)
    rescue Net::HTTPServerException => e
      #@logger.error("metacpan query failed.", :error => response.status_line,
                    #:module => package, :url => metacpan_url)
      @logger.error("metacpan query failed.", :error => e.message,
                    :module => package, :url => metacpan_url)
      raise FPM::InvalidPackageConfiguration, "metacpan query failed"
    end

    #data = ""
    #response.read_body { |c| p c; data << c }
    data = response.body







|














|



















|


















|
















|




|

|











|




|

|







213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324

      # Fix any files likely to cause conflicts that are duplicated
      # across packages.
      # https://github.com/jordansissel/fpm/issues/443
      # https://github.com/jordansissel/fpm/issues/510
      glob_prefix = attributes[:cpan_perl_lib_path] || prefix
      ::Dir.glob(File.join(staging_path, glob_prefix, "**/perllocal.pod")).each do |path|
        logger.debug("Removing useless file.",
                      :path => path.gsub(staging_path, ""))
        File.unlink(path)
      end
    end


    # TODO(sissel): figure out if this perl module compiles anything
    # and set the architecture appropriately.
    self.architecture = "all"

    # Find any shared objects in the staging directory to set architecture as
    # native if found; otherwise keep the 'all' default.
    Find.find(staging_path) do |path|
      if path =~ /\.so$/  
        logger.info("Found shared library, setting architecture=native",
                     :path => path)
        self.architecture = "native" 
      end
    end
  end

  def unpack(tarball)
    directory = build_path("module")
    ::Dir.mkdir(directory)
    args = [ "-C", directory, "-zxf", tarball,
      "--strip-components", "1" ]
    safesystem("tar", *args)
    return directory
  end

  def download(metadata, cpan_version=nil)
    distribution = metadata["distribution"]
    author = metadata["author"]

    logger.info("Downloading perl module",
                 :distribution => distribution,
                 :version => cpan_version)

    # default to latest versionunless we specify one
    if cpan_version.nil?
      self.version = metadata["version"]
    else
      if metadata["version"] =~ /^v\d/
        self.version = "v#{cpan_version}"
      else
        self.version = cpan_version
      end
    end

    metacpan_release_url = "http://api.metacpan.org/v0/release/#{author}/#{distribution}-#{self.version}"
    begin
      release_response = httpfetch(metacpan_release_url)
    rescue Net::HTTPServerException => e
      logger.error("metacpan release query failed.", :error => e.message,
                    :module => package, :url => metacpan_release_url)
      raise FPM::InvalidPackageConfiguration, "metacpan release query failed"
    end

    data = release_response.body
    release_metadata = JSON.parse(data)
    archive = release_metadata["archive"]

    # should probably be basepathed from the url 
    tarball = File.basename(archive)

    url_base = "http://www.cpan.org/"
    url_base = "#{attributes[:cpan_mirror]}" if !attributes[:cpan_mirror].nil?

    #url = "http://www.cpan.org/CPAN/authors/id/#{author[0,1]}/#{author[0,2]}/#{author}/#{tarball}"
    url = "#{url_base}/authors/id/#{author[0,1]}/#{author[0,2]}/#{author}/#{archive}"
    logger.debug("Fetching perl module", :url => url)
    
    begin
      response = httpfetch(url)
    rescue Net::HTTPServerException => e
      #logger.error("Download failed", :error => response.status_line,
                    #:url => url)
      logger.error("Download failed", :error => e, :url => url)
      raise FPM::InvalidPackageConfiguration, "metacpan query failed"
    end

    File.open(build_path(tarball), "w") do |fd|
      #response.read_body { |c| fd.write(c) }
      fd.write(response.body)
    end
    return build_path(tarball)
  end # def download

  def search(package)
    logger.info("Asking metacpan about a module", :module => package)
    metacpan_url = "http://api.metacpan.org/v0/module/" + package
    begin
      response = httpfetch(metacpan_url)
    rescue Net::HTTPServerException => e
      #logger.error("metacpan query failed.", :error => response.status_line,
                    #:module => package, :url => metacpan_url)
      logger.error("metacpan query failed.", :error => e.message,
                    :module => package, :url => metacpan_url)
      raise FPM::InvalidPackageConfiguration, "metacpan query failed"
    end

    #data = ""
    #response.read_body { |c| p c; data << c }
    data = response.body

Changes to lib/fpm/package/deb.rb.

182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212

  # Get the name of this package. See also FPM::Package#name
  #
  # This accessor actually modifies the name if it has some invalid or unwise
  # characters.
  def name
    if @name =~ /[A-Z]/
      @logger.warn("Debian tools (dpkg/apt) don't do well with packages " \
        "that use capital letters in the name. In some cases it will " \
        "automatically downcase them, in others it will not. It is confusing." \
        " Best to not use any capital letters at all. I have downcased the " \
        "package name for you just to be safe.",
        :oldname => @name, :fixedname => @name.downcase)
      @name = @name.downcase
    end

    if @name.include?("_")
      @logger.info("Debian package names cannot include underscores; " \
                   "automatically converting to dashes", :name => @name)
      @name = @name.gsub(/[_]/, "-")
    end

    if @name.include?(" ")
      @logger.info("Debian package names cannot include spaces; " \
                   "automatically converting to dashes", :name => @name)
      @name = @name.gsub(/[ ]/, "-")
    end

    return @name
  end # def name
  







|









|





|







182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212

  # Get the name of this package. See also FPM::Package#name
  #
  # This accessor actually modifies the name if it has some invalid or unwise
  # characters.
  def name
    if @name =~ /[A-Z]/
      logger.warn("Debian tools (dpkg/apt) don't do well with packages " \
        "that use capital letters in the name. In some cases it will " \
        "automatically downcase them, in others it will not. It is confusing." \
        " Best to not use any capital letters at all. I have downcased the " \
        "package name for you just to be safe.",
        :oldname => @name, :fixedname => @name.downcase)
      @name = @name.downcase
    end

    if @name.include?("_")
      logger.info("Debian package names cannot include underscores; " \
                   "automatically converting to dashes", :name => @name)
      @name = @name.gsub(/[_]/, "-")
    end

    if @name.include?(" ")
      logger.info("Debian package names cannot include spaces; " \
                   "automatically converting to dashes", :name => @name)
      @name = @name.gsub(/[ ]/, "-")
    end

    return @name
  end # def name
  
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
      control = File.read(File.join(path, "control"))

      parse = lambda do |field| 
        value = control[/^#{field.capitalize}: .*/]
        if value.nil?
          return nil
        else
          @logger.info("deb field", field => value.split(": ", 2).last)
          return value.split(": ",2).last
        end
      end

      # Parse 'epoch:version-iteration' in the version string
      version_re = /^(?:([0-9]+):)?(.+?)(?:-(.*))?$/
      m = version_re.match(parse.call("Version"))







|







228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
      control = File.read(File.join(path, "control"))

      parse = lambda do |field| 
        value = control[/^#{field.capitalize}: .*/]
        if value.nil?
          return nil
        else
          logger.info("deb field", field => value.split(": ", 2).last)
          return value.split(": ",2).last
        end
      end

      # Parse 'epoch:version-iteration' in the version string
      version_re = /^(?:([0-9]+):)?(.+?)(?:-(.*))?$/
      m = version_re.match(parse.call("Version"))
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
    # create 'debian-binary' file, required to make a valid debian package
    File.write(build_path("debian-binary"), "2.0\n")

    # If we are given --deb-shlibs but no --after-install script, we
    # should implicitly create a before/after scripts that run ldconfig
    if attributes[:deb_shlibs]
      if !script?(:after_install)
        @logger.info("You gave --deb-shlibs but no --after-install, so " \
                     "I am adding an after-install script that runs " \
                     "ldconfig to update the system library cache")
        scripts[:after_install] = template("deb/ldconfig.sh.erb").result(binding)
      end
      if !script?(:after_remove)
        @logger.info("You gave --deb-shlibs but no --after-remove, so " \
                     "I am adding an after-remove script that runs " \
                     "ldconfig to update the system library cache")
        scripts[:after_remove] = template("deb/ldconfig.sh.erb").result(binding)
      end
    end

    if script?(:before_upgrade) or script?(:after_upgrade)







|





|







334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
    # create 'debian-binary' file, required to make a valid debian package
    File.write(build_path("debian-binary"), "2.0\n")

    # If we are given --deb-shlibs but no --after-install script, we
    # should implicitly create a before/after scripts that run ldconfig
    if attributes[:deb_shlibs]
      if !script?(:after_install)
        logger.info("You gave --deb-shlibs but no --after-install, so " \
                     "I am adding an after-install script that runs " \
                     "ldconfig to update the system library cache")
        scripts[:after_install] = template("deb/ldconfig.sh.erb").result(binding)
      end
      if !script?(:after_remove)
        logger.info("You gave --deb-shlibs but no --after-remove, so " \
                     "I am adding an after-remove script that runs " \
                     "ldconfig to update the system library cache")
        scripts[:after_remove] = template("deb/ldconfig.sh.erb").result(binding)
      end
    end

    if script?(:before_upgrade) or script?(:after_upgrade)
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407

    # Write the changelog file
    dest_changelog = File.join(staging_path, "usr/share/doc/#{name}/changelog.Debian.gz")
    FileUtils.mkdir_p(File.dirname(dest_changelog))
    File.new(dest_changelog, "wb", 0644).tap do |changelog|
      Zlib::GzipWriter.new(changelog, Zlib::BEST_COMPRESSION).tap do |changelog_gz|
        if attributes[:deb_changelog]
          @logger.info("Writing user-specified changelog", :source => attributes[:deb_changelog])
          File.new(attributes[:deb_changelog]).tap do |fd|
            chunk = nil
            # Ruby 1.8.7 doesn't have IO#copy_stream
            changelog_gz.write(chunk) while chunk = fd.read(16384)
          end.close
        else
          @logger.info("Creating boilerplate changelog file")
          changelog_gz.write(template("deb/changelog.erb").result(binding))
        end
      end.close
    end # No need to close, GzipWriter#close will close it.

    attributes.fetch(:deb_init_list, []).each do |init|
      name = File.basename(init, ".init")







|






|







386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407

    # Write the changelog file
    dest_changelog = File.join(staging_path, "usr/share/doc/#{name}/changelog.Debian.gz")
    FileUtils.mkdir_p(File.dirname(dest_changelog))
    File.new(dest_changelog, "wb", 0644).tap do |changelog|
      Zlib::GzipWriter.new(changelog, Zlib::BEST_COMPRESSION).tap do |changelog_gz|
        if attributes[:deb_changelog]
          logger.info("Writing user-specified changelog", :source => attributes[:deb_changelog])
          File.new(attributes[:deb_changelog]).tap do |fd|
            chunk = nil
            # Ruby 1.8.7 doesn't have IO#copy_stream
            changelog_gz.write(chunk) while chunk = fd.read(16384)
          end.close
        else
          logger.info("Creating boilerplate changelog file")
          changelog_gz.write(template("deb/changelog.erb").result(binding))
        end
      end.close
    end # No need to close, GzipWriter#close will close it.

    attributes.fetch(:deb_init_list, []).each do |init|
      name = File.basename(init, ".init")
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
        dep = "#{name} (#{debianize_op(op)} #{version})"
      end
    end

    name_re = /^[^ \(]+/
    name = dep[name_re]
    if name =~ /[A-Z]/
      @logger.warn("Downcasing dependency '#{name}' because deb packages " \
                   " don't work so good with uppercase names")
      dep = dep.gsub(name_re) { |n| n.downcase }
    end

    if dep.include?("_")
      @logger.warn("Replacing dependency underscores with dashes in '#{dep}' because " \
                   "debs don't like underscores")
      dep = dep.gsub("_", "-")
    end

    # Convert gem ~> X.Y.Z to '>= X.Y.Z' and << X.Y+1.0
    if dep =~ /\(~>/
      name, version = dep.gsub(/[()~>]/, "").split(/ +/)[0..1]







|





|







473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
        dep = "#{name} (#{debianize_op(op)} #{version})"
      end
    end

    name_re = /^[^ \(]+/
    name = dep[name_re]
    if name =~ /[A-Z]/
      logger.warn("Downcasing dependency '#{name}' because deb packages " \
                   " don't work so good with uppercase names")
      dep = dep.gsub(name_re) { |n| n.downcase }
    end

    if dep.include?("_")
      logger.warn("Replacing dependency underscores with dashes in '#{dep}' because " \
                   "debs don't like underscores")
      dep = dep.gsub("_", "-")
    end

    # Convert gem ~> X.Y.Z to '>= X.Y.Z' and << X.Y+1.0
    if dep =~ /\(~>/
      name, version = dep.gsub(/[()~>]/, "").split(/ +/)[0..1]
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
    end
  end # def fix_dependency

  def fix_provides(provides)
    name_re = /^[^ \(]+/
    name = provides[name_re]
    if name =~ /[A-Z]/
      @logger.warn("Downcasing provides '#{name}' because deb packages " \
                   " don't work so good with uppercase names")
      provides = provides.gsub(name_re) { |n| n.downcase }
    end

    if provides.include?("_")
      @logger.warn("Replacing 'provides' underscores with dashes in '#{provides}' because " \
                   "debs don't like underscores")
      provides = provides.gsub("_", "-")
    end
    return provides.rstrip
  end

  def control_path(path=nil)







|





|







521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
    end
  end # def fix_dependency

  def fix_provides(provides)
    name_re = /^[^ \(]+/
    name = provides[name_re]
    if name =~ /[A-Z]/
      logger.warn("Downcasing provides '#{name}' because deb packages " \
                   " don't work so good with uppercase names")
      provides = provides.gsub(name_re) { |n| n.downcase }
    end

    if provides.include?("_")
      logger.warn("Replacing 'provides' underscores with dashes in '#{provides}' because " \
                   "debs don't like underscores")
      provides = provides.gsub("_", "-")
    end
    return provides.rstrip
  end

  def control_path(path=nil)
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
    write_debconf # write the debconf files
    write_meta_files # write additional meta files
    write_triggers # write trigger config to 'triggers' file
    write_md5sums # write the md5sums file

    # Make the control.tar.gz
    with(build_path("control.tar.gz")) do |controltar|
      @logger.info("Creating", :path => controltar, :from => control_path)

      args = [ tar_cmd, "-C", control_path, "-zcf", controltar, 
        "--owner=0", "--group=0", "--numeric-owner", "." ]
      safesystem(*args)
    end

    @logger.debug("Removing no longer needed control dir", :path => control_path)
  ensure
    FileUtils.rm_r(control_path)
  end # def write_control_tarball

  def write_control
    # warn user if epoch is set
    @logger.warn("epoch in Version is set", :epoch => self.epoch) if self.epoch

    # calculate installed-size if necessary:
    if attributes[:deb_installed_size].nil?
      @logger.info("No deb_installed_size set, calculating now.")
      total = 0
      Find.find(staging_path) do |path|
        stat = File.lstat(path)
        next if stat.directory?
        total += stat.size
      end
      # Per http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Installed-Size
      #   "The disk space is given as the integer value of the estimated
      #    installed size in bytes, divided by 1024 and rounded up."
      attributes[:deb_installed_size] = total / 1024
    end

    # Write the control file
    with(control_path("control")) do |control|
      if attributes[:deb_custom_control]
        @logger.debug("Using '#{attributes[:deb_custom_control]}' template for the control file")
        control_data = File.read(attributes[:deb_custom_control])
      else
        @logger.debug("Using 'deb.erb' template for the control file")
        control_data = template("deb.erb").result(binding)
      end

      @logger.debug("Writing control file", :path => control)
      File.write(control, control_data)
      File.chmod(0644, control)
      edit_file(control) if attributes[:edit?]
    end
  end # def write_control

  # Write out the maintainer scripts
  #
  # SCRIPT_MAP is a map from the package ':after_install' to debian
  # 'post_install' names
  def write_scripts
    SCRIPT_MAP.each do |scriptname, filename|
      next unless script?(scriptname)

      with(control_path(filename)) do |controlscript|
        @logger.debug("Writing control script", :source => filename, :target => controlscript)
        File.write(controlscript, script(scriptname))
        # deb maintainer scripts are required to be executable
        File.chmod(0755, controlscript)
      end
    end 
  end # def write_scripts








|






|






|



|















|


|



|















|







558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
    write_debconf # write the debconf files
    write_meta_files # write additional meta files
    write_triggers # write trigger config to 'triggers' file
    write_md5sums # write the md5sums file

    # Make the control.tar.gz
    with(build_path("control.tar.gz")) do |controltar|
      logger.info("Creating", :path => controltar, :from => control_path)

      args = [ tar_cmd, "-C", control_path, "-zcf", controltar, 
        "--owner=0", "--group=0", "--numeric-owner", "." ]
      safesystem(*args)
    end

    logger.debug("Removing no longer needed control dir", :path => control_path)
  ensure
    FileUtils.rm_r(control_path)
  end # def write_control_tarball

  def write_control
    # warn user if epoch is set
    logger.warn("epoch in Version is set", :epoch => self.epoch) if self.epoch

    # calculate installed-size if necessary:
    if attributes[:deb_installed_size].nil?
      logger.info("No deb_installed_size set, calculating now.")
      total = 0
      Find.find(staging_path) do |path|
        stat = File.lstat(path)
        next if stat.directory?
        total += stat.size
      end
      # Per http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Installed-Size
      #   "The disk space is given as the integer value of the estimated
      #    installed size in bytes, divided by 1024 and rounded up."
      attributes[:deb_installed_size] = total / 1024
    end

    # Write the control file
    with(control_path("control")) do |control|
      if attributes[:deb_custom_control]
        logger.debug("Using '#{attributes[:deb_custom_control]}' template for the control file")
        control_data = File.read(attributes[:deb_custom_control])
      else
        logger.debug("Using 'deb.erb' template for the control file")
        control_data = template("deb.erb").result(binding)
      end

      logger.debug("Writing control file", :path => control)
      File.write(control, control_data)
      File.chmod(0644, control)
      edit_file(control) if attributes[:edit?]
    end
  end # def write_control

  # Write out the maintainer scripts
  #
  # SCRIPT_MAP is a map from the package ':after_install' to debian
  # 'post_install' names
  def write_scripts
    SCRIPT_MAP.each do |scriptname, filename|
      next unless script?(scriptname)

      with(control_path(filename)) do |controlscript|
        logger.debug("Writing control script", :source => filename, :target => controlscript)
        File.write(controlscript, script(scriptname))
        # deb maintainer scripts are required to be executable
        File.chmod(0755, controlscript)
      end
    end 
  end # def write_scripts

658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
      end
      File.chmod(0644, conffiles)
    end
  end # def write_conffiles

  def write_shlibs
    return unless attributes[:deb_shlibs]
    @logger.info("Adding shlibs", :content => attributes[:deb_shlibs])
    File.open(control_path("shlibs"), "w") do |out|
      out.write(attributes[:deb_shlibs])
    end
  end # def write_shlibs

  def write_debconf
    if attributes[:deb_config]







|







658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
      end
      File.chmod(0644, conffiles)
    end
  end # def write_conffiles

  def write_shlibs
    return unless attributes[:deb_shlibs]
    logger.info("Adding shlibs", :content => attributes[:deb_shlibs])
    File.open(control_path("shlibs"), "w") do |out|
      out.write(attributes[:deb_shlibs])
    end
  end # def write_shlibs

  def write_debconf
    if attributes[:deb_config]

Changes to lib/fpm/package/dir.rb.

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

    if attributes[:prefix]
      destination = File.join(attributes[:prefix], destination)
    end

    destination = File.join(staging_path, destination)

    @logger["method"] = "input"
    begin
      ::Dir.chdir(chdir) do
        begin
          clone(source, destination)
        rescue Errno::ENOENT => e
          raise FPM::InvalidPackageConfiguration,
            "Cannot package the path '#{File.join(chdir, source)}', does it exist?"







|







60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

    if attributes[:prefix]
      destination = File.join(attributes[:prefix], destination)
    end

    destination = File.join(staging_path, destination)

    logger["method"] = "input"
    begin
      ::Dir.chdir(chdir) do
        begin
          clone(source, destination)
        rescue Errno::ENOENT => e
          raise FPM::InvalidPackageConfiguration,
            "Cannot package the path '#{File.join(chdir, source)}', does it exist?"
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
    # can include license data from themselves (rpms, gems, etc),
    # but to make sure a simple dir -> rpm works without having
    # to specify a license.
    self.license = "unknown"
    self.vendor = [ENV["USER"], Socket.gethostname].join("@")
  ensure
    # Clean up any logger context we added.
    @logger.remove("method")
  end # def input

  # Output this package to the given directory.
  def output(output_path)
    output_check(output_path)

    output_path = File.expand_path(output_path)
    ::Dir.chdir(staging_path) do
      @logger["method"] = "output"
      clone(".", output_path)
    end
  ensure
    @logger.remove("method")
  end # def output

  private
  # Copy a file or directory to a destination
  #
  # This is special because it respects the full path of the source.
  # Aditionally, hardlinks will be used instead of copies.
  #
  # Example:
  #
  #     clone("/tmp/hello/world", "/tmp/example")
  #
  # The above will copy, recursively, /tmp/hello/world into
  # /tmp/example/hello/world
  def clone(source, destination)
    @logger.debug("Cloning path", :source => source, :destination => destination)
    # Edge case check; abort if the temporary directory is the source.
    # If the temporary dir is the same path as the source, it causes
    # fpm to recursively (and forever) copy the staging directory by
    # accident (#542).
    if File.expand_path(source) == File.expand_path(::Dir.tmpdir)
      raise FPM::InvalidPackageConfiguration,
        "A source directory cannot be the root of your temporary " \







|








|



|















|







83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
    # can include license data from themselves (rpms, gems, etc),
    # but to make sure a simple dir -> rpm works without having
    # to specify a license.
    self.license = "unknown"
    self.vendor = [ENV["USER"], Socket.gethostname].join("@")
  ensure
    # Clean up any logger context we added.
    logger.remove("method")
  end # def input

  # Output this package to the given directory.
  def output(output_path)
    output_check(output_path)

    output_path = File.expand_path(output_path)
    ::Dir.chdir(staging_path) do
      logger["method"] = "output"
      clone(".", output_path)
    end
  ensure
    logger.remove("method")
  end # def output

  private
  # Copy a file or directory to a destination
  #
  # This is special because it respects the full path of the source.
  # Aditionally, hardlinks will be used instead of copies.
  #
  # Example:
  #
  #     clone("/tmp/hello/world", "/tmp/example")
  #
  # The above will copy, recursively, /tmp/hello/world into
  # /tmp/example/hello/world
  def clone(source, destination)
    logger.debug("Cloning path", :source => source, :destination => destination)
    # Edge case check; abort if the temporary directory is the source.
    # If the temporary dir is the same path as the source, it causes
    # fpm to recursively (and forever) copy the staging directory by
    # accident (#542).
    if File.expand_path(source) == File.expand_path(::Dir.tmpdir)
      raise FPM::InvalidPackageConfiguration,
        "A source directory cannot be the root of your temporary " \
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
  end # def clone

  # Copy a path.
  #
  # Files will be hardlinked if possible, but copied otherwise.
  # Symlinks should be copied as symlinks.
  def copy(source, destination)
    @logger.debug("Copying path", :source => source, :destination => destination)
    directory = File.dirname(destination)
    if !File.directory?(directory)
      FileUtils.mkdir_p(directory)
    end

    if File.directory?(source)
      if !File.symlink?(source)
        # Create a directory if this path is a directory
        @logger.debug("Creating", :directory => destination)
        if !File.directory?(destination)
          FileUtils.mkdir(destination)
        end
      else
        # Linking symlinked directories causes a hardlink to be created, which
        # results in the source directory being wiped out during cleanup,
        # so copy the symlink.
        @logger.debug("Copying symlinked directory", :source => source,
                      :destination => destination)
        FileUtils.copy_entry(source, destination)
      end
    else
      # Otherwise try copying the file.
      begin
        @logger.debug("Linking", :source => source, :destination => destination)
        File.link(source, destination)
      rescue Errno::ENOENT, Errno::EXDEV, Errno::EPERM
        # Hardlink attempt failed, copy it instead
        @logger.debug("Copying", :source => source, :destination => destination)
        copy_entry(source, destination)
      rescue Errno::EEXIST
        sane_path = destination.gsub(staging_path, "")
        @logger.error("Cannot copy file, the destination path is probably a directory and I attempted to write a file.", :path => sane_path, :staging => staging_path)
      end
    end

    copy_metadata(source, destination)
  end # def copy

  def copy_metadata(source, destination)







|








|







|






|



|



|







151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
  end # def clone

  # Copy a path.
  #
  # Files will be hardlinked if possible, but copied otherwise.
  # Symlinks should be copied as symlinks.
  def copy(source, destination)
    logger.debug("Copying path", :source => source, :destination => destination)
    directory = File.dirname(destination)
    if !File.directory?(directory)
      FileUtils.mkdir_p(directory)
    end

    if File.directory?(source)
      if !File.symlink?(source)
        # Create a directory if this path is a directory
        logger.debug("Creating", :directory => destination)
        if !File.directory?(destination)
          FileUtils.mkdir(destination)
        end
      else
        # Linking symlinked directories causes a hardlink to be created, which
        # results in the source directory being wiped out during cleanup,
        # so copy the symlink.
        logger.debug("Copying symlinked directory", :source => source,
                      :destination => destination)
        FileUtils.copy_entry(source, destination)
      end
    else
      # Otherwise try copying the file.
      begin
        logger.debug("Linking", :source => source, :destination => destination)
        File.link(source, destination)
      rescue Errno::ENOENT, Errno::EXDEV, Errno::EPERM
        # Hardlink attempt failed, copy it instead
        logger.debug("Copying", :source => source, :destination => destination)
        copy_entry(source, destination)
      rescue Errno::EEXIST
        sane_path = destination.gsub(staging_path, "")
        logger.error("Cannot copy file, the destination path is probably a directory and I attempted to write a file.", :path => sane_path, :staging => staging_path)
      end
    end

    copy_metadata(source, destination)
  end # def copy

  def copy_metadata(source, destination)

Changes to lib/fpm/package/gem.rb.

58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

  def download_if_necessary(gem, gem_version)
    path = gem
    if !File.exists?(path)
      path = download(gem, gem_version)
    end

    @logger.info("Using gem file", :path => path)
    return path
  end # def download_if_necessary

  def download(gem_name, gem_version=nil)

    @logger.info("Trying to download", :gem => gem_name, :version => gem_version)

    gem_fetch = [ "#{attributes[:gem_gem]}", "fetch", gem_name]

    gem_fetch += ["--prerelease"] if attributes[:gem_prerelease?]
    gem_fetch += ["--version", gem_version] if gem_version

    download_dir = build_path(gem_name)
    FileUtils.mkdir(download_dir) unless File.directory?(download_dir)

    ::Dir.chdir(download_dir) do |dir|
      @logger.debug("Downloading in directory #{dir}")
      safesystem(*gem_fetch)
    end

    gem_files = ::Dir.glob(File.join(download_dir, "*.gem"))

    if gem_files.length != 1
      raise "Unexpected number of gem files in #{download_dir},  #{gem_files.length} should be 1"







|





|










|







58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

  def download_if_necessary(gem, gem_version)
    path = gem
    if !File.exists?(path)
      path = download(gem, gem_version)
    end

    logger.info("Using gem file", :path => path)
    return path
  end # def download_if_necessary

  def download(gem_name, gem_version=nil)

    logger.info("Trying to download", :gem => gem_name, :version => gem_version)

    gem_fetch = [ "#{attributes[:gem_gem]}", "fetch", gem_name]

    gem_fetch += ["--prerelease"] if attributes[:gem_prerelease?]
    gem_fetch += ["--version", gem_version] if gem_version

    download_dir = build_path(gem_name)
    FileUtils.mkdir(download_dir) unless File.directory?(download_dir)

    ::Dir.chdir(download_dir) do |dir|
      logger.debug("Downloading in directory #{dir}")
      safesystem(*gem_fetch)
    end

    gem_files = ::Dir.glob(File.join(download_dir, "*.gem"))

    if gem_files.length != 1
      raise "Unexpected number of gem files in #{download_dir},  #{gem_files.length} should be 1"
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
    # Delete bin_path if it's empty, and any empty parents (#612)
    # Above, we mkdir_p bin_path because rubygems aborts if the parent
    # directory doesn't exist, for example:
    #   ERROR:  While executing gem ... (Errno::ENOENT)
    #       No such file or directory - /tmp/something/weird/bin
    tmp = bin_path
    while ::Dir.entries(tmp).size == 2 || tmp == "/"  # just [ "..", "." ] is an empty directory
      @logger.info("Deleting empty bin_path", :path => tmp)
      ::Dir.rmdir(tmp)
      tmp = File.dirname(tmp)
    end
    if attributes[:gem_version_bins?] and File.directory?(bin_path)
      (::Dir.entries(bin_path) - ['.','..']).each do |bin|
        FileUtils.mv("#{bin_path}/#{bin}", "#{bin_path}/#{bin}-#{self.version}")
      end







|







199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
    # Delete bin_path if it's empty, and any empty parents (#612)
    # Above, we mkdir_p bin_path because rubygems aborts if the parent
    # directory doesn't exist, for example:
    #   ERROR:  While executing gem ... (Errno::ENOENT)
    #       No such file or directory - /tmp/something/weird/bin
    tmp = bin_path
    while ::Dir.entries(tmp).size == 2 || tmp == "/"  # just [ "..", "." ] is an empty directory
      logger.info("Deleting empty bin_path", :path => tmp)
      ::Dir.rmdir(tmp)
      tmp = File.dirname(tmp)
    end
    if attributes[:gem_version_bins?] and File.directory?(bin_path)
      (::Dir.entries(bin_path) - ['.','..']).each do |bin|
        FileUtils.mv("#{bin_path}/#{bin}", "#{bin_path}/#{bin}-#{self.version}")
      end

Changes to lib/fpm/package/osxpkg.rb.

78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
  end # def scripts_path

  def write_scripts
    SCRIPT_MAP.each do |scriptname, filename|
      next unless script?(scriptname)

      with(scripts_path(filename)) do |pkgscript|
        @logger.info("Writing pkg script", :source => filename, :target => pkgscript)
        File.write(pkgscript, script(scriptname))
        # scripts are required to be executable
        File.chmod(0755, pkgscript)
      end
    end 
  end # def write_scripts








|







78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
  end # def scripts_path

  def write_scripts
    SCRIPT_MAP.each do |scriptname, filename|
      next unless script?(scriptname)

      with(scripts_path(filename)) do |pkgscript|
        logger.info("Writing pkg script", :source => filename, :target => pkgscript)
        File.write(pkgscript, script(scriptname))
        # scripts are required to be executable
        File.chmod(0755, pkgscript)
      end
    end 
  end # def write_scripts

106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
    with(build_path("expand")) do |path|
      doc = REXML::Document.new File.open(File.join(path, "PackageInfo"))
      pkginfo_elem = doc.elements["pkg-info"]
      identifier = pkginfo_elem.attribute("identifier").value
      self.version = pkginfo_elem.attribute("version").value
      # set name to the last dot element of the identifier
      self.name = identifier.split(".").last
      @logger.info("inferring name #{self.name} from pkg-id #{identifier}")
    end
  end # def extract_info

  # Take a flat package as input
  def input(input_path)
    # TODO: Fail if it's a Distribution pkg or old-fashioned
    expand_dir = File.join(build_path, "expand")







|







106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
    with(build_path("expand")) do |path|
      doc = REXML::Document.new File.open(File.join(path, "PackageInfo"))
      pkginfo_elem = doc.elements["pkg-info"]
      identifier = pkginfo_elem.attribute("identifier").value
      self.version = pkginfo_elem.attribute("version").value
      # set name to the last dot element of the identifier
      self.name = identifier.split(".").last
      logger.info("inferring name #{self.name} from pkg-id #{identifier}")
    end
  end # def extract_info

  # Take a flat package as input
  def input(input_path)
    # TODO: Fail if it's a Distribution pkg or old-fashioned
    expand_dir = File.join(build_path, "expand")

Changes to lib/fpm/package/pear.rb.

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
  # * :pear_package_name_prefix - changes the
  def input(input_package)
    if !program_in_path?("pear")
      raise ExecutableNotFound.new("pear")
    end

    # Create a temporary config file
    @logger.debug("Creating pear config file")
    config = File.expand_path(build_path("pear.config"))
    installroot = attributes[:prefix] || "/usr/share"
    safesystem("pear", "config-create", staging_path(installroot), config)

    if attributes[:pear_php_dir]
      @logger.info("Setting php_dir", :php_dir => attributes[:pear_php_dir])
      safesystem("pear", "-c", config, "config-set", "php_dir", "#{staging_path(installroot)}/#{attributes[:pear_php_dir]}")
    end

    if attributes[:pear_data_dir]
      @logger.info("Setting data_dir", :data_dir => attributes[:pear_data_dir])
      safesystem("pear", "-c", config, "config-set", "data_dir", "#{staging_path(installroot)}/#{attributes[:pear_data_dir]}")
    end

    bin_dir = attributes[:pear_bin_dir] || "usr/bin"
    @logger.info("Setting bin_dir", :bin_dir => bin_dir)
    safesystem("pear", "-c", config, "config-set", "bin_dir", bin_dir)

    php_bin = attributes[:pear_php_bin] || "/usr/bin/php"
    @logger.info("Setting php_bin", :php_bin => php_bin)
    safesystem("pear", "-c", config, "config-set", "php_bin", php_bin)

    # do channel-discover if required
    if !attributes[:pear_channel].nil?
      @logger.info("Custom channel specified", :channel => attributes[:pear_channel])
      channel_list = safesystemout("pear", "-c", config, "list-channels")
      if channel_list !~ /#{Regexp.quote(attributes[:pear_channel])}/
        @logger.info("Discovering new channel", :channel => attributes[:pear_channel])
        safesystem("pear", "-c", config, "channel-discover", attributes[:pear_channel])
      end
      input_package = "#{attributes[:pear_channel]}/#{input_package}"
      @logger.info("Prefixing package name with channel", :package => input_package)
    end

    # do channel-update if requested
    if attributes[:pear_channel_update?]
      channel = attributes[:pear_channel] || "pear"
      @logger.info("Updating the channel", :channel => channel)
      safesystem("pear", "-c", config, "channel-update", channel)
    end

    @logger.info("Installing pear package", :package => input_package,
                  :directory => staging_path)
    ::Dir.chdir(staging_path) do
      safesystem("pear", "-c", config, "install", "-n", "-f", input_package)
    end

    pear_cmd = "pear -c #{config} remote-info #{input_package}"
    @logger.info("Fetching package information", :package => input_package, :command => pear_cmd)
    name = %x{#{pear_cmd} | sed -ne '/^Package\s*/s/^Package\s*//p'}.chomp
    self.name = "#{attributes[:pear_package_name_prefix]}-#{name}"
    self.version = %x{#{pear_cmd} | sed -ne '/^Installed\s*/s/^Installed\s*//p'}.chomp
    self.description  = %x{#{pear_cmd} | sed -ne '/^Summary\s*/s/^Summary\s*//p'}.chomp
    @logger.debug("Package info", :name => self.name, :version => self.version,
                  :description => self.description)

    # Remove the stuff we don't want
    delete_these = [".depdb", ".depdblock", ".filemap", ".lock", ".channel", "cache", "temp", "download", ".channels", ".registry"]
    Find.find(staging_path) do |path|
      if File.file?(path)
        @logger.info("replacing staging_path in file", :replace_in => path, :staging_path => staging_path)
        begin
          content = File.read(path).gsub(/#{Regexp.escape(staging_path)}/, "")
          File.write(path, content)
        rescue ArgumentError => e
          @logger.warn("error replacing staging_path in file", :replace_in => path, :error => e)
        end
      end
      FileUtils.rm_r(path) if delete_these.include?(File.basename(path))
    end

  end # def input
end # class FPM::Package::PEAR







|





|




|




|



|




|


|



|





|



|






|




|






|




|







37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
  # * :pear_package_name_prefix - changes the
  def input(input_package)
    if !program_in_path?("pear")
      raise ExecutableNotFound.new("pear")
    end

    # Create a temporary config file
    logger.debug("Creating pear config file")
    config = File.expand_path(build_path("pear.config"))
    installroot = attributes[:prefix] || "/usr/share"
    safesystem("pear", "config-create", staging_path(installroot), config)

    if attributes[:pear_php_dir]
      logger.info("Setting php_dir", :php_dir => attributes[:pear_php_dir])
      safesystem("pear", "-c", config, "config-set", "php_dir", "#{staging_path(installroot)}/#{attributes[:pear_php_dir]}")
    end

    if attributes[:pear_data_dir]
      logger.info("Setting data_dir", :data_dir => attributes[:pear_data_dir])
      safesystem("pear", "-c", config, "config-set", "data_dir", "#{staging_path(installroot)}/#{attributes[:pear_data_dir]}")
    end

    bin_dir = attributes[:pear_bin_dir] || "usr/bin"
    logger.info("Setting bin_dir", :bin_dir => bin_dir)
    safesystem("pear", "-c", config, "config-set", "bin_dir", bin_dir)

    php_bin = attributes[:pear_php_bin] || "/usr/bin/php"
    logger.info("Setting php_bin", :php_bin => php_bin)
    safesystem("pear", "-c", config, "config-set", "php_bin", php_bin)

    # do channel-discover if required
    if !attributes[:pear_channel].nil?
      logger.info("Custom channel specified", :channel => attributes[:pear_channel])
      channel_list = safesystemout("pear", "-c", config, "list-channels")
      if channel_list !~ /#{Regexp.quote(attributes[:pear_channel])}/
        logger.info("Discovering new channel", :channel => attributes[:pear_channel])
        safesystem("pear", "-c", config, "channel-discover", attributes[:pear_channel])
      end
      input_package = "#{attributes[:pear_channel]}/#{input_package}"
      logger.info("Prefixing package name with channel", :package => input_package)
    end

    # do channel-update if requested
    if attributes[:pear_channel_update?]
      channel = attributes[:pear_channel] || "pear"
      logger.info("Updating the channel", :channel => channel)
      safesystem("pear", "-c", config, "channel-update", channel)
    end

    logger.info("Installing pear package", :package => input_package,
                  :directory => staging_path)
    ::Dir.chdir(staging_path) do
      safesystem("pear", "-c", config, "install", "-n", "-f", input_package)
    end

    pear_cmd = "pear -c #{config} remote-info #{input_package}"
    logger.info("Fetching package information", :package => input_package, :command => pear_cmd)
    name = %x{#{pear_cmd} | sed -ne '/^Package\s*/s/^Package\s*//p'}.chomp
    self.name = "#{attributes[:pear_package_name_prefix]}-#{name}"
    self.version = %x{#{pear_cmd} | sed -ne '/^Installed\s*/s/^Installed\s*//p'}.chomp
    self.description  = %x{#{pear_cmd} | sed -ne '/^Summary\s*/s/^Summary\s*//p'}.chomp
    logger.debug("Package info", :name => self.name, :version => self.version,
                  :description => self.description)

    # Remove the stuff we don't want
    delete_these = [".depdb", ".depdblock", ".filemap", ".lock", ".channel", "cache", "temp", "download", ".channels", ".registry"]
    Find.find(staging_path) do |path|
      if File.file?(path)
        logger.info("replacing staging_path in file", :replace_in => path, :staging_path => staging_path)
        begin
          content = File.read(path).gsub(/#{Regexp.escape(staging_path)}/, "")
          File.write(path, content)
        rescue ArgumentError => e
          logger.warn("error replacing staging_path in file", :replace_in => path, :error => e)
        end
      end
      FileUtils.rm_r(path) if delete_these.include?(File.basename(path))
    end

  end # def input
end # class FPM::Package::PEAR

Changes to lib/fpm/package/puppet.rb.

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    return @architecture
  end # def architecture

  # Default specfile generator just makes one specfile, whatever that is for
  # this package.
  def generate_specfile(builddir)
    paths = []
    @logger.info("PWD: #{File.join(builddir, unpack_data_to)}")
    fileroot = File.join(builddir, unpack_data_to)
    Dir.chdir(fileroot) do
      Find.find(".") do |p|
        next if p == "."
        paths << p
      end
    end
    @logger.info(paths[-1])
    manifests = %w{package.pp package/remove.pp}

    ::Dir.mkdir(File.join(builddir, "manifests"))
    manifests.each do |manifest|
      dir = File.join(builddir, "manifests", File.dirname(manifest))
      @logger.info("manifests targeting: #{dir}")
      ::Dir.mkdir(dir) if !File.directory?(dir)

      File.open(File.join(builddir, "manifests", manifest), "w") do |f|
        @logger.info("manifest: #{f.path}")
        template = template(File.join("puppet", "#{manifest}.erb"))
        ::Dir.chdir(fileroot) do
          f.puts template.result(binding)
        end
      end
    end
  end # def generate_specfile







|







|





|



|







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    return @architecture
  end # def architecture

  # Default specfile generator just makes one specfile, whatever that is for
  # this package.
  def generate_specfile(builddir)
    paths = []
    logger.info("PWD: #{File.join(builddir, unpack_data_to)}")
    fileroot = File.join(builddir, unpack_data_to)
    Dir.chdir(fileroot) do
      Find.find(".") do |p|
        next if p == "."
        paths << p
      end
    end
    logger.info(paths[-1])
    manifests = %w{package.pp package/remove.pp}

    ::Dir.mkdir(File.join(builddir, "manifests"))
    manifests.each do |manifest|
      dir = File.join(builddir, "manifests", File.dirname(manifest))
      logger.info("manifests targeting: #{dir}")
      ::Dir.mkdir(dir) if !File.directory?(dir)

      File.open(File.join(builddir, "manifests", manifest), "w") do |f|
        logger.info("manifest: #{f.path}")
        template = template(File.join("puppet", "#{manifest}.erb"))
        ::Dir.chdir(fileroot) do
          f.puts template.result(binding)
        end
      end
    end
  end # def generate_specfile
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
        when "pre-uninstall"
        when "post-uninstall"
      end # case name
    end # self.scripts.each

    if File.exists?(params[:output])
      # TODO(sissel): Allow folks to choose output?
      @logger.error("Puppet module directory '#{params[:output]}' already " \
                    "exists. Delete it or choose another output (-p flag)")
    end

    ::Dir.mkdir(params[:output])
    builddir = ::Dir.pwd

    # Copy 'files' from builddir to :output/files
    Find.find("files", "manifests") do |path|
      @logger.info("Copying path: #{path}")
      if File.directory?(path)
        ::Dir.mkdir(File.join(params[:output], path))
      else
        FileUtils.cp(path, File.join(params[:output], path))
      end
    end
  end # def build!







|








|







58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
        when "pre-uninstall"
        when "post-uninstall"
      end # case name
    end # self.scripts.each

    if File.exists?(params[:output])
      # TODO(sissel): Allow folks to choose output?
      logger.error("Puppet module directory '#{params[:output]}' already " \
                    "exists. Delete it or choose another output (-p flag)")
    end

    ::Dir.mkdir(params[:output])
    builddir = ::Dir.pwd

    # Copy 'files' from builddir to :output/files
    Find.find("files", "manifests") do |path|
      logger.info("Copying path: #{path}")
      if File.directory?(path)
        ::Dir.mkdir(File.join(params[:output], path))
      else
        FileUtils.cp(path, File.join(params[:output], path))
      end
    end
  end # def build!
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
  # Helper for user lookup
  def uid2user(uid)
    begin
      pwent = Etc.getpwuid(uid)
      return pwent.name
    rescue ArgumentError => e
      # Invalid user id? No user? Return the uid.
      @logger.warn("Failed to find username for uid #{uid}")
      return uid.to_s
    end
  end # def uid2user

  # Helper for group lookup
  def gid2group(gid)
    begin
      grent = Etc.getgrgid(gid)
      return grent.name
    rescue ArgumentError => e
      # Invalid user id? No user? Return the uid.
      @logger.warn("Failed to find group for gid #{gid}")
      return gid.to_s
    end
  end # def uid2user
end # class FPM::Target::Puppet








|











|





96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
  # Helper for user lookup
  def uid2user(uid)
    begin
      pwent = Etc.getpwuid(uid)
      return pwent.name
    rescue ArgumentError => e
      # Invalid user id? No user? Return the uid.
      logger.warn("Failed to find username for uid #{uid}")
      return uid.to_s
    end
  end # def uid2user

  # Helper for group lookup
  def gid2group(gid)
    begin
      grent = Etc.getgrgid(gid)
      return grent.name
    rescue ArgumentError => e
      # Invalid user id? No user? Return the uid.
      logger.warn("Failed to find group for gid #{gid}")
      return gid.to_s
    end
  end # def uid2user
end # class FPM::Target::Puppet

Changes to lib/fpm/package/python.rb.

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    "is used instead", :default => nil
  option "--pypi", "PYPI_URL",
    "PyPi Server uri for retrieving packages.",
    :default => "http://pypi.python.org/simple"
  option "--package-prefix", "NAMEPREFIX",
    "(DEPRECATED, use --package-name-prefix) Name to prefix the package " \
    "name with." do |value|
    @logger.warn("Using deprecated flag: --package-prefix. Please use " \
                 "--package-name-prefix")
    value
  end
  option "--package-name-prefix", "PREFIX", "Name to prefix the package " \
    "name with.", :default => "python"
  option "--fix-name", :flag, "Should the target package name be prefixed?",
    :default => true







|







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    "is used instead", :default => nil
  option "--pypi", "PYPI_URL",
    "PyPi Server uri for retrieving packages.",
    :default => "http://pypi.python.org/simple"
  option "--package-prefix", "NAMEPREFIX",
    "(DEPRECATED, use --package-name-prefix) Name to prefix the package " \
    "name with." do |value|
    logger.warn("Using deprecated flag: --package-prefix. Please use " \
                 "--package-name-prefix")
    value
  end
  option "--package-name-prefix", "PREFIX", "Name to prefix the package " \
    "name with.", :default => "python"
  option "--fix-name", :flag, "Should the target package name be prefixed?",
    :default => true
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
    if File.directory?(path_to_package)
      setup_py = File.join(path_to_package, "setup.py")
    else
      setup_py = path_to_package
    end

    if !File.exists?(setup_py)
      @logger.error("Could not find 'setup.py'", :path => setup_py)
      raise "Unable to find python package; tried #{setup_py}"
    end

    load_package_info(setup_py)
    install_to_staging(setup_py)
  end # def input

  # Download the given package if necessary. If version is given, that version
  # will be downloaded, otherwise the latest is fetched.
  def download_if_necessary(package, version=nil)
    # TODO(sissel): this should just be a 'download' method, the 'if_necessary'
    # part should go elsewhere.
    path = package
    # If it's a path, assume local build.
    if File.directory?(path) or (File.exists?(path) and File.basename(path) == "setup.py")
      return path
    end

    @logger.info("Trying to download", :package => package)

    if version.nil?
      want_pkg = "#{package}"
    else
      want_pkg = "#{package}==#{version}"
    end

    target = build_path(package)
    FileUtils.mkdir(target) unless File.directory?(target)

    if attributes[:python_pip].nil?
      # no pip, use easy_install
      @logger.debug("no pip, defaulting to easy_install", :easy_install => attributes[:python_easyinstall])
      safesystem(attributes[:python_easyinstall], "-i",
                 attributes[:python_pypi], "--editable", "-U",
                 "--build-directory", target, want_pkg)
    else
      @logger.debug("using pip", :pip => attributes[:python_pip])
      safesystem(attributes[:python_pip], "install", "--no-deps", "--no-install", "-i", attributes[:python_pypi], "-U", "--build", target, want_pkg)
    end

    # easy_install will put stuff in @tmpdir/packagename/, so find that:
    #  @tmpdir/somepackage/setup.py
    dirs = ::Dir.glob(File.join(target, "*"))
    if dirs.length != 1







|


















|












|




|







85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
    if File.directory?(path_to_package)
      setup_py = File.join(path_to_package, "setup.py")
    else
      setup_py = path_to_package
    end

    if !File.exists?(setup_py)
      logger.error("Could not find 'setup.py'", :path => setup_py)
      raise "Unable to find python package; tried #{setup_py}"
    end

    load_package_info(setup_py)
    install_to_staging(setup_py)
  end # def input

  # Download the given package if necessary. If version is given, that version
  # will be downloaded, otherwise the latest is fetched.
  def download_if_necessary(package, version=nil)
    # TODO(sissel): this should just be a 'download' method, the 'if_necessary'
    # part should go elsewhere.
    path = package
    # If it's a path, assume local build.
    if File.directory?(path) or (File.exists?(path) and File.basename(path) == "setup.py")
      return path
    end

    logger.info("Trying to download", :package => package)

    if version.nil?
      want_pkg = "#{package}"
    else
      want_pkg = "#{package}==#{version}"
    end

    target = build_path(package)
    FileUtils.mkdir(target) unless File.directory?(target)

    if attributes[:python_pip].nil?
      # no pip, use easy_install
      logger.debug("no pip, defaulting to easy_install", :easy_install => attributes[:python_easyinstall])
      safesystem(attributes[:python_easyinstall], "-i",
                 attributes[:python_pypi], "--editable", "-U",
                 "--build-directory", target, want_pkg)
    else
      logger.debug("using pip", :pip => attributes[:python_pip])
      safesystem(attributes[:python_pip], "install", "--no-deps", "--no-install", "-i", attributes[:python_pypi], "-U", "--build", target, want_pkg)
    end

    # easy_install will put stuff in @tmpdir/packagename/, so find that:
    #  @tmpdir/somepackage/setup.py
    dirs = ::Dir.glob(File.join(target, "*"))
    if dirs.length != 1
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
        "try:",
        "  import json",
        "except ImportError:",
        "  import simplejson as json"
      ].join("\n")
      safesystem("#{attributes[:python_bin]} -c '#{json_test_code}'")
    rescue FPM::Util::ProcessFailed => e
      @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)
      raise FPM::Util::ProcessFailed, "Python (#{attributes[:python_bin]}) is missing simplejson or json modules."
    end

    begin
      safesystem("#{attributes[:python_bin]} -c 'import pkg_resources'")
    rescue FPM::Util::ProcessFailed => e
      @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)
      raise FPM::Util::ProcessFailed, "Python (#{attributes[:python_bin]}) is missing pkg_resources module."
    end

    # Add ./pyfpm/ to the python library path
    pylib = File.expand_path(File.dirname(__FILE__))

    # chdir to the directory holding setup.py because some python setup.py's assume that you are







|






|







150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
        "try:",
        "  import json",
        "except ImportError:",
        "  import simplejson as json"
      ].join("\n")
      safesystem("#{attributes[:python_bin]} -c '#{json_test_code}'")
    rescue FPM::Util::ProcessFailed => e
      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)
      raise FPM::Util::ProcessFailed, "Python (#{attributes[:python_bin]}) is missing simplejson or json modules."
    end

    begin
      safesystem("#{attributes[:python_bin]} -c 'import pkg_resources'")
    rescue FPM::Util::ProcessFailed => e
      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)
      raise FPM::Util::ProcessFailed, "Python (#{attributes[:python_bin]}) is missing pkg_resources module."
    end

    # Add ./pyfpm/ to the python library path
    pylib = File.expand_path(File.dirname(__FILE__))

    # chdir to the directory holding setup.py because some python setup.py's assume that you are
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
      if attributes[:python_obey_requirements_txt?]
        setup_cmd += " --load-requirements-txt"
      end

      # Capture the output, which will be JSON metadata describing this python
      # package. See fpm/lib/fpm/package/pyfpm/get_metadata.py for more
      # details.
      @logger.info("fetching package metadata", :setup_cmd => setup_cmd)

      success = safesystem(setup_cmd)
      #%x{#{setup_cmd}}
      if !success
        @logger.error("setup.py get_metadata failed", :command => setup_cmd,
                      :exitcode => $?.exitstatus)
        raise "An unexpected error occurred while processing the setup.py file"
      end
      File.read(tmp)
    end
    @logger.debug("result from `setup.py get_metadata`", :data => output)
    metadata = JSON.parse(output)
    @logger.info("object output of get_metadata", :json => metadata)

    self.architecture = metadata["architecture"]
    self.description = metadata["description"]
    # Sometimes the license field is multiple lines; do best-effort and just
    # use the first line.
    self.license = metadata["license"].split(/[\r\n]+/).first
    self.version = metadata["version"]







|




|





|

|







180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
      if attributes[:python_obey_requirements_txt?]
        setup_cmd += " --load-requirements-txt"
      end

      # Capture the output, which will be JSON metadata describing this python
      # package. See fpm/lib/fpm/package/pyfpm/get_metadata.py for more
      # details.
      logger.info("fetching package metadata", :setup_cmd => setup_cmd)

      success = safesystem(setup_cmd)
      #%x{#{setup_cmd}}
      if !success
        logger.error("setup.py get_metadata failed", :command => setup_cmd,
                      :exitcode => $?.exitstatus)
        raise "An unexpected error occurred while processing the setup.py file"
      end
      File.read(tmp)
    end
    logger.debug("result from `setup.py get_metadata`", :data => output)
    metadata = JSON.parse(output)
    logger.info("object output of get_metadata", :json => metadata)

    self.architecture = metadata["architecture"]
    self.description = metadata["description"]
    # Sometimes the license field is multiple lines; do best-effort and just
    # use the first line.
    self.license = metadata["license"].split(/[\r\n]+/).first
    self.version = metadata["version"]
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
    self.name = self.name.downcase if attributes[:python_downcase_name?]

    if !attributes[:no_auto_depends?] and attributes[:python_dependencies?]
      self.dependencies = metadata["dependencies"].collect do |dep|
        dep_re = /^([^<>!= ]+)\s*(?:([<>!=]{1,2})\s*(.*))?$/
        match = dep_re.match(dep)
        if match.nil?
          @logger.error("Unable to parse dependency", :dependency => dep)
          raise FPM::InvalidPackageConfiguration, "Invalid dependency '#{dep}'"
        end
        name, cmp, version = match.captures

        # convert == to =
        if cmp == "=="
          @logger.info("Converting == dependency requirement to =", :dependency => dep )
          cmp = "="
        end

        # dependency name prefixing is optional, if enabled, a name 'foo' will
        # become 'python-foo' (depending on what the python_package_name_prefix
        # is)
        name = fix_name(name) if attributes[:python_fix_dependencies?]







|






|







219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
    self.name = self.name.downcase if attributes[:python_downcase_name?]

    if !attributes[:no_auto_depends?] and attributes[:python_dependencies?]
      self.dependencies = metadata["dependencies"].collect do |dep|
        dep_re = /^([^<>!= ]+)\s*(?:([<>!=]{1,2})\s*(.*))?$/
        match = dep_re.match(dep)
        if match.nil?
          logger.error("Unable to parse dependency", :dependency => dep)
          raise FPM::InvalidPackageConfiguration, "Invalid dependency '#{dep}'"
        end
        name, cmp, version = match.captures

        # convert == to =
        if cmp == "=="
          logger.info("Converting == dependency requirement to =", :dependency => dep )
          cmp = "="
        end

        # dependency name prefixing is optional, if enabled, a name 'foo' will
        # become 'python-foo' (depending on what the python_package_name_prefix
        # is)
        name = fix_name(name) if attributes[:python_fix_dependencies?]

Changes to lib/fpm/package/rpm.rb.

253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
    if self.attributes[:rpm_ignore_iteration_in_dependencies?]
      self.dependencies = self.dependencies.collect do |dep|
        name, op, version = dep.split(/\s+/)
        if op == '='
          nextversion = version.split('.').collect { |v| v.to_i }
          nextversion[-1] += 1
          nextversion = nextversion.join(".")
          @logger.warn("Converting dependency #{dep} to #{name} >= #{version}, #{name} < #{nextversion}")
          ["#{name} >= #{version}", "#{name} < #{nextversion}"]
        else
          dep
        end
      end.flatten
    end

  setscript = proc do |scriptname|
      script_path = self.attributes[scriptname]
      # Skip scripts not set
      next if script_path.nil?
      if !File.exists?(script_path)
        @logger.error("No such file (for #{scriptname.to_s}): #{script_path.inspect}")
        script_errors	 << script_path
      end
      # Load the script into memory.
      scripts[scriptname] = File.read(script_path)
    end

  setscript.call(:rpm_verifyscript)







|












|







253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
    if self.attributes[:rpm_ignore_iteration_in_dependencies?]
      self.dependencies = self.dependencies.collect do |dep|
        name, op, version = dep.split(/\s+/)
        if op == '='
          nextversion = version.split('.').collect { |v| v.to_i }
          nextversion[-1] += 1
          nextversion = nextversion.join(".")
          logger.warn("Converting dependency #{dep} to #{name} >= #{version}, #{name} < #{nextversion}")
          ["#{name} >= #{version}", "#{name} < #{nextversion}"]
        else
          dep
        end
      end.flatten
    end

  setscript = proc do |scriptname|
      script_path = self.attributes[scriptname]
      # Skip scripts not set
      next if script_path.nil?
      if !File.exists?(script_path)
        logger.error("No such file (for #{scriptname.to_s}): #{script_path.inspect}")
        script_errors	 << script_path
      end
      # Load the script into memory.
      scripts[scriptname] = File.read(script_path)
    end

  setscript.call(:rpm_verifyscript)
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
    specfile = File.join(build_path("SPECS"), "#{name}.spec")
    File.write(specfile, rpmspec)

    edit_file(specfile) if attributes[:edit?]

    args << specfile

    @logger.info("Running rpmbuild", :args => args)
    safesystem(*args)

    ::Dir["#{build_path}/RPMS/**/*.rpm"].each do |rpmpath|
      # This should only output one rpm, should we verify this?
      FileUtils.cp(rpmpath, output_path)
    end
  end # def output

  def prefix
    return (attributes[:prefix] or "/")
  end # def prefix

  def build_sub_dir
    return "BUILD"
    #return File.join("BUILD", prefix)
  end # def build_sub_dir

  def version
    if @version.kind_of?(String) and @version.include?("-")
      @logger.warn("Package version '#{@version}' includes dashes, converting" \
                   " to underscores")
      @version = @version.gsub(/-/, "_")
    end

    return @version
  end

  # The default epoch value must be nil, see #381
  def epoch
    return @epoch if @epoch.is_a?(Numeric)

    if @epoch.nil? or @epoch.empty?
      @logger.warn("no value for epoch is set, defaulting to nil")
      return nil
    end

    return @epoch
  end # def epoch

  def to_s(format=nil)







|



















|












|







430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
    specfile = File.join(build_path("SPECS"), "#{name}.spec")
    File.write(specfile, rpmspec)

    edit_file(specfile) if attributes[:edit?]

    args << specfile

    logger.info("Running rpmbuild", :args => args)
    safesystem(*args)

    ::Dir["#{build_path}/RPMS/**/*.rpm"].each do |rpmpath|
      # This should only output one rpm, should we verify this?
      FileUtils.cp(rpmpath, output_path)
    end
  end # def output

  def prefix
    return (attributes[:prefix] or "/")
  end # def prefix

  def build_sub_dir
    return "BUILD"
    #return File.join("BUILD", prefix)
  end # def build_sub_dir

  def version
    if @version.kind_of?(String) and @version.include?("-")
      logger.warn("Package version '#{@version}' includes dashes, converting" \
                   " to underscores")
      @version = @version.gsub(/-/, "_")
    end

    return @version
  end

  # The default epoch value must be nil, see #381
  def epoch
    return @epoch if @epoch.is_a?(Numeric)

    if @epoch.nil? or @epoch.empty?
      logger.warn("no value for epoch is set, defaulting to nil")
      return nil
    end

    return @epoch
  end # def epoch

  def to_s(format=nil)

Changes to lib/fpm/package/sh.rb.

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    end
    path
  end

  # Returns the path to the tar file containing the packed up staging directory
  def payload
    payload_tar = build_path("payload.tar")
    @logger.info("Creating payload tar ", :path => payload_tar)

    args = [ tar_cmd,
             "-C",
             staging_path,
             "-cf",
             payload_tar,
             "--owner=0",







|







42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    end
    path
  end

  # Returns the path to the tar file containing the packed up staging directory
  def payload
    payload_tar = build_path("payload.tar")
    logger.info("Creating payload tar ", :path => payload_tar)

    args = [ tar_cmd,
             "-C",
             staging_path,
             "-cf",
             payload_tar,
             "--owner=0",

Changes to spec/fpm/util_spec.rb.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require "spec_setup"
require "fpm" # local
require "fpm/util" # local
require "stud/temporary"


describe FPM::Util do
  subject do
    Class.new do
      include FPM::Util
      def initialize
        @logger = Cabin::Channel.new
      end
    end.new
  end

  context "#copy_entry" do
    context "when given files that are hardlinks" do
      it "should keep those files as hardlinks" do
        Stud::Temporary.directory do |path|










<
<
<







1
2
3
4
5
6
7
8
9
10



11
12
13
14
15
16
17
require "spec_setup"
require "fpm" # local
require "fpm/util" # local
require "stud/temporary"


describe FPM::Util do
  subject do
    Class.new do
      include FPM::Util



    end.new
  end

  context "#copy_entry" do
    context "when given files that are hardlinks" do
      it "should keep those files as hardlinks" do
        Stud::Temporary.directory do |path|