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

⌈⌋ branch:  cross package maker


Check-in Differences

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

Difference From 46b7983812b50762 To 049fb106f34a323d

2014-12-27
11:54
Fix clamp option declaration from :bool to :flag. check-in: 3c39fc7d17 user: mario tags: trunk
2014-12-26
20:25
Fix staging_path target name, and path localization for downloaded phars. Retain just target package information from composer.lock. check-in: 049fb106f3 user: mario tags: trunk
13:39
Minor bugfix release 1.3.3.4, updated composer plugin, more consistent staging/build_path handling, :attrs usage, exceptions instead of warnings. check-in: 193345284b user: mario tags: trunk, v1.3.3.4
2014-06-18
06:43
Merge pull request #712 from mlafeldt/cpan-metadata-nil Ignore unset CPAN metadata fields check-in: 1f2b4a8ab8 user: jls@semicomplete.com tags: trunk
2014-06-02
10:28
Ignore unset CPAN metadata fields Otherwise this can happen: $ fpm -t deb -s cpan --verbose 'Lingua::JA::Romanize::Japanese' Asking metacpan about a module {:module=>"Lingua::JA::Romanize::Japanese", :level=>:info} Downloading perl module {:distribution=>"Lingua-JA-Romanize-Japanese", :version=>nil, :level=>:info} Setting package name from 'name' {:name=>"Lingua-JA-Romanize-Japanese", :level=>:info} /usr/lib/ruby/gems/1.9.1/gems/fpm-1.1.0/lib/fpm/package/cpan.rb:86:in `input': undefined method `join' for nil:NilClass (NoMethodError) from /usr/lib/ruby/gems/1.9.1/gems/fpm-1.1.0/lib/fpm/command.rb:299:in `block in execute' from /usr/lib/ruby/gems/1.9.1/gems/fpm-1.1.0/lib/fpm/command.rb:298:in `each' from /usr/lib/ruby/gems/1.9.1/gems/fpm-1.1.0/lib/fpm/command.rb:298:in `execute' from /usr/lib/ruby/gems/1.9.1/gems/clamp-0.6.3/lib/clamp/command.rb:67:in `run' from /usr/lib/ruby/gems/1.9.1/gems/fpm-1.1.0/lib/fpm/command.rb:449:in `run' from /usr/lib/ruby/gems/1.9.1/gems/clamp-0.6.3/lib/clamp/command.rb:125:in `run' from /usr/lib/ruby/gems/1.9.1/gems/fpm-1.1.0/bin/fpm:8:in `<top (required)>' from /usr/bin/fpm:23:in `load' from /usr/bin/fpm:23:in `<main>' check-in: 46b7983812 user: mathias.lafeldt@gmail.com tags: trunk
2014-05-20
18:31
Merge pull request #700 from gerbercj/feature/sh_package_bugfixes Feature/sh package bugfixes check-in: 97546f0f78 user: jls@semicomplete.com tags: trunk

Changes to .travis.yml.

1
2
3
4
5
6
7
8
9
10

language: ruby
rvm:
  - 1.8.7
  - 1.9.2
  - 1.9.3
  - 2.0.0
script: rspec
before_install:
  - sudo apt-get update -qq
  - sudo apt-get install -qq rpm











>
1
2
3
4
5
6
7
8
9
10
11
language: ruby
rvm:
  - 1.8.7
  - 1.9.2
  - 1.9.3
  - 2.0.0
script: rspec
before_install:
  - sudo apt-get update -qq
  - sudo apt-get install -qq rpm
  - sudo apt-get install -qq lintian

Changes to CHANGELIST.





















































































1
2
3
4
5
6
7




















































































1.1.0 (April 23, 2014)
  - New package type: zip, for converting to and from zip files (Jordan Sissel)
  - New package type: sh, a self-extracting package installation shell archive. (#651, Chris Gerber)
  - 'fpm --version' will now emit the version of fpm.
  - rpm: supports packaging fifo files (Adam Stephens)
  - deb: Add --deb-use-file-permissions (Adam Stephens)
  - cpan: Improve how fpm tries to find cpan artifacts for download (#614, Tim Nicholas)
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
1.3.3.4 (December 26, 2014)
  - Restructured staging/conversion step to separate flags and dependency
    resolution for multi-target building.
  - Composer module changed to match Debian/Fedora packaging guidelines.
    Prefixed vendor/ path is omitted, composer managebility averted.
    Dependency transformation still is somewhat rough.
  - Phar, Src, Exe, Composer module now use :attrs instead of [:meta].
  - Added backtrace output for generic error message.
  - Simple update filter added to "unprefix" input system packages.

1.3.3.3 (December 22, 2014)
  - Added IPK / Listaller packaging target plugin.
  - Added Windows (EXE) installer generation.
  - Added PHP composer as source plugin.
  - Added source-based file packaging support (-t src plugin).
  - Added PHP phar archive generation plugin.
  - Introduction of multi-target building with -t rpm,deb,ipk.
  - Added -u update filters for simple packaging tasks (existing plugins: man,
    desktop, appdata, fixperms, strip, composer, deps).
  - Added --deb-sign option.
  - Changed zip plugin to omit /tmp/package* path.
  - Now supports .pax and .cpio through the tar plugin.
  - First release of the `xpm` branch. Gemspec adapted.

1.3.3 (December 11, 2014)
  - The fpm project now uses Contributor Covenant. You can read more about this on
    the website: http://contributor-covenant.org/
  - npm: Fix bug causing all `-s npm` attempts to fail due to a missing method.
    This bug was introduced in 1.3.0. (#800, #806; Jordan Sissel)
  - rpm: fix bug in rpm input causing a crash if the input rpm did not have any triggers
    (#801, #802; Ted Elwartowski)

1.3.2 (November 4, 2014)
  - deb: conversion from another deb will automatically use any changelog found in 
    the source deb (Jordan Sissel)

1.3.1 (November 4, 2014)
  - deb: fix md5sums generation such that `dpkg -V` now works (#799, Matteo Panella)
  - rpm: Use maximum compression when choosing xz (#797, Ashish Kulkarni)
  
1.3.0 (October 25, 2014)
  - Fixed a bunch of Ruby 1.8.7-related bugs. (Jordan Sissel)
  - cpan: Fix bug in author handling (#744, Leon Weidauer)
  - cpan: Better removal of perllocal.pod (#763, #443, #510, Mathias Lafeldt)
  - rpm: Use lstat calls instead of stat, so we don't follow symlinks (#765, Shrijeet Paliwal)
  - rpm and deb: Now supports script actions on upgrades. This adds two new flags:
    --before-upgrade and --after-upgrade. (#772, #661; Daniel Haskin)
  - rpm: Package triggers are now supported. New flags: --rpm-trigger-before-install,
    --rpm-trigger-after-install, --rpm-trigger-before-uninstall, 
    --rpm-trigger-after-target-uninstall. (#626, Maxime Caumartin)
  - rpm: Add --rpm-init flag; similar to --deb-init. (Josh Dolitsky)
  - sh: Skip installation if already installed for the given version. If forced,
    the old installation is renamed. (#776, Chris Gerber)
  - deb: Allow Vendor field to be omitted now by specifying `--vendor ""` (#778, Nate Brown)
  - general: Add --log=level flag for setting log level. Levels are error, warn, info, debug. (Jordan SIssel)
  - cpan: Check for Build.PL first before Makefile.PL (#787, Daniel Jay Haskin)
  - dir: Don't follow symlinks when copying files (#658, Jordan Sissel)
  - deb: Automatically provide a 'changes' file in debs because lintian
    complains if they are missing. (#784, Jordan Sissel)
  - deb: Fix and warn for package names that have spaces (#779, Grantlyk)
  - npm: Automatically set the prefix to `npm prefix -g` (#758, Brady Wetherington and Jordan Sissel)

1.2.0 (July 25, 2014)
  - rpm: Add --rpm-verifyscript for adding a custom rpm verify script to
    your package. (Remi Hakim)
  - Allow the -p flag to target a directory for writing the output package
    (#656, Jordan Sissel)
  - Add --debug-workspace which skips any workspace cleanup to let users debug things
    if they break. (#720, #734; Jordan Sissel)
  - rpm: Add --rpm-attr for controlling attribute settings per file. This setting
    will likely be removed in the future once rpmbuild is no longer needed.
    (#719)
  - deb: Add --deb-meta-file to add arbitrary files to the control dir (#599, Dan Brown)
  - deb: Add --deb-interest and --deb-activate for adding package triggers (#595, Dan Brown)
  - cpan: Fix small bug in handling empty metadata fields (#712, Mathias Lafeldt)
  - rpm: Fix bug when specifying both --architecture and --rpm-os (#707, #716; Alan Ivey)
  - gem: Fix bug where --gem-version-bins is given but package has no bins (#688, Jan Vansteenkiste)
  - deb: Set permissions correct on the package's internals. Makes lintian happier. (Jan Vansteenkiste)
  - rpm: rpmbuild's _tmppath now respects --workdir (#714, Jordan Sissel)
  - gem/rpm: Add --rpm-verbatim-gem-dependencies to use old-style (fpm 0.4.x)
    rpm gem dependencies (#724, Jordan Sissel)
  - gem/rpm: Fix bug for gem pessimistic constraints when converting to rpm (Tom Duckering)
  - python: Fix small bug with pip invocations (#727, Dane Knecht)

1.1.0 (April 23, 2014)
  - New package type: zip, for converting to and from zip files (Jordan Sissel)
  - New package type: sh, a self-extracting package installation shell archive. (#651, Chris Gerber)
  - 'fpm --version' will now emit the version of fpm.
  - rpm: supports packaging fifo files (Adam Stephens)
  - deb: Add --deb-use-file-permissions (Adam Stephens)
  - cpan: Improve how fpm tries to find cpan artifacts for download (#614, Tim Nicholas)

Added CODE_OF_CONDUCT.md.



























>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
# Contributor Code of Conduct

As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.

We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.

Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.

This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)

Changes to CONTRIBUTORS.

15
16
17
18
19
20
21

22
23
24
25
Luke Macken (github: lmacken)
Matt Blair (github: mblair)
Thomas Meson (github: zllak)
Oliver Hookins (github: ohookins)
llasram
sbuss
Brett Gailey (github: dnbert)


If you have contributed (bug reports, feature requests, help in IRC, blog
posts, code, etc) and aren't listed here, please let me know if you wish to be
added!







>




15
16
17
18
19
20
21
22
23
24
25
26
Luke Macken (github: lmacken)
Matt Blair (github: mblair)
Thomas Meson (github: zllak)
Oliver Hookins (github: ohookins)
llasram
sbuss
Brett Gailey (github: dnbert)
Daniel Haskin (github: djhaskin987)

If you have contributed (bug reports, feature requests, help in IRC, blog
posts, code, etc) and aren't listed here, please let me know if you wish to be
added!

Changes to README.md.

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
The goal of FPM is to be able to easily build platform-native packages.

* Creating packages easily (deb, rpm, etc)
* Tweaking existing packages (removing files, changing metadata/dependencies)
* Stripping pre/post/maintainer scripts from packages

## System packages
You will need the ruby-dev packages for your system. These can be installed with the below



    apt-get install ruby-dev
    
    yum install ruby-devel





## Get with the download

You can install fpm with gem:

    gem install fpm

Building a package named "awesome" might look something like this:

    fpm -s <source type> -t <target type> [list of sources]...

"Source type" is what your package is coming from; a directory (dir), a rubygem (gem), an rpm (rpm), a python package (python), a php pear module (pear), etc.


"Target type" is what your output package form should be. Most common are "rpm"
and "deb" but others exist (solaris, etc)

You have two options for learning to run FPM:

1. If you're impatient, just scan through `fpm --help`; you'll need various







|
>
>

|

|
>
>
>
>











|
>







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
The goal of FPM is to be able to easily build platform-native packages.

* Creating packages easily (deb, rpm, etc)
* Tweaking existing packages (removing files, changing metadata/dependencies)
* Stripping pre/post/maintainer scripts from packages

## System packages

Many Linux distros do not ship ruby C headers or a compiler by default, and
you'll need that to install fpm.

    apt-get install ruby-dev gcc
    
    yum install ruby-devel gcc

Additional packages will be required depending on the source and target package
types (rpmbuild for rpm, etc.). FPM will show the commands that are required
which you must map to your distribution's package names.

## Get with the download

You can install fpm with gem:

    gem install fpm

Building a package named "awesome" might look something like this:

    fpm -s <source type> -t <target type> [list of sources]...

"Source type" is what your package is coming from; a directory (dir), a rubygem
(gem), an rpm (rpm), a python package (python), a php pear module (pear), etc.

"Target type" is what your output package form should be. Most common are "rpm"
and "deb" but others exist (solaris, etc)

You have two options for learning to run FPM:

1. If you're impatient, just scan through `fpm --help`; you'll need various
141
142
143
144
145
146
147




148
149
150
151
152
153
154
  request. If you don't know git, I also accept diff(1) formatted patches -
  whatever is most comfortable for you.
* Want to lurk about and see what others are doing? IRC (#fpm on
  irc.freenode.org) is a good place for this as is the 
  [mailing list](http://groups.google.com/group/fpm-users)

### Contributing by forking from GitHub





First, create a github account if you do not already have one.  Log in to
github and go to [the main fpm github page](https://github.com/jordansissel/fpm).

At the top right, click on the button labeled "Fork".  This will put a forked
copy of the main fpm repo into your account.  Next, clone your account's github
repo of fpm.  For example:







>
>
>
>







148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
  request. If you don't know git, I also accept diff(1) formatted patches -
  whatever is most comfortable for you.
* Want to lurk about and see what others are doing? IRC (#fpm on
  irc.freenode.org) is a good place for this as is the 
  [mailing list](http://groups.google.com/group/fpm-users)

### Contributing by forking from GitHub

Please note that this project is released with a Contributor Code of Conduct.
By participating in this project you agree to abide by its terms. See
the [CODE\_OF\_CONDUCT.md](CODE_OF_CONDUCT.md).

First, create a github account if you do not already have one.  Log in to
github and go to [the main fpm github page](https://github.com/jordansissel/fpm).

At the top right, click on the button labeled "Fork".  This will put a forked
copy of the main fpm repo into your account.  Next, clone your account's github
repo of fpm.  For example:
179
180
181
182
183
184
185











186
187
188
189
190
At this point, the fpm command should run directly from the code in your cloned
repo.  Now simply make whatever changes you want, commit the code, and push
your commit back to master.

If you think your changes are ready to be merged back to the main fpm repo, you
can generate a pull request on the github website for your repo and send it in
for review.












## More Documentation

[See the wiki for more docs](https://github.com/jordansissel/fpm/wiki)








>
>
>
>
>
>
>
>
>
>
>





190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
At this point, the fpm command should run directly from the code in your cloned
repo.  Now simply make whatever changes you want, commit the code, and push
your commit back to master.

If you think your changes are ready to be merged back to the main fpm repo, you
can generate a pull request on the github website for your repo and send it in
for review.

##Problems running bundle install?

If you are installing on Mac OS 10.9 (Mavericks) you will need to make sure that 
you have the standalone command line tools seperate from Xcode:

    $ xcode-select --install

Finally, click the install button on the prompt that appears.



## More Documentation

[See the wiki for more docs](https://github.com/jordansissel/fpm/wiki)

Deleted bin/fpm.

1
2
3
4
5
6
7
8
#!/usr/bin/env ruby

require "rubygems"
$: << File.join(File.dirname(__FILE__), "..", "lib")
require "fpm"
require "fpm/command"

exit(FPM::Command.run || 0)
<
<
<
<
<
<
<
<
















Added bin/xpm.

















>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
#!/usr/bin/env ruby

require "rubygems"
$: << File.join(File.dirname(__FILE__), "..", "lib")
require "fpm"
require "fpm/command"

exit(FPM::Command.run || 0)

Changes to examples/fpm-with-system-ruby/Makefile.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#
# feel free to change these to whatever makes sense
#
# debian package we rely on
RUBY_PACKAGE=ruby
# and the executable that comes from it
RUBY_BIN=/usr/bin/ruby
# the version we name the deb
VERSION=1.0.2
# where to get the sauce
GIT_URL=https://github.com/jordansissel/fpm.git
# the tag we checkout to build from
TAG_SPEC=refs/tags/v$(VERSION)

CHECKOUT_DIR=fpm-checkout
BUILD_DIR=build








|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#
# feel free to change these to whatever makes sense
#
# debian package we rely on
RUBY_PACKAGE=ruby
# and the executable that comes from it
RUBY_BIN=/usr/bin/ruby
# the version we name the deb
VERSION=1.1.0
# where to get the sauce
GIT_URL=https://github.com/jordansissel/fpm.git
# the tag we checkout to build from
TAG_SPEC=refs/tags/v$(VERSION)

CHECKOUT_DIR=fpm-checkout
BUILD_DIR=build
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
	rm -rf $(CHECKOUT_DIR)
	rm -rf $(BUILD_DIR)
	rm -f fpm*.deb

$(CHECKOUT_DIR):
	rm -rf $(CHECKOUT_DIR)
	git clone $(GIT_URL) $(CHECKOUT_DIR) --depth 1
	cd $(CHECKOUT_DIR) && git checkout $(TAG_SPEC)

$(BUNDLE_BIN):
	$(GEM_CMD) install bundler --install-dir=$(GEM_PATH) --no-ri --no-rdoc

$(FPM_BIN):
	mkdir --parents $(BIN_DIR)
# 	Couldn't think of a nice way to do this, so here is this code turd







|







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
	rm -rf $(CHECKOUT_DIR)
	rm -rf $(BUILD_DIR)
	rm -f fpm*.deb

$(CHECKOUT_DIR):
	rm -rf $(CHECKOUT_DIR)
	git clone $(GIT_URL) $(CHECKOUT_DIR) --depth 1
	cd $(CHECKOUT_DIR) && git fetch && git checkout $(TAG_SPEC)

$(BUNDLE_BIN):
	$(GEM_CMD) install bundler --install-dir=$(GEM_PATH) --no-ri --no-rdoc

$(FPM_BIN):
	mkdir --parents $(BIN_DIR)
# 	Couldn't think of a nice way to do this, so here is this code turd

Deleted fpm.gemspec.

1
2
3
4
5
6
7
8
9
10
11
12
13
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
require File.join(File.dirname(__FILE__), "lib/fpm/version")
Gem::Specification.new do |spec|
  files = []
  dirs = %w{lib bin templates}
  dirs.each do |dir|
    files += Dir["#{dir}/**/*"]
  end

  files << "LICENSE"
  files << "CONTRIBUTORS"
  files << "CHANGELIST"

  spec.name = "fpm"
  spec.version = FPM::VERSION
  spec.summary = "fpm - package building and mangling"
  spec.description = "Convert directories, rpms, python eggs, rubygems, and " \
    "more to rpms, debs, solaris packages and more. Win at package " \
    "management without wasting pointless hours debugging bad rpm specs!"
  spec.license = "MIT-like"

  # For parsing JSON (required for some Python support, etc)
  # http://flori.github.com/json/doc/index.html
  spec.add_dependency("json", ">= 1.7.7") # license: Ruby License
  
  # For logging
  # https://github.com/jordansissel/ruby-cabin
  spec.add_dependency("cabin", ">= 0.6.0") # license: Apache 2 

  # For backports to older rubies
  # https://github.com/marcandre/backports
  spec.add_dependency("backports", ">= 2.6.2") # license: MIT

  # For reading and writing rpms
  spec.add_dependency("arr-pm", "~> 0.0.9") # license: Apache 2

  # For command-line flag support
  # https://github.com/mdub/clamp/blob/master/README.markdown
  spec.add_dependency("clamp", "~> 0.6") # license: MIT

  # For starting external processes across various ruby interpreters
  spec.add_dependency("childprocess") # license: ???

  # For calling functions in dynamic libraries
  spec.add_dependency("ffi") # license: GPL3/LGPL3

  spec.add_development_dependency("rspec") # license: MIT (according to wikipedia)
  spec.add_development_dependency("insist", "~> 0.0.5") # license: ???
  spec.add_development_dependency("minitest")
  spec.add_development_dependency("pry")
  spec.add_development_dependency("stud")

  spec.files = files
  spec.require_paths << "lib"
  spec.bindir = "bin"
  spec.executables << "fpm"

  spec.author = "Jordan Sissel"
  spec.email = "jls@semicomplete.com"
  spec.homepage = "https://github.com/jordansissel/fpm"
end

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


























































































































Changes to lib/fpm/command.rb.

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

      # Lastly, include the default help output via Clamp.
      super
    ].join("\n")
  end # def help

  option "-t", "OUTPUT_TYPE",
    "the type of package you want to create (deb, rpm, solaris, etc)",
    :attribute_name => :output_type
  option "-s", "INPUT_TYPE",
    "the package type to use as input (gem, rpm, python, etc)",
    :attribute_name => :input_type






  option "-C", "CHDIR",
    "Change directory to here before searching for files",
    :attribute_name => :chdir
  option "--prefix", "PREFIX",
    "A path to prefix files with when building the target package. This may " \
    "be necessary for all input packages. For example, the 'gem' type will " \
    "prefix with your gem directory automatically."
  option ["-p", "--package"], "OUTPUT", "The package file path to output."
  option ["-f", "--force"], :flag, "Force output even if it will overwrite an " \
    "existing file", :default => false
  option ["-n", "--name"], "NAME", "The name to give to the package"










  option "--verbose", :flag, "Enable verbose output"
  option "--debug", :flag, "Enable debug output"



  option ["-v", "--version"], "VERSION", "The version to give to the package",
    :default => 1.0
  option "--iteration", "ITERATION",
    "The iteration to give to the package. RPM calls this the 'release'. " \
    "FreeBSD calls it 'PORTREVISION'. Debian calls this 'debian_revision'"
  option "--epoch", "EPOCH",
    "The epoch value for this package. RPM and Debian calls this 'epoch'. " \







|


|

>
>
>
>
>
>











>
>
>
>
>
>
>
>
>
>


>
>
>







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

      # Lastly, include the default help output via Clamp.
      super
    ].join("\n")
  end # def help

  option "-t", "OUTPUT_TYPE",
    "Package types you want to create (deb, rpm, solaris, etc)",
    :attribute_name => :output_type
  option "-s", "INPUT_TYPE",
    "Input source or format (dir, gem, rpm, deb, python, etc)",
    :attribute_name => :input_type
  option ["-u", "--update"], "FILTER,LIST",
    "Apply post-processing filters (man, appdata, desktop, etc.)",
    :attribute_name => :update_filter
#  option "--sign", "KEY",
#    "Sign package (needs a keyname for Debian packages)",
#    :attribute_name => :sign
  option "-C", "CHDIR",
    "Change directory to here before searching for files",
    :attribute_name => :chdir
  option "--prefix", "PREFIX",
    "A path to prefix files with when building the target package. This may " \
    "be necessary for all input packages. For example, the 'gem' type will " \
    "prefix with your gem directory automatically."
  option ["-p", "--package"], "OUTPUT", "The package file path to output."
  option ["-f", "--force"], :flag, "Force output even if it will overwrite an " \
    "existing file", :default => false
  option ["-n", "--name"], "NAME", "The name to give to the package"

  loglevels = %w(error warn info debug)
  option "--log", "LEVEL", "Set the log level. Values: #{loglevels.join(", ")}.",
    :attribute_name => :log_level do |val|
    val.downcase.tap do |v|
      if !loglevels.include?(v)
        raise FPM::Package::InvalidArgument, "Invalid log level, #{v.inspect}. Must be one of: #{loglevels.join(", ")}"
      end
    end
  end # --log
  option "--verbose", :flag, "Enable verbose output"
  option "--debug", :flag, "Enable debug output"
  option "--debug-workspace", :flag, "Keep any file workspaces around for " \
    "debugging. This will disable automatic cleanup of package staging and " \
    "build paths. It will also print which directories are available."
  option ["-v", "--version"], "VERSION", "The version to give to the package",
    :default => 1.0
  option "--iteration", "ITERATION",
    "The iteration to give to the package. RPM calls this the 'release'. " \
    "FreeBSD calls it 'PORTREVISION'. Debian calls this 'debian_revision'"
  option "--epoch", "EPOCH",
    "The epoch value for this package. RPM and Debian calls this 'epoch'. " \
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
    "A dependency. This flag can be specified multiple times. Value is " \
    "usually in the form of: -d 'name' or -d 'name > version'",
    :multivalued => true, :attribute_name => :dependencies

  option "--no-depends", :flag, "Do not list any dependencies in this package",
    :default => false

  option "--no-auto-depends", :flag, "Do not list any dependencies in this" \
    "package automatically", :default => false

  option "--provides", "PROVIDES",
    "What this package provides (usually a name). This flag can be "\
    "specified multiple times.", :multivalued => true,
    :attribute_name => :provides
  option "--conflicts", "CONFLICTS",
    "Other packages/versions this package conflicts with. This flag can " \
    "specified multiple times.", :multivalued => true,
    :attribute_name => :conflicts
  option "--replaces", "REPLACES",
    "Other packages/versions this package replaces. This flag can be "\
    "specified multiple times.", :multivalued => true,
    :attribute_name => :replaces

  option "--config-files", "CONFIG_FILES",
    "Mark a file in the package as being a config file. This uses 'conffiles'" \
    " in debs and %config in rpm. If you have multiple files to mark as " \
    "configuration files, specify this flag multiple times.  If argument is " \







|



|







|







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
    "A dependency. This flag can be specified multiple times. Value is " \
    "usually in the form of: -d 'name' or -d 'name > version'",
    :multivalued => true, :attribute_name => :dependencies

  option "--no-depends", :flag, "Do not list any dependencies in this package",
    :default => false

  option "--no-auto-depends", :flag, "Do not list any dependencies in this " \
    "package automatically", :default => false

  option "--provides", "PROVIDES",
    "What this package provides (usually a name). This flag can be " \
    "specified multiple times.", :multivalued => true,
    :attribute_name => :provides
  option "--conflicts", "CONFLICTS",
    "Other packages/versions this package conflicts with. This flag can " \
    "specified multiple times.", :multivalued => true,
    :attribute_name => :conflicts
  option "--replaces", "REPLACES",
    "Other packages/versions this package replaces. This flag can be " \
    "specified multiple times.", :multivalued => true,
    :attribute_name => :replaces

  option "--config-files", "CONFIG_FILES",
    "Mark a file in the package as being a config file. This uses 'conffiles'" \
    " in debs and %config in rpm. If you have multiple files to mark as " \
    "configuration files, specify this flag multiple times.  If argument is " \
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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
198
199
200
201
202
  option "--url", "URI", "Add a url for this package.",
    :default => "http://example.com/no-uri-given"
  option "--inputs", "INPUTS_PATH",
    "The path to a file containing a newline-separated list of " \
    "files and dirs to use as input."

  option "--post-install", "FILE",
    "(DEPRECATED, use --after-install) a script to be run after " \
    "package installation" do |val|
    @after_install = File.expand_path(val) # Get the full path to the script
  end # --post-install (DEPRECATED)
  option "--pre-install", "FILE",
    "(DEPRECATED, use --before-install) a script to be run before " \
    "package installation" do |val|
    @before_install = File.expand_path(val) # Get the full path to the script
  end # --pre-install (DEPRECATED)
  option "--post-uninstall", "FILE",
      "(DEPRECATED, use --after-remove) a script to be run after " \
      "package removal" do |val|
    @after_remove = File.expand_path(val) # Get the full path to the script
  end # --post-uninstall (DEPRECATED)
  option "--pre-uninstall", "FILE",
    "(DEPRECATED, use --before-remove) a script to be run before " \
    "package removal"  do |val|
    @before_remove = File.expand_path(val) # Get the full path to the script
  end # --pre-uninstall (DEPRECATED)

  option "--after-install", "FILE",
    "a script to be run after package installation" do |val|
    File.expand_path(val) # Get the full path to the script
  end # --after-install
  option "--before-install", "FILE",
    "a script to be run before package installation" do |val|
    File.expand_path(val) # Get the full path to the script
  end # --pre-install
  option "--after-remove", "FILE",
    "a script to be run after package removal" do |val|
    File.expand_path(val) # Get the full path to the script
  end # --after-remove
  option "--before-remove", "FILE",
    "a script to be run before package removal" do |val|
    File.expand_path(val) # Get the full path to the script
  end # --before-remove

















  option "--template-scripts", :flag,
    "Allow scripts to be templated. This lets you use ERB to template your " \
    "packaging scripts (for --after-install, etc). For example, you can do " \
    "things like <%= name %> to get the package name. For more information, " \
    "see the fpm wiki: " \
    "https://github.com/jordansissel/fpm/wiki/Script-Templates"

  option "--template-value", "KEY=VALUE",
    "Make 'key' available in script templates, so <%= key %> given will be " \
    "the provided value. Implies --template-scripts",
    :multivalued => true do |kv| 
    @template_scripts = true
    next kv.split("=", 2)
  end

  option "--workdir", "WORKDIR",
    "The directory you want fpm to do its work in, where 'work' is any file" \
    "copying, downloading, etc. Roughly any scratch space fpm needs to build" \
    "your package.", :default => Dir.tmpdir

  parameter "[ARGS] ...",
    "Inputs to the source package type. For the 'dir' type, this is the files" \
    " and directories you want to include in the package. For others, like " \
    "'gem', it specifies the packages to download and use as the gem input",
    :attribute_name => :args







|




|




|




|





|



|

|

|



|


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
















|
|







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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
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
  option "--url", "URI", "Add a url for this package.",
    :default => "http://example.com/no-uri-given"
  option "--inputs", "INPUTS_PATH",
    "The path to a file containing a newline-separated list of " \
    "files and dirs to use as input."

  option "--post-install", "FILE",
    "(DEPRECATED, use --after-install) A script to be run after " \
    "package installation" do |val|
    @after_install = File.expand_path(val) # Get the full path to the script
  end # --post-install (DEPRECATED)
  option "--pre-install", "FILE",
    "(DEPRECATED, use --before-install) A script to be run before " \
    "package installation" do |val|
    @before_install = File.expand_path(val) # Get the full path to the script
  end # --pre-install (DEPRECATED)
  option "--post-uninstall", "FILE",
      "(DEPRECATED, use --after-remove) A script to be run after " \
      "package removal" do |val|
    @after_remove = File.expand_path(val) # Get the full path to the script
  end # --post-uninstall (DEPRECATED)
  option "--pre-uninstall", "FILE",
    "(DEPRECATED, use --before-remove) A script to be run before " \
    "package removal"  do |val|
    @before_remove = File.expand_path(val) # Get the full path to the script
  end # --pre-uninstall (DEPRECATED)

  option "--after-install", "FILE",
    "A script to be run after package installation" do |val|
    File.expand_path(val) # Get the full path to the script
  end # --after-install
  option "--before-install", "FILE",
    "A script to be run before package installation" do |val|
    File.expand_path(val) # Get the full path to the script
  end # --before-install
  option "--after-remove", "FILE",
    "A script to be run after package removal" do |val|
    File.expand_path(val) # Get the full path to the script
  end # --after-remove
  option "--before-remove", "FILE",
    "A script to be run before package removal" do |val|
    File.expand_path(val) # Get the full path to the script
  end # --before-remove
  option "--after-upgrade", "FILE",
    "A script to be run after package upgrade. If not specified,\n" \
        "--before-install, --after-install, --before-remove, and \n" \
        "--after-remove wil behave in a backwards-compatible manner\n" \
        "(they will not be upgrade-case aware).\n" \
        "Currently only supports deb and rpm packages." do |val|
    File.expand_path(val) # Get the full path to the script
  end # --after-upgrade
  option "--before-upgrade", "FILE",
    "A script to be run before package upgrade. If not specified,\n" \
        "--before-install, --after-install, --before-remove, and \n" \
        "--after-remove wil behave in a backwards-compatible manner\n" \
        "(they will not be upgrade-case aware).\n" \
        "Currently only supports deb and rpm packages." do |val|
    File.expand_path(val) # Get the full path to the script
  end # --before-upgrade

  option "--template-scripts", :flag,
    "Allow scripts to be templated. This lets you use ERB to template your " \
    "packaging scripts (for --after-install, etc). For example, you can do " \
    "things like <%= name %> to get the package name. For more information, " \
    "see the fpm wiki: " \
    "https://github.com/jordansissel/fpm/wiki/Script-Templates"

  option "--template-value", "KEY=VALUE",
    "Make 'key' available in script templates, so <%= key %> given will be " \
    "the provided value. Implies --template-scripts",
    :multivalued => true do |kv| 
    @template_scripts = true
    next kv.split("=", 2)
  end

  option "--workdir", "WORKDIR",
    "The directory you want fpm to do its work in, where 'work' is any file " \
    "copying, downloading, etc. Roughly any scratch space fpm needs to build " \
    "your package.", :default => Dir.tmpdir

  parameter "[ARGS] ...",
    "Inputs to the source package type. For the 'dir' type, this is the files" \
    " and directories you want to include in the package. For others, like " \
    "'gem', it specifies the packages to download and use as the gem input",
    :attribute_name => :args
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
  def execute
    # Short-circuit if someone simply runs `fpm --version`
    if ARGV == [ "--version" ]
      puts FPM::VERSION
      return 0
    end

    @logger.level = :warn







    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]

    @logger.level = :info if verbose? # --verbose
    @logger.level = :debug if debug? # --debug

    input = input_class.new

    # Merge in package settings. 
    # The 'settings' stuff comes in from #apply_options, which goes through
    # all the options defined in known packages and puts them into our command.
    # Flags in packages defined as "--foo-bar" become named "--<packagetype>-foo-bar"







|
>
>
>
>
>

>

|








|



|





|


|



<
<
<
<







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
  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]





    input = input_class.new

    # Merge in package settings. 
    # The 'settings' stuff comes in from #apply_options, which goes through
    # all the options defined in known packages and puts them into our command.
    # Flags in packages defined as "--foo-bar" become named "--<packagetype>-foo-bar"
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
325
326
327
328
329
330
331
332
333
       
        # 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)







|














|
|



















|







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
367
368
369
370
371
       
        # 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 66 #EX_NOINPUT
      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)
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
367
368
369
370


371
372
373
374
375
376
377
378
379
380
381
382





















383

384
385
386
387
388
389
390
391
392
393
394
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


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

    input.conflicts += conflicts
    input.dependencies += dependencies
    input.provides += provides
    input.replaces += replaces
    input.config_files += config_files
    input.directories += directories










    
    script_errors = []
    setscript = proc do |scriptname|
      # '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)



    # 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)

    # Provide any template values as methods on the package.
    if template_scripts?
      template_value_list.each do |key, value|
        (class << output; self; end).send(:define_method, key) { value }
      end
    end

    # Write the output somewhere, package can be nil if no --package is specified, 
    # and that's OK.
    
    # If the package output (-p flag) is a directory, write to the default file name
    # but inside that directory.
    if ! package.nil? && File.directory?(package)
      package_file = File.join(package, output.to_s)
    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::Package::InvalidArgument => e
    @logger.error("Invalid package argument: #{e}")
    return 1
  rescue FPM::Util::ProcessFailed => e
    @logger.error("Process failed: #{e}")
    return 1



  ensure











    input.cleanup unless input.nil?
    output.cleanup unless output.nil?


  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)



  end # def run

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







>
>
>
>
>
>
>
>
>
>










|











>
>







|




>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
|

|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|

|
|

|
|

|
|
<
<
<

|
|
>
>
>

>
>
>
>
>
>
>
>
>
>
>
|
|
>
>



<
|









|












>
>
>







380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
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
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
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
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551

    input.conflicts += conflicts
    input.dependencies += dependencies
    input.provides += provides
    input.replaces += replaces
    input.config_files += config_files
    input.directories += directories

#-- not sure what this was meant for, attrs was a hash already
    #h = {}
    #attrs.each do | e |
    #
    #  s = e.split(':', 2)
    #  h[s.last] = s.first
    #end
    #input.attrs = h

    
    script_errors = []
    setscript = proc do |scriptname|
      # '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 '-n', " \
                    "for example, '-n packagename')")
      return 1
    end

    # Apply update filters on staging dir, prior to packaging
    unless update_filter.nil?
      update_filter.split(/[\s,;+]+/).each do |filter_type|
        opts = []
        if filter_type =~ /(\w+)[=:]+(.+)/
          filter_type, opts = [$1, $2.split(/\W+/)]
        end
        if not FPM::Package.types.include?("filter_#{filter_type}")
          logger.warn("Unknown -u update filter '#{filter_type}'")
          next
        end
        filter = input.convert(FPM::Package.types["filter_#{filter_type}"])
        filter.update(opts)
      end
    end

    # Traverse output types (-t deb,rpm,pkg,exe).
    # Scope output here, so it's available for ensure block.
    output = nil 
    output_type.split(/[\s,;+]+/).uniq.each do |output_type|
      
      # Convert to the output type
      output_class = FPM::Package.types[output_type]
      output = input.convert(output_class)

      # Provide any template values as methods on the package.
      if template_scripts?
        template_value_list.each do |key, value|
          (class << output; self; end).send(:define_method, key) { value }
        end
      end

      # Write the output somewhere, package can be nil if no --package is specified, 
      # and that's OK.
      
      # If the package output (-p flag) is a directory, write to the default file name
      # but inside that directory.
      if ! package.nil? && File.directory?(package)
        package_file = File.join(package, output.to_s)
      else
        package_file = output.to_s(package)
      end

      begin
        output.output(package_file)
      rescue FPM::Package::FileAlreadyExists => e
        logger.fatal(e.message)
        return 77 #EX_PERM
      rescue FPM::Package::ParentDirectoryMissing => e
        logger.fatal(e.message)
        return 73 #EX_CANTCREAT
      end

      logger.log("Created package", :path => package_file)
    end # each output_type
  rescue FPM::Util::ExecutableNotFound => e
    logger.error("Need executable '#{e}' to convert #{input_type} to #{output_type}")
    return 69 #EX_UNAVAILABLE
  rescue FPM::InvalidPackageConfiguration => e
    logger.error("Invalid package configuration: #{e}")
    return 78 #EX_CONFIG



  rescue FPM::Util::ProcessFailed => e
    logger.error("Process failed: #{e}")
    return 74 #EX_IOERR
  rescue => e
    logger.fatal("Error: #{e}\n#{e.backtrace}")
    return 70 #EX_SOFTWARE
  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
    return 0 #EX_OK
  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.
  class Validator
491
492
493
494
495
496
497

498
499
500

501
502
503
504
505
506
507
        mandatory(FPM::Package.types.include?(val),
                  "Invalid input package -s flag) type #{val.inspect}. " \
                  "Expected one of: #{types.join(", ")}")
      end

      with(@command.output_type) do |val|
        next if val.nil?

        mandatory(FPM::Package.types.include?(val),
                  "Invalid output package (-t flag) type #{val.inspect}. " \
                  "Expected one of: #{types.join(", ")}")

      end

      with (@command.dependencies) do |dependencies|
        # Verify dependencies don't include commas (#257)
        dependencies.each do |dep|
          next unless dep.include?(",")
          splitdeps = dep.split(/\s*,\s*/)







>
|


>







578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
        mandatory(FPM::Package.types.include?(val),
                  "Invalid input package -s flag) type #{val.inspect}. " \
                  "Expected one of: #{types.join(", ")}")
      end

      with(@command.output_type) do |val|
        next if val.nil?
        val.split(/[\s,;+]+/).each do |val|
          mandatory(FPM::Package.types.include?(val),
                  "Invalid output package (-t flag) type #{val.inspect}. " \
                  "Expected one of: #{types.join(", ")}")
        end
      end

      with (@command.dependencies) do |dependencies|
        # Verify dependencies don't include commas (#257)
        dependencies.each do |dep|
          next unless dep.include?(",")
          splitdeps = dep.split(/\s*,\s*/)

Changes to lib/fpm/package.rb.

107
108
109
110
111
112
113


114
115
116
117
118
119
120
121
122
123
124
125

  attr_accessor :directories

  # Any other attributes specific to this package.
  # This is where you'd put rpm, deb, or other specific attributes.
  attr_accessor :attributes



  private

  def initialize
    @logger = Cabin::Channel.get

    # Attributes for this specific package 
    @attributes = {}

    # Reference
    # http://www.debian.org/doc/manuals/maint-guide/first.en.html
    # http://wiki.debian.org/DeveloperConfiguration
    # https://github.com/jordansissel/fpm/issues/37







>
>



<
<







107
108
109
110
111
112
113
114
115
116
117
118


119
120
121
122
123
124
125

  attr_accessor :directories

  # Any other attributes specific to this package.
  # This is where you'd put rpm, deb, or other specific attributes.
  attr_accessor :attributes

  attr_accessor :attrs

  private

  def initialize


    # Attributes for this specific package 
    @attributes = {}

    # Reference
    # http://www.debian.org/doc/manuals/maint-guide/first.en.html
    # http://wiki.debian.org/DeveloperConfiguration
    # https://github.com/jordansissel/fpm/issues/37
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
    @provides = []
    @conflicts = []
    @replaces = []
    @dependencies = []
    @scripts = {}
    @config_files = []
    @directories = []


    staging_path
    build_path
  end # def initialize

  # Get the 'type' for this instance.
  #
  # For FPM::Package::ABC, this returns 'abc'
  def type
    self.class.type
  end # def type

  # Convert this package to a new package type
  def convert(klass)
    @logger.info("Converting #{self.type} to #{klass.type}")

    exclude

    pkg = klass.new
    pkg.cleanup_staging # purge any directories that may have been created by klass.new

    # copy other bits
    ivars = [
      :@architecture, :@category, :@config_files, :@conflicts,
      :@dependencies, :@description, :@epoch, :@iteration, :@license, :@maintainer,
      :@name, :@provides, :@replaces, :@scripts, :@url, :@vendor, :@version,
      :@directories, :@staging_path
    ]
    ivars.each do |ivar|
      #@logger.debug("Copying ivar", :ivar => ivar, :value => instance_variable_get(ivar),
                    #:from => self.type, :to => pkg.type)
      pkg.instance_variable_set(ivar, instance_variable_get(ivar))
    end

    # Attributes are special! We do not want to remove the default values of
    # the destination package type unless their value is specified on the
    # source package object.







>














|











|


|







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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
    @provides = []
    @conflicts = []
    @replaces = []
    @dependencies = []
    @scripts = {}
    @config_files = []
    @directories = []
    @attrs = {}

    staging_path
    build_path
  end # def initialize

  # Get the 'type' for this instance.
  #
  # For FPM::Package::ABC, this returns 'abc'
  def type
    self.class.type
  end # def type

  # Convert this package to a new package type
  def convert(klass)
    logger.info("Converting #{self.type} to #{klass.type}")

    exclude

    pkg = klass.new
    pkg.cleanup_staging # purge any directories that may have been created by klass.new

    # copy other bits
    ivars = [
      :@architecture, :@category, :@config_files, :@conflicts,
      :@dependencies, :@description, :@epoch, :@iteration, :@license, :@maintainer,
      :@name, :@provides, :@replaces, :@scripts, :@url, :@vendor, :@version,
      :@directories, :@staging_path, :@attrs
    ]
    ivars.each do |ivar|
      #logger.debug("Copying ivar", :ivar => ivar, :value => instance_variable_get(ivar),
                    #:from => self.type, :to => pkg.type)
      pkg.instance_variable_set(ivar, instance_variable_get(ivar))
    end

    # Attributes are special! We do not want to remove the default values of
    # the destination package type unless their value is specified on the
    # source package object.
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
    else
      return File.join(@build_path, path)
    end
  end # def build_path

  # Clean up any temporary storage used by this class.
  def cleanup
    cleanup_staging unless @logger.level == :debug
    cleanup_build unless @logger.level == :debug
  end # def cleanup

  def cleanup_staging
    if File.directory?(staging_path)
      @logger.debug("Cleaning up staging path", :path => staging_path)
      FileUtils.rm_r(staging_path) 
    end
  end # def cleanup_staging

  def cleanup_build
    if File.directory?(build_path)
      @logger.debug("Cleaning up build path", :path => build_path)
      FileUtils.rm_r(build_path) 
    end
  end # def cleanup_build

  # List all files in the staging_path
  #
  # The paths will all be relative to staging_path and will not include that







|
|




|






|







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
    else
      return File.join(@build_path, path)
    end
  end # def build_path

  # Clean up any temporary storage used by this class.
  def cleanup
    cleanup_staging unless logger.level == :debug
    cleanup_build unless logger.level == :debug
  end # def cleanup

  def cleanup_staging
    if File.directory?(staging_path)
      logger.debug("Cleaning up staging path", :path => staging_path)
      FileUtils.rm_r(staging_path) 
    end
  end # def cleanup_staging

  def cleanup_build
    if File.directory?(build_path)
      logger.debug("Cleaning up build path", :path => build_path)
      FileUtils.rm_r(build_path) 
    end
  end # def cleanup_build

  # List all files in the staging_path
  #
  # The paths will all be relative to staging_path and will not include that
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
  def template_dir
    File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "templates"))
  end

  def template(path)
    template_path = File.join(template_dir, path)
    template_code = File.read(template_path)
    @logger.info("Reading template", :path => template_path)
    erb = ERB.new(template_code, nil, "-")
    erb.filename = template_path
    return erb
  end # def template

  def to_s(fmt="NAME.TYPE")
    fmt = "NAME.TYPE" if fmt.nil?
    fullversion = version.to_s
    fullversion += "-#{iteration}" if iteration
    return fmt.gsub("ARCH", architecture.to_s) \
      .gsub("NAME", name.to_s) \
      .gsub("FULLVERSION", fullversion) \
      .gsub("VERSION", version.to_s) \
      .gsub("ITERATION", iteration.to_s) \
      .gsub("EPOCH", epoch.to_s) \
      .gsub("TYPE", type.to_s)
  end # def to_s

  def edit_file(path)
    editor = ENV['FPM_EDITOR'] || ENV['EDITOR'] || 'vi'
    @logger.info("Launching editor", :file => path)
    command = "#{editor} #{Shellwords.escape(path)}"
    system("#{editor} #{Shellwords.escape(path)}")
    if !$?.success?
      raise ProcessFailed.new("'#{editor}' failed (exit code " \
                              "#{$?.exitstatus}) Full command was: "\
                              "#{command}");
    end







|




















|







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
  def template_dir
    File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "templates"))
  end

  def template(path)
    template_path = File.join(template_dir, path)
    template_code = File.read(template_path)
    logger.info("Reading template", :path => template_path)
    erb = ERB.new(template_code, nil, "-")
    erb.filename = template_path
    return erb
  end # def template

  def to_s(fmt="NAME.TYPE")
    fmt = "NAME.TYPE" if fmt.nil?
    fullversion = version.to_s
    fullversion += "-#{iteration}" if iteration
    return fmt.gsub("ARCH", architecture.to_s) \
      .gsub("NAME", name.to_s) \
      .gsub("FULLVERSION", fullversion) \
      .gsub("VERSION", version.to_s) \
      .gsub("ITERATION", iteration.to_s) \
      .gsub("EPOCH", epoch.to_s) \
      .gsub("TYPE", type.to_s)
  end # def to_s

  def edit_file(path)
    editor = ENV['FPM_EDITOR'] || ENV['EDITOR'] || 'vi'
    logger.info("Launching editor", :file => path)
    command = "#{editor} #{Shellwords.escape(path)}"
    system("#{editor} #{Shellwords.escape(path)}")
    if !$?.success?
      raise ProcessFailed.new("'#{editor}' failed (exit code " \
                              "#{$?.exitstatus}) Full command was: "\
                              "#{command}");
    end
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
      installdir = staging_path
    end

    Find.find(installdir) do |path|
      match_path = path.sub("#{installdir.chomp('/')}/", '')

      attributes[:excludes].each do |wildcard|
        @logger.debug("Checking path against wildcard", :path => match_path, :wildcard => wildcard)

        if File.fnmatch(wildcard, match_path)
          @logger.info("Removing excluded path", :path => match_path, :matches => wildcard)
          FileUtils.remove_entry_secure(path)
          Find.prune
          break
        end
      end
    end
  end # def exclude







|


|







371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
      installdir = staging_path
    end

    Find.find(installdir) do |path|
      match_path = path.sub("#{installdir.chomp('/')}/", '')

      attributes[:excludes].each do |wildcard|
        logger.debug("Checking path against wildcard", :path => match_path, :wildcard => wildcard)

        if File.fnmatch(wildcard, match_path)
          logger.info("Removing excluded path", :path => match_path, :matches => wildcard)
          FileUtils.remove_entry_secure(path)
          Find.prune
          break
        end
      end
    end
  end # def exclude
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507

  def output_check(output_path)
    if !File.directory?(File.dirname(output_path))
      raise ParentDirectoryMissing.new(output_path)
    end
    if File.file?(output_path)
      if attributes[:force?]
        @logger.warn("Force flag given. Overwriting package at #{output_path}")
        File.delete(output_path)
      else
        raise FileAlreadyExists.new(output_path)
      end
    end
  end # def output_path








|







494
495
496
497
498
499
500
501
502
503
504
505
506
507
508

  def output_check(output_path)
    if !File.directory?(File.dirname(output_path))
      raise ParentDirectoryMissing.new(output_path)
    end
    if File.file?(output_path)
      if attributes[:force?]
        logger.warn("Force flag given. Overwriting package at #{output_path}")
        File.delete(output_path)
      else
        raise FileAlreadyExists.new(output_path)
      end
    end
  end # def output_path

515
516
517
518
519
520
521
522
523

  # General public API
  public(:type, :initialize, :convert, :input, :output, :to_s, :cleanup, :files,
         :version, :script, :provides=)

  # Package internal public api
  public(:cleanup_staging, :cleanup_build, :staging_path, :converted_from,
         :edit_file)
end # class FPM::Package







|

516
517
518
519
520
521
522
523
524

  # General public API
  public(:type, :initialize, :convert, :input, :output, :to_s, :cleanup, :files,
         :version, :script, :provides=)

  # Package internal public api
  public(:cleanup_staging, :cleanup_build, :staging_path, :converted_from,
         :edit_file, :build_path)
end # class FPM::Package

Added lib/fpm/package/composer.rb.



















































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
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
#
# encoding: utf-8
# api: fpm
# title: Composer source
# description: Converts composer bundles into system or/and phar packages
# type: package
# depends: bin:composer
# category: source
# version: 0.5
# state: beta
# license: MITL
# author: mario#include-once:org
# 
# Creates system packages for composer/packagist bundles.
#
#  → Either works on individual vendor/*/*/ paths as input
#    (the vendor/ prefix can be omitted).
#
#  → Or downloads a single vnd/pkgname bundle.
#
# Also supports different target variations:
#
#  → Syspackages (deb/rpm) end up in /usr/share/php/Vnd/Pkg.
#
#  → Whereas --composer-phar creates Phars embedded into system
#    packages, with target names of /usr/share/php/vnd-pkg.phar.
#
#  → While a standard `-t phar` target will just compact individual
#    components into localized phars.
#
# NOTES
#
# → Currently rewritten to conform to Debian pkg-php-tools and Fedora
#   schemes.
# → The vendor/ extraction prefix isn't retained any longer. Composer wasn't
#   meant to manage system-globally installed libraries.
# → System packages thus need a global autoloader (shared.phar / phpab)
#   or manual includes.
# → The build process utilizes `composer require` to fetch new packages,
#   if xpm -s composer isn't run from within a composer managed project.
# → Bring in line with Debian packaging scheme, dh_phpcomposer/pkg-php
#   dropped -composer- in package names, got rid of nested /vendor/deep/dirs/
#   yet to adopt complete version/dependency translation after all?
# ø Dependencies are neither in line with Fedora/RPM version expressions,
#   http://fedoraproject.org/wiki/Packaging:PHP
#   https://twiki.cern.ch/twiki/bin/view/Main/RPMAndDebVersioning
# ø Unclear: require/use `-u composer` for reading meta? Currently just
#   composer.lock is scanned for input.
#

require "fpm/package"
require "fpm/util"
require "fpm/errors"
require "fpm/package/zip"
require "fileutils"
require "json"


# Composer reading (source only)
class FPM::Package::Composer < FPM::Package

  option "--ver", "1.0\@dev", "Which version to checkout", :default=>nil
  option "--phar", :flag, "Convert bundle into .phar plugin package", :default=>false
  
  public
  attr_accessor :in_bundle

  def initialize(*args)
    super(*args)
    @architecture = "all"
    @name_prefix = ""      # hold "php-" or "phar-" syspackage name prefix
    @target_dir = nil      # two-level base directory under /usr/share/php
    @in_bundle = "n/a"     # packagist bundle name
    @once = false          # prevent double .input() invocation
  end

  # download composer bundle, or compact from existing vendor/ checkout
  def input(vnd_pkg_path)

    # general params
    in_bundle = vnd_pkg_path.gsub(/^(.+\/+)*vendor\/+|\/(?=\/)|\/+$/, "")
    @name = in_bundle.gsub(/[\W]+/, "-")
    json = {}
    if @once
      @once = true
      raise FPM::InvalidPackageConfiguration, "You can't input multiple bundle names. Only one package can be built at a time currently. Use a shell loop please."
    elsif in_bundle =~ /^composer\/\w+\.\w+/
      raise FPM::InvalidPackageConfiguration, "composer/*.* files specified as input. Supply only one bundle id."
    end

    # copying mode
    if File.exist?("vendor/" + in_bundle)
      json = parse_lock("composer.lock", in_bundle)[in_bundle]
      # localize contents below vendor/*/*/ input directory
      ::Dir.chdir("./vendor/#{in_bundle}/#{json['target-dir']}/") do
        FileUtils.cp_r(glob("./*"), build_path)
      end
    else
      # download one package (and dependencies, which are thrown away afterwards)
      ::Dir.chdir(staging_path) do
        ver = attributes[:composer_ver]
        safesystem(
          composer, "require", "--prefer-dist", "--update-no-dev", "--ignore-platform-reqs",
          "--no-ansi", "--no-interaction", in_bundle, *(ver ? [ver] : [])
        )
        # localize Vnd/Pkg folder
        json = parse_lock("composer.lock", in_bundle)[in_bundle]
        FileUtils.mv(glob("./vendor/#{in_bundle}/#{json['target-dir']}/*"), build_path)
        FileUtils.rm_r(glob("#{staging_path}/*"))
      end
    end

    #-- staging
    # At this point the build_path contains just the actual class files, etc.
    # Conversion to sys/phar/sysphar is handled in convert() along with the
    # dependency translation.
    composer_json_import(json)
    @target_dir = json.include?("target-dir") ? json["target-dir"] : in_bundle
    attributes[:phar_format] = "zip+gz" unless attributes[:phar_format_given?]
  end # def output

  
  def convert(klass)
    pkg = super(klass)

    # becomes local -t phar
    if klass == FPM::Package::Phar
      pkg.instance_variable_set(:@staging_path, build_path)
      @name_prefix = ""  # needs to be reset in case of multi-target building

    # prepare matroska phar-in-deb/rpm, ends up in /usr/share/php/*.phar
    elsif attributes[:composer_phar_given?]
      FileUtils.mkdir_p(staging_dest = "#{staging_path}/usr/share/php")
      ::Dir.chdir("#{build_path}") do
        phar = convert(FPM::Package::Phar) # will loop internally
        phar.output("#{staging_dest}/#{@name}.phar");
      end
      @name_prefix = "phar-"

    # system package (deb/rpm) with plain files under /usr/share/php/Vnd/Pkg
    else
      dest = "/usr/share/php/#{@target_dir}"
      FileUtils.mkdir_p(dest = "#{pkg.staging_path}/#{dest}")
      FileUtils.cp_r(glob("#{build_path}/*"), dest)
      @name_prefix = "php-"
    end

    # add dependencies
    pkg.name = "#{@name_prefix}#{@name}"
    pkg.dependencies += @cdeps.collect { |k,v| require_convert(k, v, @name_prefix, klass) }.flatten.compact
    return pkg
  end # def convert


  # translate package names and versions
  def require_convert(k, v, prefix, klass)

    # package type/name maps
    map = { FPM::Package::RPM => :rpm, FPM::Package::Deb => :deb, FPM::Package::Phar => :phar }
    typ = map.include?(klass) ? map[klass] : :deb
    pn = {
      :php => { :phar => "php",     :deb => "php5-common", :rpm => "php(language)" },
      :ext => { :phar => "ext:",    :deb => "php5-",       :rpm => "php-" },
      :lib => { :phar => "sys:lib", :deb => "lib",         :rpm => "lib" },
      :bin => { :phar => "bin:",    :deb => "",            :rpm => "/usr/bin/" }
    }

    # package names, magic values
    case k = k.strip.gsub(/\W+/, "-")
      when /^php(-32bit|-64bit)?|^hhvm|^quercus/
        k = pn[:php][typ]
        @architecture = ($1 == "-32bit") ? "x86" : "amd64" if $1
      when /^(ext|lib|bin)-(\w+)$/
        k = pn[$1.to_sym][typ] + $2
      else
        k = prefix + k
    end

    # expand version specifiers (this is intentionally incomplete)
    if attributes[:no_depends_given?]
      v = ""
    else
      v = v.split(",").map {
        |v|
        case v = ver(v, typ)
          when "*"
            ""
          when /^[\d.-]+~*$/  # 1.0.1
            " = #{v}"
          when /^((\d+\.)*(\d+))\.\*/  # 1.0.*
            [" >= #{$1}.0", " <= #{$1}.999"]
          when /^([!><=]*)([\d.-]+~*)$/  # >= 2.0   # debianize_op() normalizes >, <, = anyway
            " #{$1} #{$2}"
          when /^~\s*([\d.-]+~*)$/  # ~2.0   # deb.fix_dependency translates that into a range ["pkg(>=1)", "pkg(<<2)"]
            " ~> #{$1}"
          else
            ""
        end
      }
    end
    return k ? v.flatten.map { |v| k + v } : nil
  end
  
  # normalize version strings to packaging system
  def ver(v, typ)
    v.gsub!(/ (?:^.+ \sAS\s (.+$))? | \s+() | ^v() /nix, "\\1")
    case typ
      when :deb
        v.gsub!(/[-@](dev|patch).*$/, "~~")
        v.gsub!(/[-@](alpha|beta|RC|stable).*$/, "~")
      when :rpm
        v.gsub!(/[-@](dev|patch|alpha|beta|RC|stable).*$/, "")
      else
        v.gsub!(/@/, "-")
    end
    return v
  end


  # collect per-package composer.json infos
  def composer_json_import(json)
    if json.key? "name" and not attributes[:vendor_given?]
      @vendor = json["name"].split(/\W/).first
    end
    if json.key? "version" and not attributes[:version_given?]
      @version = json["version"].sub(/^v/, "")
    end
    if json.key? "description" and not attributes[:description_given?]
      @description = json["description"]
    end
    if json.key? "license" and not attributes[:license_given?]
      @license = [json["license"]].flatten.join(", ")
    end
    if json.key? "homepage" and not attributes[:url_given?]
      @url = json["homepage"]
    end
    if json.key? "authors" and not attributes[:maintainer_given?]
      @maintainer = json["authors"].map{ |v| v.values.join(", ") }.first or nil
    end
    if json.key? "require" and dependencies.empty?
      @cdeps = json["require"]
    end
    # stash away complete composer struct for possible phar building
    @attrs[:composer] = json
  end


  # Extract package sections from composer.lock file, turn into pkgname→hash
  def parse_lock(fn, in_bundle)
    json = JSON.parse(File.read(fn))
    if !json.key? "packages"
      json["packages"] = []
    end
    if json.key? "packages-dev"
      json["packages"] += json["packages-dev"]
    end
    lock = Hash[  json["packages"].map{ |entry| [entry["name"], entry] }  ]
    unless lock.key? in_bundle
      raise FPM::InvalidPackageConfiguration, "Package name '#{in_bundle}' absent in composer.lock"
    end
    return lock
  end
  
  # Add composer.lock package date into per-package composer.json→extra→lock
  def inject_lock(fn, extra)
    json = JSON.parse(File.read(fn))
    json["extra"] ||= {}
    json["extra"]["lock"] = extra
    File.write(fn, JSON.pretty_generate(json))
  end

  # Locate composer binary
  def composer
    (`which composer` or `which composer.phar` or ("php "+`locate composer.phar`)).split("\n").first
  end

  def glob(path)
    ::Dir.glob(path)
  end

end # class ::Composer

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
    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



    # Not all things have 'author' listed.
    self.vendor = metadata["author"].join(", ") unless metadata["author"].nil?




    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)
140
141
142
143
144
145
146
147
148
149
150
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
    end #no_auto_depends

    ::Dir.chdir(moduledir) do
      # TODO(sissel): install build and config dependencies to resolve
      # build/configure requirements.
      # META.yml calls it 'configure_requires' and 'build_requires'
      # META.json calls it prereqs/build and prereqs/configure
 
      prefix = attributes[:prefix] || "/usr/local"
      # TODO(sissel): Set default INSTALL path?

      # Try Makefile.PL, Build.PL
      #
      if File.exists?("Makefile.PL")
        if attributes[:cpan_perl_lib_path]
          perl_lib_path = attributes[:cpan_perl_lib_path]
          safesystem(attributes[:cpan_perl_bin],
                     "-Mlocal::lib=#{build_path("cpan")}",
                     "Makefile.PL", "PREFIX=#{prefix}", "LIB=#{perl_lib_path}",
                     # Empty install_base to avoid local::lib being used.
                     "INSTALL_BASE=")
        else 
          safesystem(attributes[:cpan_perl_bin],
                     "-Mlocal::lib=#{build_path("cpan")}",
                     "Makefile.PL", "PREFIX=#{prefix}",
                     # Empty install_base to avoid local::lib being used.
                     "INSTALL_BASE=")
        end
        if attributes[:cpan_test?]
          make = [ "env", "PERL5LIB=#{build_path("cpan/lib/perl5")}", "make" ]
        else
          make = [ "make" ]
        end
        safesystem(*make)
        safesystem(*(make + ["test"])) if attributes[:cpan_test?]
        safesystem(*(make + ["DESTDIR=#{staging_path}", "install"]))
      elsif File.exists?("Build.PL")
        # Module::Build is in use here; different actions required.
        safesystem(attributes[:cpan_perl_bin],
                   "-Mlocal::lib=#{build_path("cpan")}",
                   "Build.PL")
        safesystem(attributes[:cpan_perl_bin],
                   "-Mlocal::lib=#{build_path("cpan")}",
                   "./Build")







|





<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|







146
147
148
149
150
151
152
153
154
155
156
157
158























159
160
161
162
163
164
165
166
    end #no_auto_depends

    ::Dir.chdir(moduledir) do
      # TODO(sissel): install build and config dependencies to resolve
      # build/configure requirements.
      # META.yml calls it 'configure_requires' and 'build_requires'
      # META.json calls it prereqs/build and prereqs/configure

      prefix = attributes[:prefix] || "/usr/local"
      # TODO(sissel): Set default INSTALL path?

      # Try Makefile.PL, Build.PL
      #























      if File.exists?("Build.PL")
        # Module::Build is in use here; different actions required.
        safesystem(attributes[:cpan_perl_bin],
                   "-Mlocal::lib=#{build_path("cpan")}",
                   "Build.PL")
        safesystem(attributes[:cpan_perl_bin],
                   "-Mlocal::lib=#{build_path("cpan")}",
                   "./Build")
193
194
195
196
197
198
199

























200
201
202
203
204
205
206
207
208
209

210
211
212
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
                     --destdir #{staging_path} --prefix #{prefix} --destdir #{staging_path}")
        else
           safesystem("./Build", "install",
                     "--prefix", prefix, "--destdir", staging_path,
                     # Empty install_base to avoid local::lib being used.
                     "--install_base", "")
        end

























      else
        raise FPM::InvalidPackageConfiguration, 
          "I don't know how to build #{name}. No Makefile.PL nor " \
          "Build.PL found"
      end

      # 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

      ::Dir.glob(File.join(staging_path, 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







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>










>
|
|














|



















|


















|
















|




|

|











|




|

|







176
177
178
179
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
208
209
210
211
212
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
                     --destdir #{staging_path} --prefix #{prefix} --destdir #{staging_path}")
        else
           safesystem("./Build", "install",
                     "--prefix", prefix, "--destdir", staging_path,
                     # Empty install_base to avoid local::lib being used.
                     "--install_base", "")
        end
      elsif File.exists?("Makefile.PL")
        if attributes[:cpan_perl_lib_path]
          perl_lib_path = attributes[:cpan_perl_lib_path]
          safesystem(attributes[:cpan_perl_bin],
                     "-Mlocal::lib=#{build_path("cpan")}",
                     "Makefile.PL", "PREFIX=#{prefix}", "LIB=#{perl_lib_path}",
                     # Empty install_base to avoid local::lib being used.
                     "INSTALL_BASE=")
        else
          safesystem(attributes[:cpan_perl_bin],
                     "-Mlocal::lib=#{build_path("cpan")}",
                     "Makefile.PL", "PREFIX=#{prefix}",
                     # Empty install_base to avoid local::lib being used.
                     "INSTALL_BASE=")
        end
        if attributes[:cpan_test?]
          make = [ "env", "PERL5LIB=#{build_path("cpan/lib/perl5")}", "make" ]
        else
          make = [ "make" ]
        end
        safesystem(*make)
        safesystem(*(make + ["test"])) if attributes[:cpan_test?]
        safesystem(*(make + ["DESTDIR=#{staging_path}", "install"]))


      else
        raise FPM::InvalidPackageConfiguration, 
          "I don't know how to build #{name}. No Makefile.PL nor " \
          "Build.PL found"
      end

      # 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.

45
46
47
48
49
50
51



52
53
54
55
56
57
58
    if !COMPRESSION_TYPES.include?(value)
      raise ArgumentError, "deb compression value of '#{value}' is invalid. " \
        "Must be one of #{COMPRESSION_TYPES.join(", ")}"
    end
    value
  end




  # Take care about the case when we want custom control file but still use fpm ...
  option "--custom-control", "FILEPATH",
    "Custom version of the Debian control file." do |control|
    File.expand_path(control)
  end

  # Add custom debconf config file







>
>
>







45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
    if !COMPRESSION_TYPES.include?(value)
      raise ArgumentError, "deb compression value of '#{value}' is invalid. " \
        "Must be one of #{COMPRESSION_TYPES.join(", ")}"
    end
    value
  end

  option "--sign", "KEY", 
    "Sign the resulting package with named GPG key."

  # Take care about the case when we want custom control file but still use fpm ...
  option "--custom-control", "FILEPATH",
    "Custom version of the Debian control file." do |control|
    File.expand_path(control)
  end

  # Add custom debconf config file
94
95
96
97
98
99
100


















101
102
103
104
105
106
107
  end

  option "--suggests", "PACKAGE", "Add PACKAGE to Suggests" do |pkg|
    @suggests ||= []
    @suggests << pkg
    next @suggests
  end



















  option "--field", "'FIELD: VALUE'", "Add custom field to the control file" do |fv|
    @custom_fields ||= {}
    field, value = fv.split(/: */, 2)
    @custom_fields[field] = value
    next @custom_fields
  end







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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
  end

  option "--suggests", "PACKAGE", "Add PACKAGE to Suggests" do |pkg|
    @suggests ||= []
    @suggests << pkg
    next @suggests
  end

  option "--meta-file", "FILEPATH", "Add FILEPATH to DEBIAN directory" do |file|
    @meta_files ||= []
    @meta_files << File.expand_path(file)
    next @meta_files
  end

  option "--interest", "EVENT", "Package is interested in EVENT trigger" do |event|
    @interested_triggers ||= []
    @interested_triggers << event
    next @interested_triggers
  end

  option "--activate", "EVENT", "Package activates EVENT trigger" do |event|
    @activated_triggers ||= []
    @activated_triggers << event
    next @activated_triggers
  end

  option "--field", "'FIELD: VALUE'", "Add custom field to the control file" do |fv|
    @custom_fields ||= {}
    field, value = fv.split(/: */, 2)
    @custom_fields[field] = value
    next @custom_fields
  end
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

  # 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







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







|









|



>
>
>
>
>
>







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
213
214
215
216
217
218

  # 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
  
  def prefix
    return (attributes[:prefix] or "/")
  end # def prefix
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
      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"))







|







231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
      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"))
308
309
310
311
312
313
314
315
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
    # Abort if the target path already exists.

    # 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
















    write_control_tarball

    # Tar up the staging_path into data.tar.{compression type}
    case self.attributes[:deb_compression]
      when "gz", nil
        datatar = build_path("data.tar.gz")
        compression = "-z"
      when "bzip2" 
        datatar = build_path("data.tar.bz2")
        compression = "-j"
      when "xz"
        datatar = build_path("data.tar.xz")
        compression = "-J"
      else
        raise FPM::InvalidPackageConfiguration,
          "Unknown compression type '#{self.attributes[:deb_compression]}'"
    end

    if attributes[:deb_changelog]
      dest_changelog = File.join(staging_path, "usr/share/doc/#{name}/changelog.Debian")
      FileUtils.mkdir_p(File.dirname(dest_changelog))


      FileUtils.cp attributes[:deb_changelog], dest_changelog

      File.chmod(0644, dest_changelog)





      safesystem("gzip", dest_changelog)

    end



    attributes.fetch(:deb_init_list, []).each do |init|
      name = File.basename(init, ".init")
      dest_init = File.join(staging_path, "etc/init.d/#{name}")
      FileUtils.mkdir_p(File.dirname(dest_init))
      FileUtils.cp init, dest_init
      File.chmod(0755, dest_init)







|

|





|





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



















|
|
|
>
>
|
>
|
>
>
>
>
>
|
>
|
>
>







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
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
    # Abort if the target path already exists.

    # 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)
      if script?(:before_install) or script?(:before_upgrade)
        scripts[:before_install] = template("deb/preinst_upgrade.sh.erb").result(binding)
      end
      if script?(:before_remove)
        scripts[:before_remove] = template("deb/prerm_upgrade.sh.erb").result(binding)
      end
      if script?(:after_install) or script?(:after_upgrade)
        scripts[:after_install] = template("deb/postinst_upgrade.sh.erb").result(binding)
      end
      if script?(:after_remove)
        scripts[:after_remove] = template("deb/postrm_upgrade.sh.erb").result(binding)
      end
    end

    write_control_tarball

    # Tar up the staging_path into data.tar.{compression type}
    case self.attributes[:deb_compression]
      when "gz", nil
        datatar = build_path("data.tar.gz")
        compression = "-z"
      when "bzip2" 
        datatar = build_path("data.tar.bz2")
        compression = "-j"
      when "xz"
        datatar = build_path("data.tar.xz")
        compression = "-J"
      else
        raise FPM::InvalidPackageConfiguration,
          "Unknown compression type '#{self.attributes[:deb_compression]}'"
    end

    # 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")
      dest_init = File.join(staging_path, "etc/init.d/#{name}")
      FileUtils.mkdir_p(File.dirname(dest_init))
      FileUtils.cp init, dest_init
      File.chmod(0755, dest_init)
386
387
388
389
390
391
392



393
394
395
396
397
398
399
400
401
402
403
404













405
406
407
408
409
410
411
    safesystem(*args)

    # pack up the .deb, which is just an 'ar' archive with 3 files
    # the 'debian-binary' file has to be first
    with(File.expand_path(output_path)) do |output_path|
      ::Dir.chdir(build_path) do
        safesystem("ar", "-qc", output_path, "debian-binary", "control.tar.gz", datatar)



      end
    end
  end # def output

  def converted_from(origin)
    self.dependencies = self.dependencies.collect do |dep|
      fix_dependency(dep)
    end.flatten
    self.provides = self.provides.collect do |provides|
      fix_provides(provides)
    end.flatten
      













  end # def converted_from

  def debianize_op(op)
    # Operators in debian packaging are <<, <=, =, >= and >>
    # So any operator like < or > must be replaced
    {:< => "<<", :> => ">>"}[op.to_sym] or op
  end







>
>
>











|
>
>
>
>
>
>
>
>
>
>
>
>
>







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
478
479
480
    safesystem(*args)

    # pack up the .deb, which is just an 'ar' archive with 3 files
    # the 'debian-binary' file has to be first
    with(File.expand_path(output_path)) do |output_path|
      ::Dir.chdir(build_path) do
        safesystem("ar", "-qc", output_path, "debian-binary", "control.tar.gz", datatar)
        if @attributes[:deb_sign]
          safesystem("dpkg-sig", "-s", "builder", "-k", @attributes[:deb_sign], output_path)
        end
      end
    end
  end # def output

  def converted_from(origin)
    self.dependencies = self.dependencies.collect do |dep|
      fix_dependency(dep)
    end.flatten
    self.provides = self.provides.collect do |provides|
      fix_provides(provides)
    end.flatten

    if origin == FPM::Package::Deb
      changelog_path = staging_path("usr/share/doc/#{name}/changelog.Debian.gz")
      if File.exists?(changelog_path)
        logger.debug("Found a deb changelog file, using it.", :path => changelog_path)
        attributes[:deb_changelog] = build_path("deb_changelog")
        File.open(attributes[:deb_changelog], "w") do |deb_changelog|
          Zlib::GzipReader.open(changelog_path) do |gz|
            IO::copy_stream(gz, deb_changelog)
          end
        end
        File.unlink(changelog_path)
      end
    end
  end # def converted_from

  def debianize_op(op)
    # Operators in debian packaging are <<, <=, =, >= and >>
    # So any operator like < or > must be replaced
    {:< => "<<", :> => ">>"}[op.to_sym] or op
  end
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
        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]







|





|







492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
        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]
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
    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)







|





|







540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
    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)
502
503
504
505
506
507
508


509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555

556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
  def write_control_tarball
    # Use custom Debian control file when given ...
    write_control # write the control file
    write_shlibs # write optional shlibs file
    write_scripts # write the maintainer scripts
    write_conffiles # write the conffiles
    write_debconf # write the debconf files


    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)

      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








>
>




|






|






|



|















|


|



|

>













|







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
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
  def write_control_tarball
    # Use custom Debian control file when given ...
    write_control # write the control file
    write_shlibs # write optional shlibs file
    write_scripts # write the maintainer scripts
    write_conffiles # write the conffiles
    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

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
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
      rescue Errno::ENOENT => e
        raise FPM::InvalidPackageConfiguration,
          "Error trying to use '#{cfg_path}' as a config file in the package. Does it exist?"
      end
    end
    allconfigs.sort!.uniq!

    File.open(control_path("conffiles"), "w") do |out|

      # 'config_files' comes from FPM::Package and is usually set with
      # FPM::Command's --config-files flag
      allconfigs.each do |cf|
        # We need to put the leading / back. Stops lintian relative-conffile error.
        out.puts("/" + cf)
      end


    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]
      FileUtils.cp(attributes[:deb_config], control_path("config"))
      File.chmod(0755, control_path("config"))
    end

    if attributes[:deb_templates]
      FileUtils.cp(attributes[:deb_templates], control_path("templates"))
      File.chmod(0755, control_path("templates"))
    end
  end # def write_debconf

























  def write_md5sums
    md5_sums = {}

    Find.find(staging_path) do |path|
      if File.file?(path) && !File.symlink?(path)
        md5 = Digest::MD5.file(path).hexdigest
        md5_path = path.gsub("#{staging_path}/", "")
        md5_sums[md5_path] = md5
      end
    end

    if not md5_sums.empty?
      File.open(control_path("md5sums"), "w") do |out|
        md5_sums.each do |path, md5|
          out.puts "#{md5} #{path}"
        end
      end
      File.chmod(0644, control_path("md5sums"))
    end
  end # def write_md5sums

  def to_s(format=nil)







|
>
|
|
|
|
|
|
>
>





|
















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>















|







662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
      rescue Errno::ENOENT => e
        raise FPM::InvalidPackageConfiguration,
          "Error trying to use '#{cfg_path}' as a config file in the package. Does it exist?"
      end
    end
    allconfigs.sort!.uniq!

    with(control_path("conffiles")) do |conffiles|
      File.open(conffiles, "w") do |out|
        # 'config_files' comes from FPM::Package and is usually set with
        # FPM::Command's --config-files flag
        allconfigs.each do |cf|
          # We need to put the leading / back. Stops lintian relative-conffile error.
          out.puts("/" + cf)
        end
      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]
      FileUtils.cp(attributes[:deb_config], control_path("config"))
      File.chmod(0755, control_path("config"))
    end

    if attributes[:deb_templates]
      FileUtils.cp(attributes[:deb_templates], control_path("templates"))
      File.chmod(0755, control_path("templates"))
    end
  end # def write_debconf

  def write_meta_files
    files = attributes[:deb_meta_files]
    return unless files
    files.each do |fn|
      dest = control_path(File.basename(fn))
      FileUtils.cp(fn, dest)
      File.chmod(0644, dest)
    end
  end

  def write_triggers
    lines = [['interest', :deb_interest],
             ['activate', :deb_activate]].map { |label, attr|
      (attributes[attr] || []).map { |e| "#{label} #{e}\n" }
    }.flatten.join('')

    if lines.size > 0
      File.open(control_path("triggers"), 'a') do |f|
        f.write "\n" if f.size > 0
        f.write lines
      end
    end
  end

  def write_md5sums
    md5_sums = {}

    Find.find(staging_path) do |path|
      if File.file?(path) && !File.symlink?(path)
        md5 = Digest::MD5.file(path).hexdigest
        md5_path = path.gsub("#{staging_path}/", "")
        md5_sums[md5_path] = md5
      end
    end

    if not md5_sums.empty?
      File.open(control_path("md5sums"), "w") do |out|
        md5_sums.each do |path, md5|
          out.puts "#{md5}  #{path}"
        end
      end
      File.chmod(0644, control_path("md5sums"))
    end
  end # def write_md5sums

  def to_s(format=nil)

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

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134

135
136
137
138
139
140


141
142
143
144
145
146
147
148
149
150
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

    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 '#{source}', does it exist?"
        end
      end
    rescue Errno::ENOENT => e
      raise FPM::InvalidPackageConfiguration, 
        "Cannot chdir to '#{chdir}'. Does it exist?"
    end

    # Set some defaults. This is useful because other package types
    # 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 " \
        "directory (#{::Dir.tmpdir}). fpm uses the temporary directory " \
        "to stage files during packaging, so this setting would have " \
        "caused fpm to loop creating staging directories and copying " \
        "them into your package! Oops! If you are confused, maybe you could " \
        "check your TMPDIR or TEMPDIR environment variables?"
    end

    # For single file copies, permit file destinations

    if File.file?(source) && !File.directory?(destination) 
      if destination[-1,1] == "/"
        copy(source, File.join(destination, source))
      else
        copy(source, destination)
      end


    else
      # Copy all files from 'path' into staging_path
      Find.find(source) do |path|
        target = File.join(destination, path)
        copy(path, target)
      end
    end
  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)







|






|















|








|



|















|















>
|





>
>














|








|







|






|



|



|







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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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

    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?"
        end
      end
    rescue Errno::ENOENT => e
      raise FPM::InvalidPackageConfiguration, 
        "Cannot chdir to '#{chdir}'. Does it exist?"
    end

    # Set some defaults. This is useful because other package types
    # 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 " \
        "directory (#{::Dir.tmpdir}). fpm uses the temporary directory " \
        "to stage files during packaging, so this setting would have " \
        "caused fpm to loop creating staging directories and copying " \
        "them into your package! Oops! If you are confused, maybe you could " \
        "check your TMPDIR or TEMPDIR environment variables?"
    end

    # For single file copies, permit file destinations
    fileinfo = File.lstat(source)
    if fileinfo.file? && !File.directory?(destination) 
      if destination[-1,1] == "/"
        copy(source, File.join(destination, source))
      else
        copy(source, destination)
      end
    elsif fileinfo.symlink?
      copy(source, destination)
    else
      # Copy all files from 'path' into staging_path
      Find.find(source) do |path|
        target = File.join(destination, path)
        copy(path, target)
      end
    end
  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)

Added lib/fpm/package/exe.rb.





























































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
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

# api: fpm
# title: win32 exe/sfx target
# description: Builds a self-extracting ZIP posing as Windows installer
# type: package
# category: target
# version: 0.3
# state: beta
# depends: bin:zip
# config: <opt name=prefix description="Relocatable extraction path">
# pack: exe.rb
# license: zlib, MITL
# doc: https://github.com/jordansissel/fpm/issues/810
# author: mario#include-once:org
# 
# `fpm -t exe` outputs a self-extracting zip for Windows. It utilizes
# the »FreeExtractor« SFX by Andrew Fawcett (zlib license, binary below).
#
# http://freeextractor.sourceforge.net/FreeExtractor/ holds the original
# MakeSFX.exe builder. It's somewhat olden, but still very functional,
# installer-esque, smaller and more robust than e.g. 7zips sfx.
#
# Dist files will be packaged relocatable, with --prefix or --exe-dest
# serving as selectable target path at installation (--exe-autoextract
# can evade this).
#
# Following placeholders can be used in all path parameters:
#
#       $programfiles$    $favorites$       $windir$            
#       $targetdir$       $quicklaunch$     $sysdir$            
#       $desktop$         $startmenu$       $curdir$           
#       $temp$            $startup$         
#
# An --exe-shortcut='$desktop/program.lnk|$targetdir$/run.py' for instance
# creates a desktop icon. While --exe-exec='$targetdir$/setup.cmd' starts
# a post-installation script (shows up in a console box though).
#
# With a binary-safe editor, one can even edit the embedded ini-section
# in the generated .exe; It's advisable to apply `zip -A` afterwards however.
#

require "fpm/package"
require "fpm/util"
require "fileutils"


# Convert a ZIP to Windowns installer using FreeExtractor SFX
class FPM::Package::Exe < FPM::Package

  option "--dest", "DIRECTORY", "Default extraction path, may include \$temp\$ or \$programfiles\$ as placeholder."
  option "--icon", "ICO_FILE", "Icon (32x32 and 256 colors) to use for installer."
  option "--shortcut", "ICON.lnk|CMD", "Create a .lnk file, where the link name could use \$desktop\$ as placeholder."
  option "--exec", "CMD_FILE", "Start batch script after unpacking. Path could use \$targetdir\$ placeholder."
  option "--autoextract", :flag, "Immediately start unpacking, no path selection dialog."
  option "--delete", :flag, "Delete temporary files after --autoextract and --exec."


  # Create zip, combine with SFX
  def output(output_path)
    output_check(output_path)
    # Create zip
    tmpzip = ::Dir::Tmpname.create(['fpm_exe_FE_', '.zip']) { }
    ::Dir.chdir(staging_path) do
       files = Find.find(".").to_a
       safesystem("zip", tmpzip, "-9", *files)
    end

    # Prepare INI
    ini = [
       "[FE]",
       "Name='%s'"      % name,
       "ZipSize=%s"     % File::size(tmpzip),
       "Exec=%s"        % attributes[:exe_exec],
       "DefaultPath=%s" % (attributes[:exe_dest] || attributes[:prefix]),
       "Intro=%s"       % description[0..1023].dump,
       "URL=%s"         % url[0..127],
       "Author=%s"      % maintainer,
       "AutoExtract=%s" % to_i(attributes[:exe_autoextract]),
       "Delete=%s"      % to_i(attributes[:exe_delete]),
       "OpenFolder=0",
       "NoGUI=0",
       "Shortcut0=%s"   % attributes[:exe_shortcut],
       "Debug=0",
       # FreeExtractor uses neither of the following settings,
       # they're just preserved here for eventual re-extraction
       "[FPM]",
       "Version='%s'"   % version,
       "Epoch=%s"       % epoch,
       "Iteration=%s"   % iteration,
       "Architecture=%s"% architecture,
       "Category=%s"    % category,
       "License=%s"     % license,
       "Provides=%s"    % provides.to_s,
       "Depends=%s"     % dependencies.to_s,
    ]

    # Icon file setting now required displacing parts of the binary (offset x5AFD)
    if attributes[:exe_icon_given?]
      if [2216, 2238].include? size = File.size(fn = attributes[:exe_icon])
        icondat = File.read(fn, 2216, size==2216 ? 0 : 20, :binmod=>true).unpack("H*")[0]
        @@feHeader.sub!(/2800000020000000 4000000001000800 (\s*[[:xdigit:]]{2}){2200}/x, icondat)
      else
        logger.error("Windows .ico resources must be exactly 2216 or 2238 bytes")
      end
    end

    # Combine into installer
    open(output_path, "wb") do |sfx|
      sfx << [@@feHeader.gsub(/\s+/, "")].pack("H*")
      sfx << ini.join("\n") << "\n"
      sfx << "\x00\x03\x04\x1C"  # ␀␄␃␜ padding: NUL byte, End-of-Text, End-of-Transmission, File-Separator
      # Append ZIP file
      open(tmpzip, "rb") do |zip|
        IO::copy_stream(zip, sfx)
      end
      File.unlink(tmpzip)
    end

    # final zip tuning
    safesystem("zip", "-A", output_path)  # fix "junk" offsets in central directory
    File.chmod(0755, output_path)
  end # def output


  # ReImport package infos from [FE]+[FPM] ini section, unpack zip
  def input(path)
    # tuned to FE installer section
    if File.read(path, 48<<10, 20<<10) =~ /(\[FE\]\n.+?)\x00/m
      # extract key=value fields
      meta = Hash[$1.scan(/^(\w+)=["']?(.+?)['"]?$/).map{ |k,v| [k.downcase, v] }]
      self.name       = meta["name"]
      self.maintainer = meta["author"]
      self.version    = meta["version"]
      self.epoch      = meta["epoch"]
      self.iteration  = meta["iteration"]
      self.license    = meta["license"]
      self.category   = meta["category"]
      self.description = meta["intro"].gsub("\\n", "\n").gsub("\\\"", "\"")
      self.architecture = meta["architecture"]
      #self.dependencies = meta["depends"] .to_a ? JSON.parse ? eval ?
      #self.provides   = meta["provides"]
      attributes[:post_install]    = meta["exec"]
      attributes[:exe_dest]        = meta["defaultpath"]
      attributes[:exe_autoextract] = meta["autoextract"]
      attributes[:exe_delete]      = meta["delete"]
      attributes[:exe_shortcut]    = meta["shortcut0"]
      # keep other attributes?
      #@attrs.merge!(meta)
    end
    # extract via Zip
    zip = convert(FPM::Package::Zip)
    zip.input(path)
    #safesystem("unzip", "-d", build_path, path)
  end


  # FreeExtractor SFX header, http://freeextractor.sourceforge.net/FreeExtractor/
  #
  # This software is provided 'as-is', without any express or implied warranty.
  # In no event will the author(s) be held liable for any damages arising from
  # the use of this software. Permission is granted to anyone to use this
  # software for any purpose, including commercial applications, and to alter
  # it and redistribute it freely, subject to the following restrictions:
  #
  #  1. The origin of this software must not be misrepresented; you must not
  #     claim that you wrote the original software. If you use this software in a
  #     product, an acknowledgment in the product documentation would be
  #     appreciated but is not required.
  #
  #  2. Altered source versions must be plainly marked as such, and must not be
  #     misrepresented as being the original software.
  #
  #  3. This notice may not be removed or altered from any source distribution.
  #
  # Code Copyright (C) 2000-2001 Andrew Fawcett (andrewfawcett@users.sourceforge.net)
  #
  protected
  @@feHeader = %q{
    4d5a90000300000004000000ffff0000b800000000000000400000000000000000000000000000000000000000000000000000000000000000000000e00000000e1fba0e00b409cd21b8014ccd21546869732070726f6772616d2063616e6e6f742062652072756e20696e20444f53206d6f64652e0d0d0a2400000000000000272030dd63415e8e63415e8e63415e8e63415e8e60415e8e63415f8e02415e8e015e4d8e6e415e8e8b5e558e62415e8e8b5e548e6f415e8edb47588e62415e8e5269636863415e8e000000000000000000000000000000000000000000000000504500004c0103004dfab63a0000000000000000e0001f010b010600006000000010000000300100009201000040010000a001000000400000100000000200000400000000000000040000000000000000b001000010000000000000020000000000100000100000000010000010000000000000100000000000000000000000bcab01007401000000a00100bc0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000555058300000000000300100001000000000000000040000000000000000000000000000800000e0
    555058310000000000600000004001000054000000040000000000000000000000000000400000e02e727372630000000010000000a00100000e000000580000000000000000000000000000400000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0024496e666f3a20546869732066696c65206973207061636b6564207769746820746865205550582065786563757461626c65207061636b657220687474703a2f2f7570782e7473782e6f726720240a002449643a2055505820312e303720436f707972696768742028432920313939362d323030312074686520555058205465616d2e20416c6c205269676874732052657365727665642e20240a00555058210c09020ae8ba261082435eb39f770100ff510000001201002602008f
    ffffffff558bec8b4d0c56578b7d088bf781e6ffff0000c1ef1085c975086a0158e90200fdedb7ff0111837d10000f86e2170053bab0150539551073038bffeffeb604290283fa100f8c97198bc2c1e8048bd8f7dbc1e3042063dffe03d30fb61903f304590103fe0702830c32c8030405060c32c82007080932c820830a0b0cce20830c0d0e0f5bfbeddf0383c110480f8577caff85d2740b1786f0f6ffeeb741154a75f5bbf1dd8bc633d28bf3f7f68bc78bf209ffadfbeff7f3de8bfa0f87202f5b14c1e0100bc65f5e5deeeddb87c318550c41750433c00d8b450828bd0bee850827f7d0b9441082cbf6befdb6b012532d03621a8bf0836d1e23f133f36bff0b1705c1eb4a04b59871400033c342181d1762df9e17065a018b34162b0833468eb5b7f019c623c120160485ee6e54985bc6302d22f016ba602c124f293df927646cfb06741b313a23f7ff4dfe1b5ae11075e513fa0744240c5333db568b74db7ffbdf073bc35774058b4e3c892a068b7c241483f8040f836ecbfffff805750bff760cff7728ff57245959833e06571004e86dfb6f374b11230f8b4628891e89463402300ab7dbb7f638895e1c450420740e5300ffd0143c83351e7bfbc40c8947306c5bc35364576a40880b7ff7ad3e4c56208bf81c85ff744c68a0057b6af7de63b30815302485c06510736b267b7feb2a8b5c2418533721281794852dd8
    6c24232a1910dc56b8bbfdeb1d8327000385472cdc146a00e30ac76bb8dd38ad022b38108a0bdf4c257783ec2c901528144e348b4759b6ed1bee1f8945fcc32005081c6dfbff650c303bc8894df873052bc148eb050e2c07dd58286c16f01c13095f510778e9f6b6b3ffa7f01a40ba035a440c732369c1dd5873fc4884efcc770385bb7da1b08365836bfcd3e00952438350ffffff7808ebd877e0078bc8d1e883e10183e800894e1874634874172abdd96e0209483a5beb9fc16d30e9b6ff568ce316eb948d45e8575004e403e02cdb6db7dc50c71d3eed75e802e4e0ad7c61b7dc110b4812288946044277a9f1ca1f0640be55c7060c9b736e5300f4035e4f7a931301ed5263337c0711d8e8291039fb1773ee21aff9207322c42ab6db1736c1c1088924ebd929088d85b1ee4bf7d1255ce972c873b5b6e0dab8040a817d390451ee177ee3543a6a0258eb0a3518f7d81bc0f4b4b9b3d88906675fca2f09f0375cf8b977858c4e2c39677522718b56280b862ffc3bd07418895576082bc2486f792bc7b6fdb6ca77f02d7562f0107bf86fadb5ed6157560a6d393022120f6bdf0c0ddb3b9d350b8bc12b0638b7b5421b094cb2484bf008f66e8e4b397519ad257412275d33ddf9f9c849894dd08955952022edefb54609f1f84ff4760324b69b7bb7f472f0390a0f80f45303f8da46b7ed7c366316f4762935014fa1706bdf
    05f003d8293bd0dcfd1751481e32b2048c0ed80318ba85160b4c3f7bc8437db7b12d9b1f361d6065308bd081e2e02a98cddd0781faa0051351b88ad4f05f94e01f6a048d840802cb509966e4b37114010c1d040eb7e70a6f03660800f110d6380a831c32f6c2c004a008735b9a033d63e170904eec3f0cb6fdf168fc0c8d9875ee5a6289048a85cb5bbb1d04ff4f5b08c1e9c10431df6b73ff72a5837e08137316121b0cac3024b65ffadd810026ebe4578d4e14a9248d461051b676b06d30378b00074a1879be14b2b5e5ce5cbe85d702212cb505d96ebb6f80d0f0c1eaf6e21ff4022a7cdbd2970f54011f10983c20cf4979750213e20955cdaddc928b10a1cf1423542d38286cac8d10effbecaed16eb8324801e47319d3d43cb6dbe06ea729fbfe1481b4c906ca276df0b2711275ce07038d4264c9c3d116de0e0f95c149a808f31e5db003d65b3c8d140802ec01885b1afbaed2650c8b2348013d36d26dafc81142037a0804f04a1b0d56ba5ed118c2024f3701c90138ecc012c07e8bb0c101b5ba04334488fceb02636b30947da7954171f0a6990bb3565abc744dd8ade165c3560d1445ec095851149fa16dbad403f0ec51818c71e9756edd8e057c0501534127f006968f87fb711c751ffb69242c4b976b96b9f47f278ad8d47e0cb22cf0ec148597014c8e59b1108241b82e5b2bd00ef2de051cfc89a45fe0066fc3
    2b079a1f56014708d26e051cdf3e7cc75ed885c402c9c0f0e73c6aa13070020359a259b7020d0313a6a411a6b96eb473064e851800029a9bc0b347372604868ff6d2ccf5fdfb8c0ffdeb0f13920f606e1ca0891f6afd81bdb25d440add27eb1013b0e75ae983676e39ee3c3a838624b1463ffdd6004b96ac45109982afc3615b8057485625f64af6fb0c6451e7487506c09db204e69cc3c16afc02660b802756077189c307566b394e74cf2c5bc3410b67494d4e2ccdcbb24d4fcb2b0f4f4dd29dedad2b50eb4e9e0875012d92ef45eb2324fea537e1828683c9c9c3741347b35c36db391403a18b151816ce5136cb659518511a993919c58b2e667cd60c112c6271880e7a36461fa356ae245d3245186a1c8db3401778706350dabcfddb42548af38320008848108af68848118bcb76a3d948140514185d5a3146ea39180c8306b2626b746cc457aaf8c3d95f52a1c7761f081cc318d2b516a78b5dc2ae079f5d9436638b0bd75d02058dff9f2e9d96203dffe99100f8f80ae11a236c09874d0c0c4d78e70ecd784a186268c8572b0edd7a6b9767014e156fff7318834311b2eea9bb0614500710c8118410927bb4429e83c4184796a16c7db77415e649f7d91bc9f50290b6edb7630b220bbc5889433b43fc43dc6d7b37c10301990e39477328ab05da8306095ac46e091a0fb69308f8310060f71db22cd0149708d04b4033
    d04a6d15936c059a7b8b90034001298f0d1d851badb60c0c08c16fe8756666c29dad4052036f7e845aa3fc2d99e8f6c11074147845b66dbae6211b2402ebdc1840846c6ed9dce80a0820de141907420ec8f6c2a708b202d03ab325440399016ade48836b32a70811189842809c10025702097501f9e4178504dfed8152ef0ce7e74a734b84c262ae8348dbc88719e71c3214f2ca01050ce12ed08d8b5ec2678bcaa3f7b82db41f28433b10730e290ca16d38362303c2ff7b969be79e709b6cb83bd2860722b463030d5891751e628b4fcacb583886ccd16b45f09d1caea6d25e6ebba13e93fb786ba21030793de862c7363099442c983bd14bbacd6d80751f5142e854121fb812910ab38b910f77fb6733f7ec8a09880a42f1ec480f3757a03d4349444149ecff8bf1b2a14bc148ff8323005cbf22e40ae4825a1cb9083f74538a4bb3b9dcc1b05c3ff90f4405049a41e1b27766eb40b00a9635c0dae5fd1e1dcca5ecec3f10ec25c128c6426fa43c080776e16d2060642edaff26cfe55b831ffc1157300e0c5003774d2fcbb22c4f4d4e4dd92b1e205b83f3890e015e0ea2086c0f16c9010e24fe93c16c91830ed2b2596e971b811c03291d84111ed32c97cd5f501f5620a80544c0015bffab04fbd89a01810662b71c18b5b3fc05248b4f348b5f201c76039e051c201efcc80f4d324d06471533df22593b13b10c5dfa1473
    1770a452848b3030680bd81b3099da3f83c20ad55123c3a9b5994ceeda5d18e0f36dbfcdc6a652d3eb0b2bd121181b999136f47522dd9d388d7a58a423cb03e339ebc4dc36b0f616091e0e083ec3beb5d64945861b5d18731d899cdb2d737f8c1c72e61eb8c9bd5bd98f14191cc1937ff667f3f66c1886f6c3832bf6c3df00f63ad7ce9d105c652ec8882ef8ad6660a22d4283e30f3bd3bf6daf357d4f6f1a3172d3e60975186d8bd864fa3440282313805386dd2bd3360370d50201bec35544542ed83bde7217c877b4f67fc68a1888198a5801414006037f02eb1fb5ffe10b7403c68bf07dc6397508760f2904de66eeed251e4e75f73d0b780875f54ed8d01acb8bd9b9eb16518a4d6d0f053654f489f42288e3a63ef101817df05c722eb60a7228ea9aabb5b611888934d7e4ba678d4b8e269173026e48c761ae766da3fd19eb4bf51fe7e0466997d2eb346219c2256b6674171e1b3578bc947d16eb15164cd3ad51f8e665f1c1c1e3030cd068c314e3f455fc03ca37e4fbe7df11c3f0473420e0b40433d23bc2d96ef1db7431a51c3bca742a065014020856e2ff426f710c52f7de1bf65083e69f1e45b060e3401cdc14f71cbad4a2cfce4a58c306aacbd44b4bf674303f5429bfd86d5f27240074232b140c0956230b1407a1854a0d061c09661c8a1183b5000e3c5e2d06771b5f7133ff3bc7a8d2d58a003a2042a6a9
    c290cbaf005d68df99ed1c380ab966103bf77507360bbdd46f54153e397e208991750ac7da3c736bdba63a1b7e2811241d0e24566d08716b6a180a5b53dc5cb7df6843fb7504a3eb748b4c24dc78142d4edd76ac3bcf070c7df68476df6ec08958bd087ce7f90f7f4b11d3e35db0b9857a1007c60c6b530b5f53ebf7d025b3f356fd96931cb6dbd7041c4145394b750e585fbadf5bfd19141b40c7eb120a13dce3eb47cc88efacebe56afa58211e6b4db8b7163bf057ebc3393e08db43b767c90607c1028d0ca4fbb7abd5a55b3c0c744ea90d5f83e0c885da08a9bea2024528c0d26d39700fc930014935d178684fd90e4ccf096c37b5b3b40405bcff0680d174f7b1d12df03811c7a50531b11a3dbeb56dc1e91dc152de77e4c700477c473e708acc720c4851468ddaedb406ede76a1f56f7e8df3a1a1e37413ae00803c35ff7f7d98dd61634179b723f89395dbd65e9619679d3202e4acf6883bd1dc701071676f9390321735b35c08bd88ffb6311399cd0dccf1e8360043b85db7503c71c6b99620bace498237fb205f786715156772e3eb7d9866d1b620bbd0c75c80888b9e72ea90ac5bdc88259d8461a00bb18b00817b0c243b62d09747f3af10efb33433e70c1e1100148290a4b0e9049550886ad64920b2b2635d8d61a7f3b0aa3dcb9bad60eaf6b8a0618d730e1444c32944de6c203bc944c32940492c25e973005
    4464492d63eb58f66a02c574302f0653394c03e10ec7d3eb5c9ae05ab30e9e86ebedcb6ee35b38ebe99125b7e00370279acb65d334c4ee20284426a9354dd734d701172b3d413a5da0c90335510bfc003a3a624c5ed3046a13452f05dbe2a5977958eb4415fc535040140225d7ed620c466a000113296b5e7ab915c918e683f8fb4e18740803cdb5099f3800250c2a682dc1ac202a1891d638dc1d096c81ecf004fb7065db61a0c7f8558c02909665599694989ca0a4a859966559acb0b4b8bc0d0a9465c0c4d8311bb8df8801b7ff44858c8d0341f054b6a5e3397d8cec45100420de5c4fa0ff757d20a2b9908b37292ef10db6897520391009414c465c7a578176f33bf17dfc7308df78fbb7230f8d75c8583916cb4883ee3bc275f4390557147244d73905fd1bd46a36a8503773168d748d8cbb37c40b120f88306983c604d11772eeb6dde006d4c1e6197c358a4c03179a6deb2bdf57d0210e030e95505fe0970c00be33c94874137d034c3d90e6f8dde883c704068c3d1775ef8d15ec1fbffb8b0383c37374198b8c854c10552c8d8409ec17de7e893c8a418921d2473b2372d88b84351e063051605d6ba0f9b58fc5c2452c0af4c9fcf7db3bae52fb6caf6e20051055e40203edb0f4eb8ffe257ddcdfff28ebaadc72b7d42fe08b02dfc848d15aedfdb184c522e774f4a289112ef58ee29920f40303ed685bb0d920cc
    df2bc353fc5db84a08ae8a55ec6a012bcb58d57ee1b642b7762d20e083caff2b14c5dba26d453b1faf410568aa7bb805560421e0210b0deab76ddbd49f831c6b4d283b5a7ed3e21670d1779e8d341081fe4479712eb5c0292c7a045f55f837e8ce5ee48d94c6023089f545f813b6b6353469f4cbd7b4274689176eb2884d15cb88458510db16defdc6d3e88b4a99d82bf9c1682b4214b1cd9163637c21eb8dd033161cf57507ccdf8f5f528a7983bb014b0c2ac33c468d0c888b6d832f500506c645d850379eddfff70b4d1073143d0094f81ac980e1a080c1606debdf32d45a44189e24028a0c084a5de86d1350817e8b3ce22bf47cd83f041a45f45e389836b7d05a9145081e7d5fc10b6f6ce199fe89a1d6c1e2457950aa5d8b042a1b72ea02ab2e0cad913049dd85455eb45b74063361ebf6055ff6d982d11107518b4e23fea10bb0d43bac0bfd83e810d5e6e84607ebe68d0dec587580fbeb3bfe280be00bc5dd82e345d40fcc12153955d0ccf6026cd9fc1a7de80109e29ad8550dfd52194daaf65d3a199efb5164fffa6820d2e110c2f2358976fe085753294aaa500257bcd0581b50829a7524be6f836e7d2c0f1c6860764468e432ad6e585b24530e394d0a6eae449b14744a793b3f04bbb9e6cbe638331820685477dc388601eb6a485250360f61eba5adc5390149d37735f6eb1158898dba41c80aebd2fcb0cab9
    d24646586f4eb14f420bc103fa59618b0d08b26c0617d5ec0b080cd3b90d1ab1100b091019f52e55a04cc0c3f74995376a63625e4c0c3bcbbf6c8d1240ea0f2c102bd93bd80b71818d7683d805740a52fb1af5066894cb015f142ba878eb8e2ebb3896135351e23cffd0d16e48e550381275fc5b2b0a7c6c01feda05fc85da0d7218f3755f6b44af1425dc463463876776e2bd056066606c084dce997c052436101ad15030bceb7f68c10b41856fa04dc508ff15c8700785af35d8f6740d791555011665a2d66a2bab6b2542f7262bc5375630651e6b4be6deb66d591e597521151411c21fc41a0f17e903ce89ac1d6237d1064321005e4365c8ef50a13068d8447d083968c1dc0c1312000c46aeccf03bf9fe17101ff2c8614b72081803cf7327455bf0cc10fd1289bfd0bd538b1dc0e95650f800578d3c01605b67aa6d6a07be02bf89cf89516a63ab7d53d35aff4d79a9e0a06e0c1004bf0404422b51636828262b285468d8ccec6f89bb07203df1ebd637201504584b868f9a08e04543cc6731010507e51c146a2b4df73a8127962fbcaff02b75dec63bc777f834d12c9cb95676705492f057813724c1fd90e73d8d47fd9cb841db1d7e27be803cf3dd807cebcdbbb7404b75e906020575e2030675db4fae6f875f95f875f28e100f827302cb6c936eb680005150bc486a9849a25f758cf3b7f1cd5d5257bb2489536a03
    560d680aae0bcc588080e045f83db4c7da049e1f4903105656a479e1ee6c2fc01a020bf088f4505727db4be7d59483ceff12fc91b9533220f88578a3b71cd9952808128b3b2bd36353b889007506a9fd23bb926d995e7b9498126d21806c687c4f94580adfc0f0988d1401b297730785f6d2ca740be052570be0b4c2a5dae84e3f29f0c02bd1e578196383d398065389bd74d28c859155800f9012ad3676fffd6a20598db5168bfb53f3a56a250bafc31a9800116eaf3b9afe637b017f837e7cbc07ba29c159ff3660c220cc6aa956a79beb7819abc1e704330b4c55a3835dc150043140a5022330ab14b03c2042cd88d0c81c14863d34180dda5a2245fffb6011381ae072eff7d02a7c6bb3429a25074814530346dbb66d84578984d0cb23c3a58993e09ab63debd3f41747756636f4edea817d104b010274c9fc9920d9dc12cea0523613a490016400a8acc864cb06b47013b0800c2003b8bc03d80036c063c413c832800c20ccd02503d800d48bf0435ba3328b9205d084aad047e885a853143bcf7430a2e4165e06730b80db0020685b6f6fb7247d1832761d830600761710f8c5ce3a3c519d0d754b5b0377ba432bd7004c3f081c77a3061a4c44c41520723f612b6d4020281ac63852d417b4280cd148da5aa104bc3cda0260ec11e98ed0761850923f60a9edaf852163c703d03203605b08b65f6a75755924e4531bb6
    52bb458c5d28522db53895906e0370c86e15232f8ffd1403094569c8761c28761753b5c2c1b25767d17ea90db38c591b0c9514755097a974a059388cf6056352e9b3a5f0fc5b403fc07b238834424946245700c0c55b6389c0048d46788a30e3c18dbf7e100ef80ac12d68b6db94c197ab2f07d63e3d04eb10397e9628dd2f405c10403b46044f9c19fb2d96744ec65648034e4c551010f64b2dc4112e014e1459a56215a12db597dbf2d85272fb5e7c395ff7047c5e07264be2570cd46faf3d040126b147e32d99eb4e05eb2f4364536a6c5347662bd81b78f37436156e40349503661894a648e0560a1c589daa895e76b0c6da0a123653356a98cadbb6e1db7516448b4f387d5f3424580d54b641bb2b08648b0fb51f0e0cc42f05df0c3c68e275296a3868b6ed4233f6e67cf1501128022c1a22b25b30a11459da442cdbf60bae47407e5c054460e0121db478125e97440835016ead3218777c5eac06348c6b0cbf53e47200c3061c893ba745a62260dceaae0c6d78502712b2f459410b284e32f8e0b2f585dccfffeb0c70f80304730b597ae75f7c0c2113b190ad640827496a04064a531e5d21db1a1e700978087b08559a4b06b0d541d51511501b1b864531383af64086b2b761753e2a40056c42ae44fcc26c6b0633022d0c082a48b9434bc9760103fdf4e39bbdeaaa43fc98ce018bd4106ead25072c8bc7a1350807
    f1dd082b276ec4302debf8eeb518707ceed3c13b78f5395e04209c29bc443e60ab52012323110ca0743339f27062603bd6402db62578182a18f36b340acd7808561c1f51d4baf4787655bfb121730acdcd0691aa0fb366684031560c8453f9778d625bb2cb2a80760418b976a3fd1110017e40297e5c66f17e0c60e9da56f064754c828b0a5a022e4b2f45cffb7611b408148a04eef68de6880c02401872ef4a65db366c98541301153c60020cad53cbf218017e14081c10706100ba6f5154eb428bc75e285dd47ef5fa026c15435c232b012e20b7fb4475083258ecba28a675013974167e6813bfc21e3efc870305395d868b8259720e9e2c580b87d0c2c8a70364516e6c2d1317b62416c96ae989bf0ff4b3164a1263f1c156394e60d5c642335b52d0588fc79db63ad7a0974cf7534c3dcb29d84e1b36bbda3453641b546a6dd7442f7c960ce52d5674b150f6135351563a560bb4441ddd7ccd5f5b726f2f36ed5937d4040faf10c3e89031b550c30d013fab40d530c3a1e4e9fb364637543574716c036ad6a104eb0b66b1eb2ed81e5e0c0750a5c5e86c05e938d840da85eb1ee63cd0101390df9b55eb3a2fd62c1d625e6f5436d75c201280091187de886c52d4a085fcfbcccce5baf6086840a2105a787e711776eee4ed6ab62ca2406418ed2a64ddced00e040fb7104a9b4e44353837777ba53935dca09a6071ccd8ee
    8f84466a2c79a3e4177c34f689b3963cf96a3c223110a9b163de80717a2815b8b91a1ac1fdd4a905c48a998816941c08ab37b33db31ecc3a09245734a83236b27dc10cca20240b1cd9de333218183526171042b0661b280814c988acdfa0d1d5c4480e2d177dc8f3ab11a123073a6c587fec195c136f756d539daf4163d99620d6a9811f72a64dfed49078ab8dd4bcbe01f60a2194b15410d184b126a102f31cc26cd339d7283053127a5c5e5f9de0231b1c445685b88a063c2237f88d5634039c8a460146840bf480d8defeb73e100d46eb0a3c207e06460c207ffa238434ddd639c50fe9d4e874bc7c976f71cbcae801730fb745ec6a0a87d95e89583cc61ac4bba36898f1378e5045d453135c225a45e7074301fd0c5363ad33a6ae3bdf7e1768bd001df71033f18a11306e71e9172288140e4741f97cf17b070068ba499862d9b2dc2bc0ad6d25d89500aff27e04d84e08be25ebf38bc6fb105f808215822d7666cc2b97bac01b743bf87f55507d021585ff5f62bb2d0cd203f7c40c4a8a0e84c9ed9bdfda742a3476ce2bc88a10120fbe1c01037e17dee0d22bdaaf40803c7775e84e06ffe06b0b05120dd029755b80ce545b7307f2ae50c0c0dd53e773b80488488d0341eba5e96e2bdf9d51fb2ce559a15440cbc57f2032c05338023d1336d4b75b7eeb35e43ad0742d2c8bd66bbbfdad6f8a193ad87411773c0a03db
    96e5edbf55f34138040a75e93801798a5601060bc6b6d3dfeb6c5bbc350b1a1ef0da3fda10b93046ab58079897808193d5184e3f202dda47c1c7b24138ff2c1ad62e7d2928cbf3ce03c13b037d190c30112c7f36f2b88201260411296b81a1db7d665036c34b5511bdfbc225c02c900f4716104875f266f6c80087617ec72b954fc1c3811b0204cc8afff68d8d0a45424ef9617c08047a7f0380c1e03cd1f0d6680b7c0a0204e08a0ca151dbdf62133ac87c0a7f0df2f617d45436ca4064c90113b6683c7a7c2167f6d743e37715603bd87f2e85db3a71418d6f0b7656e84441827d2fd0b9f6bd4bebe5164617edbffe0a097f07b8b9eb0d2bc6504e47dfc17c17bd565b089a8559b7702e803fee296af765ebefc65faa383a76128a08300e2e300807134bed1b703bc677eedf49c36b4db7083498fa32bb04a6bfbbdb5c7f5f53be78d47855612e2a53080d60ba0d6a2055fef2c9c642341317766c5d0cd4cc01b47cda18d00250885077d3c07607de4bce83e001f6c3402f81780f4fe8a3188d5950717c6503b12d5672dfb1d8235a186d903e1b5728c30c89648a642f15493740f98b20c382681900c9525dd76e805068c580bb041dbdd6d030aec702dad3f4e380db0cdd0df40cb24b9691adc649fa00fc08741e1e4c2e0ba04b5516d8ef4088ebfeb08d2df605dcbdfd080866abaa8d1379b367dbb9cc72923075d84059
    1873b35863db50b7100b19f9824d06c416cebc841b35987740268a15845e6e065e797973e1fc8895e0be63b1e605107b89e9fe15e852254ac18f04e78698d4907905012daa0b110cd1d47b5c5eba47400936bebf975911593922d050a3264857f46e1846e7febcbe0ca4f00e53728c60dff0134653741e68081b0a6b77135e08b8251cebddcc9e4d2823c59d50e5bab131033b1857e6235050ef9d43160de8e8ea1dac5ce8d12edd1952e5021a10e1811cfca375448fe72407f98ddcfbddfbdcfbb99821c8f0a3862592c17614735af52635215ac873bb90e4243fccf7dc7260490eccf73ad001792e171bd4f9c84ec9607f1159ebc331bcc4f520cf0179b0c4f591e4b95cc8a41ed0f89c196cf70511599832d93490d8b18d0b0a0eeaf432da556a2e17328419c8f65338b0d8d9294c0d5074839ce4427f7be4fde52dd95e92fd13a06cbd0e269d2550c759121123d88cd355406f351b927db3effe610e1091481eed4206f99199306a52245b23cdd8fa1cd609085a7020cff00232eb74bb6637ecf050330cec5027e0215b67cb11ec0b087821bcc9082a12d4a2eb5af54842c0f427cf442f678f841cc0f43dea57a9d7a864099e219dd78cb5a0f45016dc482b198c31acea748b7e2307f2ac17180241e919eb349ffde8fd4116f10ce51e19f08b1d5c591e9d1c3dd71c9028e8d22952a903c9bcf41b61ced4d91d56bd7a0c
    493034a4b3eac66b6ff6ca74705c1047131156c23c0435cce94533377bb3124f0977089730d9627d16d3fa11285766b2505754dd8961f5d41276caa185dd06c7a20ca9c24a2bf989120a7e09f7568d720115ca0288014142125b3c588b5ece5d55694f7ca13d1883641a33072a47018a407df8ea069a5c75bc10ebbe10cf573078cd7be8bdbbce32ec0e0a59175564e7572be11eb0e5e5837c51e55f3bc0c16c531dabac5d5b07fda042269b56689cd006cb209aae2876db00629b8ffb0d1a5600fad95aa3dd3d1368f83bfe3721d335a40cb41c1f2dc0d3d1045264c6ab35ffffe6e01bf825d6ebdc6681384d5a75d58b703c68f8f6271b775e03f056dc60c0813e5045123403ede275b86aebb58db9ef5e2d0bb1eba374ac580ac704fed768d824e80b48c04b26393d64de26e1eb0fb37454be2c568e18ebacfefc0a355c0d6834a4d89da032cbee6f6e37427e593d51df5c60e7ba17506a0557fa2c25bafc70e0ebf01305eb42be24640ecc866bee9ff456eb05681412fc362b08f0c56da44d4a03661d67015af7c705d4050dc8275493286c012ec2731a96828f04da5683e80f1f4b2214a6ea0d2dc766f59bdb8acaaf361b2774222dc9d2db61135e85ad8b010268a1dcb7b0c31d385253396557700aa82c757668f20318d663146d9bb5f63f2d72a319aa0dd005fb42c647683227242119fadad8a1e4c0c056b2ac68c8
    650bbfd0eb259103265645472f0274e157e46a572e9b2b48873a4376dca1ba6f80742f2df13c74a21478ee5bde3c6a01567c85740455c65aec70560d2007123c7c2ecce6c25ce47a0bd65aa02306692ad588a42e033ec58e7158a4b70c68f3cdce1450d64644eba450e6733580c50f48dc5ed3b0ef10cb34b8b8925050cfe6a8ae11388f8d307e03ce0053add34c01f6f631a05368682000cc1c2a6a2b30c6d2d0df53066a126a66c02ccac1c2992f625c702e9c60a009e73be983c25fdc54240cb811c7563bc8ebdbe682e187341edf9901e90f08c39aee6c0dcf4906a6079e8193a0371a183b08852303f6049b1d87a309f746ad50ade7e1a69b85aa20a154a13444de10dcee3485580c68f7383e1e0107e4f91868f96a657d4003861a9ff5f218877d875718c6050d10bc8635db775bd46a46ed501dd800de3f87ebe0833d67d76e27cc5ee441281e81d8d1beef2c53a80c4d3c7f7ea9e0be2ec069c0262d51c19959f7f950c1901396785394570447422842957397977fae85331308ffd6c1e8108d9d83b9a5049c994b5fb8010e3b59885db6b8539a5c761c0666aabcc93b14f02cbd71f2592bc1995be6f4f9fe8cb248e803743617645498d343f8f99a7c01686348976881a384366cb0b75454e42314cd6c1d75126b5844ee15fb760e890d143905b750b31a7fb99c1669368b59a85ac1d6ac67317409c9ba17424458
    8a7a57ead073f0ecc03a0871f45a327552056cd7a2213f5068f62afa81437c3050896f1d6a243f9df554f1a0c8f5645cf806747eaa65b110b7523956e0591c0081cfc703752338ff05c6c678637f3505c15954ff2de3f7c7efebe76a3468b87998a4d4622616827e0788b844c08dc0c7328bc12d382184cdf0b30e4715746f8e693b176a710da5188772047239c7c8c841ae1c1432e60ecc61a56f611435982de11e92095ff0182cb84cbdd6e5ec4c68b10767e95530b08274e04b02800d2677816581856c7a5dd8581dc8c8b2ff5757572c566e2d218029e2a5fc2d2d3904565f530623c7c0e03758045338e4c8490ee2028b02ed016c3e9b5b1a4201c800e100746c04086e332d7194812a53c921e4e4e9e4e941181c76b23d5250514a2ca5dc7f196f44873bf0752abe54af438f569ba45d1a716f4d24c412063157821a850dac7e7187bd6583d7556875740f0d796e878e305588df288260b620b33024f14a2ee11dc44ff114848c80fdeb5168f12716841dc649911616f2296325e1b66cb7e0bf169ff8f5004277ac3dec732b3da053c9d7419c90b1eb595d9685f66c90f0d8d87ddc17fbd55b37e028a53ee40c0663fe67a16d410d17ecbff4c774268d8da8fbf5459aad1e51f80d50109bc184814428962783077db8050e415f39b36fe1084c75294724107b92cd4618e8893d38b4b37fa3628ef80881d6a38cdfb64b
    c8433e0c5c85d700e4e9b3400c5209fa39390083c9106c676b4086641414687ce4ec35f768a3803919643818043ba7a3006273c6d9421ee4e90f50688651a41aca1ec4c09401edb610b423c0437d566f33db15e158b809fa4abe35742ef52cdec698dfa726205c1f95ab4699747c228cde55432718060b68a4a5f9550b193c68ef0bb9b488074b473d0e8972ddaa14e77668016816b94bbbf1395d2c86d4d367c956eeab6b5300fc9859be862dcb9c28f8681e20d49d251968295006fd36de4236bb54a51440fa4ef56c48564e44305c2a5623d94d300a9efda67af916a23b45f0597318332549165c97cd328053a74b04e95a039e36ec0e2f3248b363aeb10f8206003529c107aec192ef21cb592ff0cde21dea3e0bc06c68eba768c723c5616079b3637041468a93035364980b0e289499b6d239aa411884c353be3541563d83018490088bff7b86f790ff8a8d38db4108f0edffe4fc3acb741680f92f7405045c75068d2e62db0090fc8a05409ab7ddddecfc3818246a0c68d45e753e1090fc2c8245388c0c7190932d5572501ccc70db917031776a46a83d9409ee5992fdbf25a339fd78afd44b268907ed162c82cdb23cdf4a8008234dccbfe06a329dedfe780253bf06405321575f62d644058f0a7acd987ad905f974644c2f811b0409dc57a7532409d026903320f47e0bd90b5aec012b789dd6f7269bb9274779536e
    468481e12001818a7fd033472df32853df3bf37d15e216b00df50e7e168f8ec1d8a3f6c87ce460ebb52ccbe6089f13b0b27816647bcf0f030d74789ce099b13070084c3b919b058a4bc97a62148ac7b36f7c001bc0ab485655f581af72572e74142d10ca147020c2d5ca9cc113ea0478852b10c959e902161d5feb913c6c0a9909b7bfe8138016915656b34962b5dbbd1156661c8768fb6676bbb509d55dea6a3b0368fe177198d902b47c1d0c0f4b082d1e535c181b6a38f142962cf953bd5505dfb2937955b8ebd114360bb6d85319ed1d2c2513c9edfbf4d999865c2276a5068200384038f085dbbe5a536031d948545df80238c0ae2c7bfa1d8d2cd4dd61d432688252475324e8a596c59f4b2168a0e3d8b6e2b5e0403bae15de575b4aaf3e961dc504eb385fc01b9f8022f11b2060ace4f622528683d73ccc2f5143600bf911edf3ea32914c641decf6894784011b4298f146185d94f7791dad64b2915f17fe0b5918cc006ad5f3fbd056099310251a0e8c4cf08c68dc37d7b02d8c16b4df71292df9ad41ead731d5a1077e4868eca0031da06a9e7143a8ee49d32d68e81a560275fb6d11640835e8077f83c07d90aa2de96a0d7850eb01560396181888fa1d73571c98fb1268dc42afd72539254728e168079dfd463d13b29cfda1a071b3e78c0a0d999805bf4741b0f81b513399f8c3dec53655d0140f7dd1abab0a15
    98b5b6e117e0f40fbc0ac65efc6cbe204e8f0b50b32b1864608cd548709911fa097968c0e5f14c8d0e3d0bfe98bf350c4b8539d2a3e012947090530062e0262b9310e30b68e5c088d85495701134ad5355a33029380dcad847befd43762fb4a6807de05b751205e146750c1da52e5fe2457506e35d4a366a85ef62b0fd37d695817dfc20a14a01fa80c17ea947eb9c8b7d15c8c8149869504b8513f2c803047423478159e040b6522266ac9f6786e8f774682b7d362ba3a095a2474f23338b18c12d204702ce83cec21d055c53515708d0936842c0917a92cd7d9845f3efbeb0a53ce1842f661e059063026689c9879921694050724592ceb0a3a8a4dd9837eb0a1a15af35721e7462d989550b277d7a4bb6418c726d6890a61fd7a36490c94e1e1488780cc8410e8064de783172be836cde5988bbb3dd48ee9b1fa8682468602907b6d36139bb0c1eae591d8890b0581a1884799e17d2194c0004df44be6d57092a6a061f3c68300b9b64c819a4244dd8f68017c4db4d201968f28c5cffb4186c21f58f11d0680ca69a622fbad8e985929c6ed0d17a9350b3d713411810d0a8209f94e8e91277ebbd409ba7803dcf3e1db8076a83dfe88038960d8078016e8dc6ae10b4df9fc640010a4012cfdf3a21d552cea6eac705111c5ef477d82c1ef18c83393dccf51883d8c68e74d059d7a6f4a5ca5643bdce8e5231444fad665ff4
    bd08b5bb105323b920db29560a5315670206b285a48f682604413a885c4e968c43338087290cca054ec084670a3002752480a1ad5cbebabf427daf89ab7c5894590a140e0209815cfc30a77c0b2c8458fc1c11f95de1c580bd08ad16551b301b02a6557b8b5509e2891d9459b1bf5006700bbfb21cffd368e474b5241e236bd7f4db01cc923dac86bee85dc4e2980e6050315e241f3623562e181b8d8424dd81f1040ad43224151c8d9acc010f9680b485173a073c4418e92f7503f686546f845cf2f2112a99d962ad2f0e8b9c19ed610732bc84a56759d6c0972a08423a182bc851c536313ba9e4db50ecb6e015019b4be242e6133cfcb281c4a3c360640304b1028fbbb5fb5418682326cb0986dc8e418319d5a6bc9dfc2ee0857c9cfce5fdca60ee03c9e4fdaabf9ca746643a072ce060593b9cfc16398019461ce4fd25f696cd08da331f16cb1c2b5b2dd922c96ed87b0b8f3e12301a563ac08488ba9f12e4640558ea32e4fd6cb6920b0c2b6cb10963a75765df67676c292270e00f879c4c268186041826399497911392f044507610951e9e80a4a7bfebc78413930cd10c4494cdae2404134eac189fe07b30210c750d5ed3d9e00f142d235068a7fa66e5a4027cee2392e792c9229410cbb99221b990a88c21b9922152889221b9922f840c4cc921b980e9007c905cc990c678c9905cc9a3748086ec845c7074611e6c
    4286e4424268f04286ec237f6485f6a148f178364226e9912bcd3e6a0a563687b119cfa0656a5656b61fbee229783701b99869ec7bcf46ab2bd5a056007ca3930d0e56ffa82617c490ea80c25561d5b36d54ec75a405c2724873c663d53ffdfd64f3146f5222e848a7bea5e2251496401656059a1da11aeb16325d9c80ef1493e4a0744487a055ab65bdd02f2e25850de27413abfc515065109dbbf338603f57e8139080d38b72431bdb88ace26851b7bcd4193924fcfba0221aa825d75ec93a030de6418410911a88ace20761fae0774f113fea7b68d077328ca07c705b08babfc48d55f85268f0158bbf1114c18a85a05263fd862863140f7608ecb063cd4d228629fd0b0c6f2976232436128b3c9539b6cd8d6d675215181125082a0851996d6608799004d02a88cbabf4dea4d8ecc3568e7068c40d6a46345c025ea39a545d340caaf37fb18be20831218e3ceb0689a81bc46307ff4004be30587518d4315f8b009b04db82c50773066380882dc3dc09f1890eee4e3533a02ac06e41c30000ffffff7f963007772c610eeeba51099919c46d078ff46a7035a563e9a395649e3288ffffffffdb0ea4b8dc791ee9d5e088d9d2972b4cb609bd7cb17e072db8e7911dbf906410ffffffffb71df220b06a4871b9f3de41be847dd4da1aebe4dd6d51b5d4f4c785d3835698ffffffff6c13c0a86b647af962fdecc9658a4f5c01
    14d96c0663633d0ffaf50d088dc820a5feffff6e3b5e10694ce44160d5727167a2d1e4033c47d4044bfd69ffffffffd26bb50aa5faa8b5356c98b242d6c9bbdb40f9bcace36cd832755cdf45cf0dd6ffffffffdc593dd1abac30d9263a00de518051d7c81661d0bfb5f4b42123c4b3569995baffffffffcf0fa5bdb89eb802280888055fb2d90cc624e90bb1877c6f2f114c6858ab1d61ffffffffc13d2d66b69041dc760671db01bc20d2982a10d5ef8985b1711fb5b606a5e4bfffffffff9f33d4b8e8a2c9077834f9000f8ea8099618980ee1bb0d6a7f2d3d6d08976c64ffffffff91015c63e6f4516b6b62616c1cd83065854e0062f2ed95066c7ba5011bc1f408ffffffff8257c40ff5c6d9b06550e9b712eab8be8b7c88b9fcdf1ddd62492dda15f37cd3ffffc2ff8c654cd4fb5861b24dce2c3a7400bca3e230bbd441a5df4ad795ffffffb7d861c4d1a4fbf4d6d36ae96943fcd96e34468867add0b860da732d04ffadfeff44e51d03335f4c0aaac97c0ddd3c71aeaa41022710100bbeffffffff86200cc925b56857b3856f2009d466b99fe461ce0ef9de5e98c9d9292298d0b0ffffffffb4a8d7c7173db359810db42e3b5cbdb7ad6cbac02083b8edb6b3bf9a0ce2b603ffa5feff9ad2b1743947d5eaaf77d29d1526dba116dc73120b63e384ffffffff3b64943e6a6d0da85a6a7a0bcf0ee49dff099327ae000a
    b19e077d44930ff0d2ffffffffa3088768f2011efec206695d5762f7cb67658071366c19e7066b6e761bd4fee0ffff5b10405a7ada10cc4add676fdfb9f9f9efbe8e43ffffffffbeb717d58eb060e8a3d6d67e93d1a1c4c2d83852f2df4ff167bbd16757bca6ddffffffff06b53f4b36b248da2b0dd84c1b0aaff64a0336607a0441c3ef60df55df67a8efffffffff8e6e3179be69468cb361cb1a8366bca0d26f2536e2685295770ccc03470bbbb95feaffff1602222f260555be3bbac5280bbdb2925ab42bf1b35ca7ffffffffffd7c231cfd0b58b9ed92c1daede5bb0c2649b26f263ec9ca36a750a936d02a906ffffffff099c3f360eeb8567077213570005824abf95147ab8e2ae2bb17b381bb60c9b8effffffffd2920dbed5e5b7efdc7c21dfdb0bd4d2d38642e2d4f1f8b3dd686e83da1fcd16ffffffffbe815b26b9f6e177b06f7747b718e65a0888706a0fffca3b06665c0b0111ff9effffffff658f69ae62f8d3ff6b6145cf6c1678e20aa0eed20dd75483044ec2b303396126ffffffff67a7f71660d04d476949db776e3e4a6ad1aedc5ad6d9660bdf40f03bd83753aeffffffffbca9c59ebbde7fcfb247e9ffb5301cf2bdbd8ac2baca3093b353a6a3b4240536ffffffffd0ba9306d7cd2957de54bf67d9232e7a66b3b84a61c4021b685d942b6f2a37bea66bfeff0bb4a18e0cc31bdf055a8def022d101103
    129aae699a0008070917060a699aa669050b040c03a4699aa60d020e010f7befbd17232f3b474f4bf3bdf7de4743372b73134dd3345d177f1b1f232b33344dd3343b43536373ef344dd383a3c3e378326443600001030243322443030405b249332400704fef2d6159ff43efeb9aa669ba193f21314161816976dd69c1408103010203a6699aa60406080c10b0a6699a1820304060e338b291add3c3063021094b9fa3a74106f996ab030b0cb3b167930d6b140207c00e20210fd946ee0f0b0100b6a20a905e010809440045cb2ba2312e33235db49c5f0560780150a66b9ab300071054731f523b3bc8c91f0070300040c01f196490a6500a60208305a306a0c83f800d32c82040e00637c820831f5818907f32c8204d533b78383248d30cd0511168c820830c28b00820830c328848f0d30c36c80454071455830c3258e37f2b74340c32c820c80d6432c8208324a804c920830c8444e806196cb29f5c1f1c980619a46954537c3c061b8441d89f17ff6c196490412cb80c649041068c4cf89041061903521241061964a323720619649032c40b621964904122a402649041068242e490410619075a1a4106196494437a061964903ad4136a196490412ab40a649041068a4af49a410619055616c0904106690033764106196436cc0f061964906626ac06196490418646ec64904106095e1e904106199c637ec10619643e
    dc1b1f061b64906e2ebc0f0e1a6490c11f8e4efc41066148ff51ff11904186a483ff713190418664c2612141061964a201814186649041e259418664901992794186649039d2690619649029b209898664904149f2554236bd411517ff0201480619e47535ca410619646525aa06196490058545ea061964485d1d9a061964487d3dda061964486d2dba196490410d8d4d19644806fa531319644806c3733319644806c6632364904106a603836448061943e65b644806191b967b644806193bd66b904106192bb60b480619648b4bf6640819645717776448061937ce679041061927ae07480619648747ee480619645f1f9e480619647f3fdecd06196c6f1f2fbe0f31c860939f8f1f4ffe0c254325ffc1a1c9503294e191940c2543d1b150325432f1c90c2543c9a9e999c9503294d9b925432543f9c55032940ca5e50c2543c995d5b532543294f5cd2543c950aded5032940c9ddd432543c9bdfdc332940c25a3e32543c95093d35432940cb3f343c95032cbabeb32940c259bdb2543c950bbfb940c2543c7a743c95032e797d783c40c25b78692a192f7cfaf6428194aef9f4a8692a1dfbfc73be91bff7f059f5707ef74ee69ba0f115b10df0f05d334cbd3590455415dce3dddd9403f030f5802af0f349d7b9a215c209f0f095af634cdf2085681c0607f21830c720281197272c8c91807066127879c1c6004033172
    c8c921300d0c0da15872c1afa669baefc3670f031f3f7f9aa6699eff0103070f1f54b3a6693f7fff3fdfecf27351031ca208a20b03e0d78b6796a19c009fa302ffffdf6aef6f7273742070726f6772616d2065766572b5d6de6eff467265654578741063741d206973160cfcdffe7f696e672074686520636f6d7022737365642066696c6573b1b53bb76e172761726306422eafbdb5f6af651831112064692a47e666c3b679226f4836102987bd77d9455a46149f136fb743d66e565061588f4572ac7200b33bf7ca416e08bab46e747381d775db61c866076da76e58755b7f63e17e3a0a0a20257302135c9f6f66746fbbedb777af655c4d69632b730d5c5728646f77addbbaf6735c43755851565f73825c797bbbddda706c670c5c53fa6c6ca56f6c6419cf8dfc846a2e74744667106623e06e5e7973ee76508825ffd6c6d344136f4f46545741524508b6ab48e73a5c3e2e42b04b1325d939610ab7366047493b3a6e65553ffcb7c9a951f3636b204c61756e63eb4170701bfbb6854447614371176c1625afacdd0ef9637572251f7267430efe66ee6e530b747570cf08204661766ed858db5a6968e7660c2bb1827b6fb964546ffb089e4bc259d7b644186b0a3f6408250ff336365bcb576d307525003de186c32493944d532063446cb3a6c263c2546168c56465d7dbda850b176f757b6e27e8f965cfcb68b746870e
    74700261cab0301e772bf0315a3b6f2b76312e348972ffc16ddbb774373a2f2f77002e3b73c97db72dc786cb2ed6b1413f2079688d606f5820737508241cb69ba9cdbaf06b836e666f6d98608ac50ea30f3aeeb542c0e9d6f8a7249dcd666fea2e2043ae3f0b44add09e862bdf43e6c9133423606c6820f967f759736ca37222fafb206e6f54d28c49b123893a4f6d1d09294bec2e0000b6e9bd8137a227ad666f94859f02d254b0e36c696b7ad8a150b679516ff10e3b3dd89b1bb4085346583b4974273ba1bd91306b746f2688d60aef050bdfc376ebb5e78cc23ae76f2424626b94ae6b440931ff530e1bc0354763742fab060d05c150de1f55524caf06c78b1ff6176f5646414c53bbf86d8d18051d6f90a9748dafd0622b23575f4e6b6eb6b99f65556e6e09bc41e92b68aef7ef6275671f6f4755493ba78a1f0fcddf635a697053697a61b877dd20257c4478a23257450f3d6ced2180731258651a2e84c712824b7d1badefae24302a7220626524afb5096f086d6f76671875374df6dec8757052190f8073e0b5d8a17fb92824d71d66384c057381966242ed84ed6fff75be00f1895af75bb44035786d6c00740368042bbbd6d6078f000e036466bac2d6685b1b6c5bae07623c740057b62f6f3fdf84918c916bdc53610069b01c74042e0572b477ba0a4265482026ffd11a984518383261e2b0b5c6edf30576614b4f
    ad61fc7ffb8f29723a200a2f205c20062a203f2022203c203e207c2014518414092a2002a3d9581514c8aaa80029d23fbf349f0100ffff104008c880084b01decb068a9100080b014d20cd2d50740368d36c010bfe65c0ba4405675110503700c00018016203eb5ef5030055821b1f2be93a28094f1ffe28362a7e6450a6413222bac7969de81f80003c67420699b237dd63006b2bd8ebca9abb414e997800743e2bd70d72721201ed43536e55d134873dcba3300b0dd96cbb11f50201f5f7a31f516c454c1d0df245b4902de1f91f77ad15b13def5816bc6f3f6c737793fb174600726901459f84d0756f72cf076f09208783d07d930040670387c177fb02214f8bbd5075c3d10d605dd5fdf1a3801f763681bd5051d0008dc3001fa003d23db000cfe3f2400425796c040e0497140188ff30778593404b5b13011600f803d95cd77d4d43296f01732f3774ba6e73dd07661135640b720d79745fa46b7507771720056909a13b7307336f05654f3df3bd6149554d690dfb5cd79d2d2e07491520172b6413eb66cd991d6e744d1b730b72f798ac2c2563270d6200652cc84d3772b161650064ab0cf610bb2ee55f81f329c796d0dc0e00ea81e51be100d7f1718c3b8fec038d26e372431541be5877bb2703d8600d1de19c87f6274381f1be370befb1cd691916027b935779c1ed0006422e61df03009f1b644215501da5731b
    3b732377098577af6cbbb06d2424a7096ed55f6cac9b61c9630d762b0f65497a4809f78b2e439ffbe67bc38f2d0c00ef036d49256cddd80877075f00706767477301336617f60c853215314249919bc9db240800faaf163e545c801bc0903e22c7e7168027cbad0041334989a40f1574ffd521d2f7186df95305c176db6fd9008f2800d39e333227d05cb690b71f182b7ba6e3cc871f430d704bdf8d6d6e6c688da903f530012d37d67583093113417b64a977b35e6ca4812177743599f746ee3ef363516e007462dfc724b23abab32ea50ae9dfb26027d3662151a1658737c208f973317275d170dd4f086b03508f0054a70e0b608f8a13c34fb5242f59e7611f49b7f18d6c06ba745f3a9101772e92f1de3b2d6bc37943b3e926fbdb6d4e287fa33801c993dfacf503120b09007d0f15ffc6c3c6008482841784038230f75cec7ba4020843004476c82332222327f208ac838c8300844461ef205b27809ea7e441e65200a783830e1072824444222240c8014244442507083922224444aa6c65572200a72ab0016c440043a7ecad02f9222228a9a7b6b2d3aaeda9a7ca862c55b243a7652b3baceda9a751d9ca56a9a78bb295bd97a9a70659207b96a9a7215b6107d0a7a91bb209b023a7847f8494a36c6529a9a7605376647f339333a92b5b655d3085a74a60207b12a9802d5b590abb23a7a9a76c853c024444a9d95b65
    09a77f10eb4ed81b0ea927770072292be411d8a7444402837d67a94753741a802d43482f04a736330281b365a7d3302d6c641336a743d1a7424e81bc44442222c0dcb297a9a7a9a7cf0709a431777778378b30186b071d407d75b70542d74b7388008764826cac071d20a7446408eca553a72307811c20438333b2043608e033a8a70596840b84e0a72abbc0580128a7b0651316a94317a7f60283ab32792832a705d2b9105aa732ec5c2039838349588888cb5260cf36a7a93db7300e67a0f1f8d8c0ba21a0372098a7b061c956a908a758f686bc888838a7c15242482637a711d6859238392097ab2c65b30fb8a7662323c9838378a7616f9612602893a747d21248e72144c2de1b61a70793a7802d12ba800ca700791532324444447660a57e28a72f9207c83383834444ef254b2010a700ec9902068aea0ea7902b7b81a9a72219852c9700927f842d0c584ba70e56d91b53a777224b96922310a72403803c44443262cf15322232734e62b309c0a7432a9d023995cd58a72832213b02998843af43c200b043a758231bd901124733a74650f61299a709a7651029c883968a464805b2a70fac252424ef459208e441b6a783834861af434592a76055b6123ba72417b63248a7844c64c9466aa7322e0be6863fa70078836cd9c8a76ba73bb0659384005373a72547720a3832322107616f00a7842513c8143833235b6013
    8310a754d8a46076f4a7d2ca5eb02ca700f45bb62a1ba700a708399221332200394806448403c904f2383823228b00322032874aa6b22b84a74426209b34f4a743269089403223c92919423383835b60bc513f9aa79c21902b2838e42a4b609aa743e402990833235c81bc823838289008249733370b64c88644a7845d21b19243372246240794a784b265812436b8a74032913c83838372403661f2a73250d98c149aa70239421e383833b009a9e438f2a765ac9283839aae4aa6b2a722843902e448442356813c0a8383950c6113f2a732d98c0132229aa7840c241343444a1ec844833838362089c9dea7150aec2e58723838a7902bb00b888383a73392a9902128430a1948a6432286e428b983448461279a4a778ba76521240636a72ab043bd009fa70a99a0e44483a3e488e4843881cde0922378b1a7c851c9137384443225532543284300c91432433342a805c933a55928ecb512a70f02092c1ba7626c4012cba736a7a96428b944238be4a890288457480392324830066c4012dea7443c42ae482222220e9203844444284b422d4221a70506ad0076bd4148bca7de215c65b3911318a784ad906895c895ad42e2a7dea8039298b0a930365c646f65a82da732ad0c6295100a835895a710940d48e280dea9ac42062ca78399b2901094a738c4041620a93059281b90dea9a7b6328854baa724ae6c55a980deb6ca5e
    20a7a9250a6c85c1308ea7620b09401ca73365a1e4ca8622a9a540a20d20643606ee2a9ba7a940a78eec8db255a7a9a7abecc082cea9f612807ca73223a7c016255743a959822c5ea786a9402ec002a7444b22250f8383de2c84ad923300a732941c20838323240e3940383844def9565882a9a722843c0702383344a479413243838322cc0d4aa632a9a732851c2083802848267944383083990a19198444382a5b5920a8a7866c95b598a7332942064032099021904433122990913822de45b6845550a70d4858322250047210b2a7844496b0ca608e50b09085caa77ca7908990833228086464228443990819993823430b84402843a7b72c95c4dea7a9c05e20b0a726a96801d22da7228955b6a43fa7de5b59d12a7ea765216422228d42d9326058a7235a25cc7c4f874a5a652ba7320a5b49b002a7802db21a435ca7087bcb42a8a9a748aa96b0f8b8f6ca9225a78f8838a959912c44a7da40b6ca56a9a7b6ca56c23850a9a710d9b3508e4883a9124c2eb0a78203bd50d85423a78402994b84a918a7b341d81250a933c8171c61a788574844b6b00414a76c490b2ca990a7896c95b050a95d09b36511a7c08205a16c2549a9650bb28067a7a908cb0e2c0e40a7502b9bb241a922a7c896125e96f8a9887823b015a7a991054202b7a76153646f84a94090085b911d22a9a7f64661e788a90ea74189b055a9c25e207b
    25a772a95d0a9b8d0ea7a9226cc842202533a7b5007b8180a9b2a5b0949fa7a988940c6a110929b20296ef2ff61a16b28321a783a983a1e4412888288f15d9920984a96c5921893783a90c8100abc7884204ca92a90f83b0241732a925d08eb088b830a5d414c683b0a98884b689b065c58057a984560c6433305750907495ada98460b295042fa79ba9621b24a560a788cb56b64a25a910d92a0378a92fa98b2eb256b430266c42950a1ba960a3842b80c4a99748204892a71224b18450ce40be60caa980ec82aeb090059da9302255b6326da96114b6a4f7a9569d5218c0d749a5305885a9b285c296043fa925410bec29a9e71512a1b0a95c44d8b2a27fa9a216d8a4f7a92c12614bc7a9066c09008307a936c8e2202806a71689906ea988b05c85bd44a7a98815f61259b0a7a9b2b748685aa7a9bd4442015aa704f64681a9a7a9803c0472882083830406104b182f092d406a1e32896c655788a7a9ec8dc06614a9a7a9a5c066a912a90442a9eca7a9dc5e2a5b7684a7a90ba3ec05c2a9a7a9b237087b0da9a7a9e220ec8d0aa9205920b9402388a90049045340860aec85b2a9a7a9ec28b037a7a98068c4c856d9a9b82123444607a740be006b9ba9308383822c8025baa709b095d4b6a98190ca66a7a96480d4826ca7422a7bab2ca9a7a98c8814c60e0781b059888ea944864b65a798b8d9a28c8807a9a9b020560f
    05f64660a9a7a96f14d81ba7a9a781bd5160a9a7a91b05f646a7a9a7606f14d8a9a7a94681bd51a7a9d81b05f6a7a9a7a951606f14a7a9f64681bda7a9a7a914d81b05a7a9bd51606fa7a9a705f64681a9a7a946811d1a2fa9a3516087d7a97f026c14d8a9a7f65681bc3838a9a7ab405e05383820af027ba9a738385781bd55a9a738c0de2a9038a9a76015c8ab3838905c05763fa73030881615afdf6789682b2f2a08000038ef204ac9070093e1c7bd00ffedffffcdac9e00a7887c008c625a008b6158006b423b3505050020161400a5ff2dbefd7c7400845c53134a3e004f4000d9c3ba00513629ffffffff00b09b9100745747007a685e00ccb7ac0090786700f5f3f100fcfbfa00f9f8f77f6bffff00e4e1dd00dcd8d200f0eeeb00e9e7e4171800fafaf900fe7c675f6e0000fd00ce715f84330333b3354dd36699ccff17664dd3344d666666666699344dd3349999999999d3344dd3cccccccccc36db744dccff0fffffcc6a0137fb4c3766039913ff7e8393cd344d33333392ffd94cd37483333333a6ffcd344d3783333333ba4cd37493ff83333333a6e99cd9ceff13873333e9de9b6cdeff89018b66a6e9269bf2ff8b66669aa6699a6666666666669a6eb26016cc876666a673a66966661a0333334dd37413ccccffff839bd06ca66601ffcc4267dc7bba99010f003391079ce94ef80fff6a66133374a6e94c
    d70317337b99995ca4699a99999999176b9aa6e97f999999999917105930dd8399b6ff3bbc77988901815bca1364c199a6333387daff70a773df87661787ccd8344dd345ee8bccccccccd3344dd3cccccccccc3af7344dccccccff178bccee34dd69cc87ffff839aa6699affffffffffff699aa669ffccffffcca6699aa6ffffffffff9aa6699affffffffffffa6eb9a69ff6e43ff13ffbd679e86ffa36666635b97dba5ed23072181005f000077008672b95c2e009600cb00b200d72e97cbe500dd00e300ea00f100f8a9fdb65473fb3ba4a0a0008000e5bd37e9de0206ff0112259757d6041b00191a191bd9dfa76c2b1c14180f0018141c2bdbdede4629120310000b10032d192bfb375d22291304040b0e0e1013122dfedd43d82b19170d290b04090f090a060edb76d9ca2d2b19530f2a0ad973efb713290202030a0b2c2d892b67aebd4d17a71311041b292b066d17f6bd0b2db71c2b1cfc11ccdc6cb6a609292b2c2d9b7d0333122b2a11532b64efd72d2c06890c13592bf742f6dc1955a72b002d08f903760b0f1a2b1a0f10110f0247c809f90e131811130604761821dd2bec30e12d280909035f0683867b638129032c2b11061b618f162b530f913db7ed050304270a002d2bf65e36c3175e2909120d0d2c2b842fd6ba2804002d2b1c33b936569e140d112901002c1b612cb657132a2d0bba1a2b369bfd38145e
    0a03292c2ba35b385b2c0c7d172b1c34f77833c733292a6edab5d65e3259852d5a2bf84bb6add60c04290103110c0e08bbed256bd910862d2c1a2b100bbdd70e2b2a127f2900399e9bac1d2d04872b62f7350fbb2c13022913100700085bfb5cf7bd882a0c831cdb59138493b3e7dea854282c08af9de0662adb89105954dcefc0be0b7e072c10dc0c1055305798cd2b2c827f2bb0b92d16b37009162a17b209a584b7941dad6a053769089b00d3d7de105a424329d150bde036ced8131912004404121a612798922b6e490a2cb10831be53031f9b2c2e367bb3002aee2c2d543977788f041f2a9ece2ccd8610c12b617588730c21b22903a1202cf796b57719002a492b5ba81b13863d7d2a799800c6c01e1d9013762c4aee46976c2a8409fc031783be80d11a928f2a2c11d0fa58f62b29870d7d5897ec9d8b125a2db113ab25a9b9730d66296facc3b1ac5a2ddd790d29195ed6b903fb141a8dd6e00e9c2d0aac1f580229d20d093c0d1719531c2d11ba05032e54de340fa704f40610e82914d71ce40633e78e89171cc18da72f6e30ba05220f541a081e043278a787b042197cb3380819df217bb325061c63082b513f457d0090ef419c262baa7749df2e01b7196946ea0283204001b6406a160687009c3c7b1003fc0307fe0fd334cdf2ff1f173777af69ba6e67fb8bdb03dad8c09b21906f650300ff1e3979d9f80103
    f000e07fffc099419a41800010429aee8b272f07033fff1a1a26e1f900022220330100618a6a346db21405645409b219a000c44060e8f6cbff016c7374726361744147657453790d656d6d6743aa1c1454117040cd0c15db0d5534b0092d23b5eeed6ff7616e64456e76136f6e6d1c53522a720b153e731aa82864916e50fc6cdb6d4b41742362751b736446756c6c7ee7a8e83711497342616452bdffaddd3b6450260d4d6170566965774f6639eb5cf786540b18706930530cdbee33776570065d54696d274c6fd86c73bfec610e546f18446f7344411613807223867a0761a9a24966411b6d707b312a32a62aaee66e6e3b50bc7641066f6613ef256c036c194874446cfb9bbd3edf114d6f64f3651316ba202c5c3c63187342785454893eb753456d576862086169706f6e6bea135378414f626a2190866043193eaf53beb9df264d777469427974fc4e6465436883e142d7f33a6c146548c76185d7babf8a6f6d6d0d4c5f9b7e93b0190c164c44656450b6cc01d46f69f9cc3e83f00e6f2eb922427054513b08f84c610db76bcd8d3801ba5e36a6616731707bcbb6d469f6343c6e415c15bd81eafb154cd63d3002546806649d413bcd466b4240dad10085acb5767b675175a079561a066097c3eeaa6867d44b65790e38cde662f60f36de105b8c214ceb5214566b5ec3c3610f53cb781e6cc6821bc1ed0d426bfc156c
    20891b2ea016990264f166330e7b071f0b6b48f6cd330d6368426c2c717006ef63b7521446444359524f5032a4cc663b6c2f61e4a2e8886101a856de2a496e69d73205b7cd9c0d4013732942cd96ac7c1f5521dbf2f4430ca03e5348d07edc170128d9894897b7702b9c7bdd814944f1739fa6c70ba8096aa385d269b6417afe0454711650b30bb66e75ce29bb6007d749311110726f676a6fd74abc62817473476f1c1f11826963698adb191d37755363476e9e6c2a7346fb456a533d5a49630bb81d2fd86c34591ac973da6e366bb61c0b516869c5e310e3cd29bd92c1406df684b76f79a6c075620b1fdbac0d8c737336ae0a558eec30c3706440a753d842be58a135281a225c8a42166cb6d1dabd704216f89850da0e6f780f72cf0c09cfe078c752160d0aa55cb061b328258d37b5b646eb0c541368a37080c9618eefae5d610d95475d18962ce11ad17072edb981d0856639eb0d0abf1c8364f02a50454c0104004d9ae53fe4fab63ae0001f010b01065afad9d9344f733c1070000bc98210c10208073b73496e0c80011e341007b6b36403060078078c05187179f05886b9bc42a86ea798432e74960db633ca07a058905ac43abe8c0808602e7209610be90ed9fbc012275e4eaacfba40022e2600305f211ab96e53f62f27c04f7372945b49d39debf04f888a3b00c09f5bbc247af97501000100000000000020ff00
    60be004041008dbe00d0feff5783cdffeb109090909090908a064688074701db75078b1e83eefc11db72edb80100000001db75078b1e83eefc11db11c001db73ef75098b1e83eefc11db73e431c983e803720dc1e0088a064683f0ff747489c501db75078b1e83eefc11db11c901db75078b1e83eefc11db11c975204101db75078b1e83eefc11db11c901db73ef75098b1e83eefc11db73e483c10281fd00f3ffff83d1018d142f83fdfc760f8a02428807474975f7e963ffffff908b0283c204890783c70483e90477f101cfe94cffffff5e89f7b9560100008a07472ce83c0177f7803f0275f28b078a5f0466c1e808c1c01086c429f880ebe801f0890783c70589d8e2d98dbe007001008b0709c0743c8b5f048d8430bc9b010001f35083c708ff96489c0100958a074708c074dc89f95748f2ae55ff964c9c010009c07407890383c304ebe1ff96509c010061e91fa9feff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    000000000000000000000000000007000100000048000080020000008800008003000000f8000080040000003801008005000000780100800c000000780200800e000000b8020080000000000000000000000000000001000100000060000080000000000000000000000000000001000904000078000000486c01003401000000000000000000000000000000000000000000000000020067000000a80000807e000000d00000800000000000000000000000000000010009040000c000000018fa0000ae66000000000000000000000000000000000000000000000000010009040000e8000000c8600100600b00000000000000000000000000000000000000000000000001000200000010010080000000000000000000000000000001000904000028010000fca20100a80800000000000000000000000000000000000000000000000001007100000050010080000000000000000000000000000001000904000068010000286c01002000000000000000000000000000000000000000000000000000050066000000b001008068000000d801008069000000000200806a0000002802008081000000500200800000000000000000000000000000010009040000c801000000f300008201000000000000000000000000000000000000000000000000010009040000f001000088f40000a00000000000000000000000
    00000000000000000000000000000100090400001802000028f50000dc010000000000000000000000000000000000000000000000000100090400004002000008f700002a010000000000000000000000000000000000000000000000000100090400006802000038f80000dc0100000000000000000000000000000000000000000000000001007b000000900200800000000000000000000000000000010009040000a8020000806d01001400000000000000000000000000000000000000000000000000010065000000d00200800000000000000000000000000000010009040000e8020000a8ab0100140000000000000000000000986d0100280000002000000040000000010008000000000080040000000000000000000000000000000000000000000039383900292c29003138390042414200100c0800292018004a30290010101000181c180063595200524d42002928290042454200101410003124210063494200845d520073554a004a383100423c3900393c390052454200c6b2a500d6b6b500bda69c009c8a840052300000634939007b5952008c655a008c6152007b594a004230290052494a00dec3bd00e7c3bd00debeb500ceb2ad00ad9a94004a4d4a0008101000211c1800523c39008c615a005a4539006b615a00dec7bd00e7c7bd00d6b6ad00ad9694004a494a005238310073514a0094695a00
    846152008c757300d6baad00dec3b50094867b0031303100291c18004a3429008c696300b5968c00d6beb50018181800212021005a3c310084615a00845952009c756b00ceaaa500debab5006b6d6b0029202100734d42008c695a007b554a006345390052343100a5867b00c6a29c00d6b2ad00d6bab5008c7d7b0052595a0031201800211410002918100039242100ad8a8400c6a69c00debebd00b59e9400524d4a00949694006b49420063413900392821001814100018101000291818004a343100946d6300b5928c00ceaea50073656300634d4a007b514a00422c2900080808000804000010080800211818005a41390094797300bd9a8c008c797300292421006b656b0031282100b5a29c0094655a0000040000180c10006b45420021201800e7cfc600e7cbc600d6aea50094716b005a454200181810000808000042342900bda29c0052494200080c080021242100635d5a0039343100100c1000b59e9c00393029005a4942008c6552006b514200947d7b00c6aaa500101008007b6563008c5d5200b58e84006b4d4200846d6300bd9a94009c696300945d5200bd968c00a5756b00cea69c007b4d4a007349420031343100846d6b00ceaa9c00bd928c0094615a00c69e94009c716b00a5716b006b453900c6a29400b58a84008c595200ad7d73007b655a004a3c3900bd9e9400d6b2a5002118100029282100
    63514a009c7d7300d6aaa500735d5200ad8e840052342900312c290063555200312021004238310094756b006b4139005a3831008c716b00ffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070707070707070707070707070701b1b1b1b70707070707070707070707070707070707070707070707070701b1b1bc4121b1b1b707070707070707070707070707070707070707070701b1b1bc19ca11eac441b1b1b7070707070707070707070707070707070700d1bbe5bada682a61e4cac4f501b1b1b70707070707070707070707070700d1bba5bada682a6a6a11e9aac9a9a4fbc1b1b0d70707070707070707070701bb7b89c4882a6a6a6a6a11e9aacacac9a4c4f6e1b0e707070707070707070a4b29cb482a6a6a6a6a6a6a11e9aacacacacac4c4c441b1b
    70707070707070700db14848a6a6a6a6a6a6a6a11e9aacacacacacac7eac631b70707070707070700d9ba1a6a1a648a1a1a6a6a11e9aacacacacacac7eac5a1b70707070707070700da5ada1a6489998b0a1a6a1459aac9a9aacacac7e4f5a1b70707070707070700da552a69f7b7ba947a048a11e9a9a201d129a7eacac5a1b70707070707070700d9b9c9d9e9f2525257ba0a13f4c1d451e9211a24c4f5a1b7070707070707070159798994125232323417b996811921e1e1e1e37119a631b707070707070701b1b945d242323232323244195741e1e1e1e1e1e1e1e112d1b05157070700d1b6b418123242323232323418f5f8d9112111e1e1e1e1e922093670570701b1b5e2424242323232323257a8c8d2a642a0f2b20371e1e1e1e1e1e111d050e701b1b1981242323232448760b8b0e0505050e2a3e9a11921e1e1e1e37790e7070701b1b7a81248182c184790905707070700505643d6720921e1e37870e70707070700d1b7a315b7b44773d5805707070700505057d3d7e1e1e11137770707070707070706c47924e9a6e0f640570700505642a77632d74754076777070707070707070709a1e92111d124f63640505643d6367126840482541196b1b707070707070703e371e1e1e1111204c2b0f583d5a443f5b5231255d23415e5f1b70707070701b4c4d1e1e921e3737114e4f3e504551523125232323232341
    555f1b7070701b441e1e1e1e1e1e1e453711111147482525252323232324242424263b1b701b3d3e121e361e1e1e1e1e1e92113f4041233a2323232323242323263b1b1b7070701b1b2b121e361e1e1e1e1e37123839232323242324243a263b1b1b707070707070281b1b2b201e1e1e1e1e112d2e2623232424242331321b1b707070707070707070700d1b1b1c1d1e921d201b1b192324232526271b1b70707070707070707070707070700d1b1b1011121b70701b1718191a1b1b7070707070707070707070707070707070700d1b1b1b707070701b0b1b1b70707070707070707070707070707070707070707070157070707070701b7070707070707070707070707070707070707070707070707070707070707070707070707070707070707070fffc3ffffff00fffffc003ffff0000fffc00003ff800001ff000000ff000000ff000000ff000000ff000000ff000000ff000000fe0000003800000010000000080000001c0000003e0000007f000000ff8000007f0000003e0000001c000000080000000e0000003f000000ffc00003fff0180ffffc3c3fffff7efffffffffff407601000000010001002020000001000800a8080000020000000000000000000000000080ac010048ac01000000000000000000000000008dac010058ac01000000000000000000000000009aac010060ac01000000000000000000
    00000000a4ac010068ac0100000000000000000000000000aeac010070ac0100000000000000000000000000baac010078ac01000000000000000000000000000000000000000000c4ac0100d2ac0100e2ac010000000000f0ac010000000000feac01000000000008ad01000000000016ad01000000000026ad0100000000004b45524e454c33322e444c4c0041445641504933322e646c6c0047444933322e646c6c006f6c6533322e646c6c005348454c4c33322e646c6c005553455233322e646c6c00004c6f61644c69627261727941000047657450726f634164647265737300004578697450726f63657373000000526567436c6f73654b6579000000536574524f5032000000436f496e697469616c697a6500005368656c6c4578656375746541000000476574444300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
  } # 25.5 KB, hexdump via `od -A n -v -t x1 -w512`


  # Ruby shame
  def to_i(bool)
    bool == true ? 1 : 0
  end

end #class FPM::Package::Exe

Added lib/fpm/package/filter_appdata.rb.

































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
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
47
48
#
# api: fpm
# title: AppData/AppStream
# description: Generates a pkg.appdata.xml for distribution package managers
# type: template
# depends: erb
# category: meta
# doc: http://en.wikipedia.org/wiki/AppStream, http://people.freedesktop.org/~hughsient/appdata/
# version: 0.2
#
# Creates a /usr/share/appdata/PKGNAME.appdata.xml file for consumption by
# distribution package managers.
#
#  → The point of which is to embed a shared screenshot and lookup user
#    reviews/ratings. (At least should benefit appcenter listings.)
#  → Only use this filter (-u appdata) if you're not already including a
#    custom appdata.xml file.
#  → See also the advised description style in the AppData spec.
#  → Primarily meant for desktop applications.
# 
# This plugin will write to the default usr/share/appdata/ location in the staging
# path regardless of --prefix.
#
# BUGS:
#  - Does not yet escape XML properly.
#  - Doesn't split up description into <p> and <ul> sections (or store lang=).
#  - Stub screenshot used, we might need a new --screenshot flag.
#

require "fpm/package"
require "fpm/util"
require "fileutils"
require "erb"

# create appdata.xml file
class FPM::Package::Filter_appdata < FPM::Package

  include ERB::Util

  def update(opts=nil)
    dest = "#{staging_path}/usr/share/appdata/#{name}.appdata.xml"
    FileUtils.mkdir_p(File.dirname(dest))
    File.open(dest, "w") do |xml|
      xml.write template("appstream.erb").result(binding)
    end
  end

end

Added lib/fpm/package/filter_composer.rb.







































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#
# api: fpm
# title: Composer.json stub
# description: injects/updates a composer.json from fpm attributes
# type: template
# depends: json
# category: meta
# doc: https://getcomposer.org/doc/04-schema.md
# version: 0.1
#
# Adapts or creates a composer.json from fpm/package attributes.
#
#  → To be used in conjunction with -t phar plugin, to craft a whole
#    composer bundle from plain scripts.
# 

require "fpm/package"
require "fpm/util"
require "json"
require "fileutils"
require "time"

# composer.json
class FPM::Package::Filter_Composer < FPM::Package
  def update(opts=nil)
    # read existing data
    dest = "#{staging_path}/#{@prefix}/composer.json"
    if File.exist?(dest)
      json = JSON.parse(File.read(dest))
      # technically it could also become an input filter now,
      # injecting known values for absent fpm attributes here
      # (only had to be transferred back to @input in command.rb then..)
    else
      # create afresh
      json = {
        "name" => "#{@name}/#{@name}",
        "description" => @description,
        "license" => @license,
        "homepage" => @url,
        "type" => "library",
        "extra" => {
          "\$packaged-by" => "xpm/fpm",
          "maintainer" => @maintainer,
          "epoch" => @epoch,
          "releases"=> []
        },
        "autoload" => {
          "shared" => ["#{@name}.phar"]
        }
      }
    end
    # update current build information
    json.merge!({        
      "version" => @version,
      "time" => Time.now.strftime("%Y-%m-%d"),
      "bin" => bin()
    })
    # save `composer.json` to staging path
    File.write(dest, JSON.pretty_generate(json))
  end

  def bin
    ::Dir.chdir(staging_path) do
      return ::Dir.glob("**").keep_if { |fn| File.executable?(fn) and not File.directory?(fn) }
    end
  end
end

Added lib/fpm/package/filter_deps.rb.

















































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
# api: fpm
# title: Dependency matching
# description: resolves package names for other distributions, using distromatch/whohas
# type: filter
# category: dependency
# version: 0.1
# depends: bin:whohas | bin:distromatch
# license: MITL
# 
# Package names in -d dependency lists aren't universal across distributions.
# Which kind of complicates building targetted RPMs (or sometimes DEBs).
# 
# Distromatch or whohas.pl can resolve library and package names across
# different Linux systems. They're utilized in this filter to convert basenames
# between each other. Debian being the assumed reference point, you should
# standardize on theirs for specifying fpm -d lists.
# 
# A flag `-u deps=opensuse` flag can be used to specify the desired target
# distro/family.
#
#  → With `-u deps=fedora..debian` an inverse lookup is performed, only with
#    Distromatch though.
#
#  → Using whohas only plain package lists are fetched and distros and
#    package names searched raw.
#
# Optimum version matches aren't performed. (This is where it's getting even
# more complicated.)
#
# Obviously as these lookups can be time-consuming, you may wish to use either
# of the mentioned tools manually, and prepare per-package --dependencies lists
# yourself. Whohas is likely most revealing for that.


require "fpm/package"
require "fpm/util"

# Resolve package names cross-distro for dependency, suggest, conflicts, .. lists
class FPM::Package::Filter_Deps < FPM::Package

  def initialize()
    @dm = `which distromatch`
    @wh = `which whohas`
    logger.warning("Neither distromatch nor whohas are available.") unless (@dm or @wh)
    super
    @source = "debian"
    @target = "debian"
    @opts = []
  end
  
  # traverse lists
  def update(opts=nil)
    
    # check for `-u deps=target` or `deps=source..target` options (option tokens are preseparated in command.rb)
    if opts.count >= 2
      @source, @target = opts
    elsif opts.count == 1
      @target = opts[0]
    end
    
    # replace all the things
    @dependencies = translate(@dependencies)
    @provides = translate(@provides)
    @replaces = translate(@replaces)
    @conflicts = translate(@conflicts)
  end

  # replace individual package references
  def translate(deps)
    renamed = []
    deps.each do |name|
      name =~ /([\w\.\+\-]+)(.*)/
      name, ver = [$1, $2]
      name = resolve(name)
      renamed << "#{name}#{ver}"
    end
    return renamed
  end

  # lookup tools
  def resolve(pkg)
    if target == source
      return pkg
    elsif @dm
      return distromatch(pkg)
    else
      return whohas(pkg)
    end
  end

  def distromatch(pkg)
    return pkg
  end

  def whohas(pkg)
    ls = `#{@wh} --no-threads --shallow -d #{@target} #{pkg}`
    if ls =~ /\w+\s+(\S+)/
      return $1
    else
      return pkg
    end
  end

end

Added lib/fpm/package/filter_desktop.rb.































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#
# api: fpm
# title: .desktop files
# description: Generates a pkg.desktop files
# type: template
# depends: erb
# category: meta
# version: 0.1
#
# Creates a /usr/share/applications/PKGNAME.desktop file if absent.
#

require "fpm/package"
require "fpm/util"
require "fileutils"
require "erb"

# create .desktop file
class FPM::Package::Filter_desktop < FPM::Package

  include ERB::Util

  def update(opts=nil)
    dest = "#{staging_path}/usr/share/applications/fpm:#{name}.desktop"
    FileUtils.mkdir_p(File.dirname(dest))
    File.open(dest, "w") do |ini|
      ini.write template("desktop.erb").result(binding)
    end
  end

end

Added lib/fpm/package/filter_fixperms.rb.



























>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
# Set file permissions to 644/755 maximum

require "fpm/package"
require "fpm/util"
require "fileutils"

class FPM::Package::Filter_fixperms < FPM::Package
  def update(opts=nil)
    ::Dir["#{staging_path}/**/*"].each do |fn|
      File.chmod(File.stat(fn).mode & 0777755, fn)
    end
  end
end

Added lib/fpm/package/filter_man.rb.

















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# api: fpm
# title: manpage compression
# description: Compresses any man/* pages in the build path
# type: delegate
# category: filter
# version: 0.1
# license: MITL
# 
# Simply compresses any manpages in the build path.
# Only looks for files with uncategorized ….1 / ….5 suffixes.
#

require "fpm/package"
require "fpm/util"

# find manpages, compress them
class FPM::Package::Filter_man < FPM::Package
  def update(opts=nil)
    ::Dir[staging_path + "/**/man/**/*.[12345678]"].each do |file|
       safesystem("gzip", "-9", file)
    end
  end
end

Added lib/fpm/package/filter_strip.rb.

















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Strip debugging symbols from binaries,
# ignore shared libs

require "fpm/package"
require "fpm/util"
require "fileutils"

class FPM::Package::Filter_strip < FPM::Package
  def update(opts=nil)
    ::Dir["#{staging_path}/**/*"].each do |fn|
      unless File.directory?(fn)
        # only work on ELF files
        if File.read(fn, 4) != "\x7FELF"
          next
        elsif File.executable?(fn)
          safesystem("strip", fn)
        elsif fn =~ /\.so$/
          # don't strip libs
          #safesystem("strip", fn)
        end
      end
    end
  end
end

Added lib/fpm/package/filter_unprefix.rb.







































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Strips anything but the given prefix dir from staging_path.
#  -u unprefix=/usr/share/pkg/
# Will move the contents of that folder into the top level path.
#
# (It's kind of like the --chdir option for input,
# except that it works after input package extraction.)

require "fpm/package"
require "fpm/util"
require "fileutils"

# find manpages, compress them
class FPM::Package::Filter_unprefix < FPM::Package
  def update(opts=nil)

    if opts and opts.count == 1
      staging_from = "#{staging_path}/#{opts.first}"

      if File.exist?(staging_from)
        staging_keep = ::Dir.mktmpdir("package-#{type}-staging")

        if File.directory?(staging_keep)
          FileUtils.mv(::Dir.glob("#{staging_from}/*"), staging_keep)
          logger.debug("Exchanging staging path", :path => staging_path, :new => staging_keep)
          FileUtils.rm_r(staging_path)
          FileUtils.mv(staging_keep, staging_path)
        end

      else
        logger.error("Prefix directory doesn't exist in staging path", :path => opts.first)
      end

    end # opts
  end # update
end # class

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
214
215
216
217
    # 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?]
      (::Dir.entries(bin_path) - ['.','..']).each do |bin|
        FileUtils.mv("#{bin_path}/#{bin}", "#{bin_path}/#{bin}-#{self.version}")
      end
    end
  end # def install_to_staging
  
  # Sanitize package name.







|



|







199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
    # 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
    end
  end # def install_to_staging
  
  # Sanitize package name.

Added lib/fpm/package/ipk.rb.

























































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
# encoding: utf-8
# api: fpm
# title: Listaller IPK
# description: generates Listaller packages using lipkgen
# type: package
# category: target
# version: 0.1
# doc: http://listaller.tenstral.net/docs/chap-Listaller-Packaging.html
# depends: bin:lipkgen, erb
#
# Listaller uses .IPK files for cross-distro installations. It's well
# integrated with Freedesktop schemes and distro application managers.
# 
# This module just chains to the generation tool currently, and builds
# static / unrelocatable packages. (Proper support would require using
# Listallers relaytool + ligcc when building the app binaries.)
#

require "fpm/package"
require "fpm/util"
require "fileutils"
require "erb"
require "time"

# Build Listaller package
class FPM::Package::IPK < FPM::Package

  include ERB::Util

  option "--relocatable", :bool, "Assume application was built relocatable."

  # Create doap, files list, then package up
  def output(output_path)
    output_check(output_path)

    # pre-generate files list
    files = []
    ::Dir.chdir(staging_path) do
      files = ::Dir["**/*"]
    end
    
    # set up build path
    ipk = "#{staging_path}/ipkinstall"
    ::Dir.mkdir(ipk)
    File.open("#{ipk}/pkoptions", "w") do |f|
      f.write template("listaller/pkoptions.erb").result(binding)
    end
    File.open("#{ipk}/#{name}.doap", "w") do |f|
      f.write template("listaller/doap.erb").result(binding)
    end
    File.open("#{ipk}/files-#{architecture}.list", "w") do |f|
      f.write template("listaller/files.erb").result(binding)
    end
    File.open("#{ipk}/build.rules", "w") do |f|
    end
    File.open("#{ipk}/dependencies.list", "w") do |f|
    end
    
    # let the packaging be done
    opts = ["-b", "--sourcedir=.", "--outdir=#{build_path}"]
    if attributes[:deb_sign] || attributes[:rpm_sign]
      opts << "--sign"
    end
    if @verbose || @debug
      opts << "--verbose"
    end
    ::Dir.chdir(staging_path) do
      safesystem("lipkgen", *opts);
    end
    FileUtils.rm_rf(ipk) unless attributes[:debug?]
    
    # move file
    FileUtils.mv(::Dir["#{build_path}/*.ipk"].first, output_path)
  end # output

end

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

1
2
3
4
5
6



7
8
9
10
11
12
13
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
47
48
49
50
51
52
require "fpm/namespace"
require "fpm/package"
require "fpm/util"
require "fileutils"

class FPM::Package::NPM < FPM::Package



  # Flags '--foo' will be accessable  as attributes[:npm_foo]
  option "--bin", "NPM_EXECUTABLE",
    "The path to the npm executable you wish to run.", :default => "npm"

  option "--package-name-prefix", "PREFIX", "Name to prefix the package " \
    "name with.", :default => "node"

  option "--registry", "NPM_REGISTRY",
    "The npm registry to use instead of the default."

  private
  def input(package)
    # Notes:
    # * npm respects PREFIX
    settings = {
      "cache" => build_path("npm_cache"),
      "loglevel" => "warn",
      "global" => "true"
    }

    if attributes.include?(:npm_registry) && !attributes[:npm_registry].nil?
      settings["registry"] = attributes[:npm_registry]
    end


    if attributes.include?(:prefix) && !attributes[:prefix].nil?
      settings["prefix"] = staging_path(attributes[:prefix])
    else
      @logger.info("Setting default npm install prefix",
                   :prefix => "/usr/local")
      settings["prefix"] = staging_path("/usr/local")
    end

    FileUtils.mkdir_p(settings["prefix"])

    npm_flags = []
    settings.each do |key, value|
      # npm lets you specify settings in a .npmrc but the same key=value settings
      # are valid as '--key value' command arguments to npm. Woo!
      @logger.debug("Configuring npm", key => value)
      npm_flags += [ "--#{key}", value ]
    end

    install_args = [
      attributes[:npm_bin],
      "install",
      # use 'package' or 'package@version'






>
>
>




















<
|
<
>

<
|
<
<
<
<
<
<






|







1
2
3
4
5
6
7
8
9
10
11
12
13
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
47
require "fpm/namespace"
require "fpm/package"
require "fpm/util"
require "fileutils"

class FPM::Package::NPM < FPM::Package
  class << self
    include FPM::Util
  end
  # Flags '--foo' will be accessable  as attributes[:npm_foo]
  option "--bin", "NPM_EXECUTABLE",
    "The path to the npm executable you wish to run.", :default => "npm"

  option "--package-name-prefix", "PREFIX", "Name to prefix the package " \
    "name with.", :default => "node"

  option "--registry", "NPM_REGISTRY",
    "The npm registry to use instead of the default."

  private
  def input(package)
    # Notes:
    # * npm respects PREFIX
    settings = {
      "cache" => build_path("npm_cache"),
      "loglevel" => "warn",
      "global" => "true"
    }


    settings["registry"] = attributes[:npm_registry] if attributes[:npm_registry_given?]

    set_default_prefix unless attributes[:prefix_given?]


    settings["prefix"] = staging_path(attributes[:prefix])






    FileUtils.mkdir_p(settings["prefix"])

    npm_flags = []
    settings.each do |key, value|
      # npm lets you specify settings in a .npmrc but the same key=value settings
      # are valid as '--key value' command arguments to npm. Woo!
      logger.debug("Configuring npm", key => value)
      npm_flags += [ "--#{key}", value ]
    end

    install_args = [
      attributes[:npm_bin],
      "install",
      # use 'package' or 'package@version'
100
101
102
103
104
105
106
















107
108
109
    # any automatic dependency information since every 'npm install'
    # is fully self-contained. That's why you don't see any bother, yet,
    # to include the package's dependencies in here.
    #
    # It's possible someone will want to decouple that in the future,
    # but I will wait for that feature request.
  end

















  public(:input)
end # class FPM::Package::NPM







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



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
    # any automatic dependency information since every 'npm install'
    # is fully self-contained. That's why you don't see any bother, yet,
    # to include the package's dependencies in here.
    #
    # It's possible someone will want to decouple that in the future,
    # but I will wait for that feature request.
  end

  def set_default_prefix
    attributes[:prefix] = self.class.default_prefix
    attributes[:prefix_given?] = true
  end

  def self.default_prefix
    npm_prefix = safesystemout("npm", "prefix", "-g").chomp
    if npm_prefix.count("\n") > 0
      raise FPM::InvalidPackageConfiguration, "`npm prefix -g` returned unexpected output."
    elsif !File.directory?(npm_prefix)
      raise FPM::InvalidPackageConfiguration, "`npm prefix -g` returned a non-existent directory"
    end
    logger.info("Setting default npm install prefix", :prefix => npm_prefix)
    npm_prefix
  end

  public(:input)
end # class FPM::Package::NPM

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

Added lib/fpm/package/phar.rb.





































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162

# api: fpm
# title: PHP Phar target
# description: Chains to PHP for creating a .phar (native/tar/zip) archive
# type: package
# category: target
# version: 0.2
# state: very alpha
# license: MITL
# author: mario#include-once:org
# 
# This packaging target generates simple PHP Phar assemblies. With its
# default stub assuming `__init__.php` for CLI applications, and `index.php`
# as web router. A custom --phar-stub can be set of course.
#
# It honors the output filename, but alternatively allows `--phar-format`
# overriding the packaging format. It flexibly recognizes any concatenation
# of „phar·zip·tar“ with „gz·bz2“, case-insensitively.
#
#  ┌─────────────┬─────────────┬─────────────┬─────────────┬───────────────┐
#  │ Extension   │ Archive     │ Compression │ Envelope    │ Use           │
#  │ / Specifier │ Format      │ Per File    │ Compression │ Cases         │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ phar        │ Phar        │ -           │ -           │               │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ phar.gz     │ Phar        │ gzip        │ -           │ general       │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ phar·bz2    │ Phar        │ bzip2       │ -           │               │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ PHAZ        │ Phar        │ -           │ gzip        │               │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ -           │ Phar        │ gzip        │ gzip        │ (eschew)      │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ zip         │ Zip         │ -           │ -           │               │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ zip.gz      │ Zip         │ gzip        │ -           │ distribution  │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ zip…bz2     │ Zip         │ bzip2       │ -           │               │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ tar         │ Pax         │ -           │ -           │               │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ tgz         │ Pax         │ -           │ gzip        │               │
#  ├─────────────┼─────────────┼─────────────┼─────────────┼───────────────┤
#  │ tar+bz2     │ Pax         │ -           │ bzip2       │ data bundles  │
#  └─────────────┴─────────────┴─────────────┴─────────────┴───────────────┘
#
# ZIPs and TARs can be read by other languages, but contain PHP/phar-specific
# additions (.phar/stub and a signature).
#

require "fpm/package"
require "fpm/util"
require "fileutils"
require "json"

# Phar creation (output only)
class FPM::Package::Phar < FPM::Package

  option "--format", "PHAR.gz/TGZ/ZIP", "Phar package type and compression (append .gz/.bz2 for method)", :default=>"PHAR.GZ"
  option "--stub", "STUB.php", "Apply initialization/stub file", :default=>""
  #option "--nocase", :flag, "Lowercase filenames within archive", :default=>false
  option "--sign", "PEM_FILE", "Sign package with OpenSSL and key from .pem file", :default=>""

  # Invoke PHP for actual packaging process
  def output(output_path)

    # Flags
    o_nocase = attributes[:phar_nocase] || false
    o_stub = attributes[:phar_stub] || ""
    o_sign = attributes[:phar_sign] || ""
    
    # Retain package meta information, either from fpm attributes, or collected :attr hash (src module, formerly :meta)
    meta = attrs.merge({
      "id" => @name,
      "version" => @version.to_s,
      "epoch" => @epoch.to_s,
      "iteration" => @iteration.to_s,
      "architecture" => @architecture,
      "category" => @category == "none" ? nil : @category,
      "author" => @maintainer,
      "url" => @url,
      "license" => @license,
    }).delete_if{ |k,v| v.nil? || v==""}
    
    # Match format specifier/extension onto type/settings
    fmt = (attributes[:phar_format] + output_path).downcase
    fmt, enc = fmt.match(/zip|phaz|tar|t[gb]z|pz/).to_s||"phar", fmt.match(/gz|bz2/).to_s
    map2 = { "tgz" => ["tar", "gz"], "tbz" => ["tar", "bz2"], "pz" => ["phaz", ""]  }
    map = {
        # fmt,  enc          extension   format        per-file-gz   extns ← archive-compr
       ["phar", ""   ] => [ ".phar",     "Phar::PHAR", "Phar::NONE", "",     "" ],
       ["phar", "gz" ] => [ ".phar",     "Phar::PHAR", "Phar::GZ",   "",     "" ],
       ["phar", "bz2"] => [ ".phar",     "Phar::PHAR", "Phar::BZ2",  "",     "" ],
       ["zip",  ""   ] => [ ".phar.zip", "Phar::ZIP",  "Phar::NONE", "",     "" ],
       ["zip",  "gz" ] => [ ".phar.zip", "Phar::ZIP",  "Phar::GZ",   "",     "" ],
       ["zip", "bz2" ] => [ ".phar.zip", "Phar::ZIP",  "Phar::BZ2",  "",     "" ],
       ["tar",  ""   ] => [ ".phar.tar", "Phar::TAR",  "Phar::NONE", "",     "" ],
       ["tar",  "gz" ] => [ ".phar.tar", "Phar::TAR",  "Phar::GZ",   ".gz",  "(Phar::GZ)" ],
       ["tar", "bz2" ] => [ ".phar.tar", "Phar::TAR",  "Phar::BZ2",  ".bz2", "(Phar::BZ2)"],
       ["phaz", ""   ] => [ ".phar",     "Phar::PHAR", "Phar::GZ",   ".gz",  "(Phar::GZ)" ],
       ["phaz", "gz" ] => [ ".phar",     "Phar::PHAR", "Phar::GZ",   ".gz",  "(Phar::GZ)" ],
       ["phaz", "bz2"] => [ ".phar",     "Phar::PHAR", "Phar::GZ",   ".bz2", "(Phar::BZ2)"],
    }
    opt = map[[fmt,enc]] || map[map2[fmt]] || map[["phar", ""]]
    o_stdext, o_format, o_filegz, o_extout, o_hullgz = opt
    
    # Prepare output / temp filename
    output_check(output_path)
    tmp_phar = ::Dir::Tmpname.create(['_\$fpm_phar_', o_stdext]) { }

    # Have PHP generate the package
    code = <<-PHP
       #-- Create phar
       $p = new Phar('#{tmp_phar}', 0, '#{name}');
       $p->startBuffering();

       #-- Add files
       $p->buildFromDirectory('#{staging_path}');
       
       #-- Stub
       if (strlen('#{o_stub}') && file_exists('#{staging_path}/#{o_stub}')) {
          $p->setStub(file_get_contents('#{staging_path}/#{o_stub}'));
       }
       elseif (#{o_format} == Phar::PHAR) {
          $p->setDefaultStub("__init__.php", "index.php");
       }
       else {
          $p->setDefaultStub();
       }

       #-- Carry packaging info over as meta data (in particular for `fpm -s src` module)
       $p->setMetadata(json_decode($_SERVER["argv"][1]));

       #-- Per-file compression
       if (#{o_filegz}) {
          $p->compressFiles(#{o_filegz});
       }
       
       #-- Signature
       if (strlen('#{o_sign}')) {
          $p->setSignatureAlgorithm(Phar::OPENSSL, file_get_contents('#{o_sign}'));
       }
       else {
          $p->setSignatureAlgorithm(Phar::SHA256);
       }
              
       #-- Save all the things
       $p->stopBuffering();

       // Whole-archive compression; output goes to a different filename. (Cleaned up in Ruby...)
       if ("#{o_extout}") {
          $p->compress(#{o_hullgz});
       }
    PHP
    safesystem("php", "-dphar.readonly=0", "-derror_reporing=~0", "-ddisplay_errors=1", "-r", code, JSON.generate(meta))

    #-- but might end up with suffix, for whole-archive ->compress()ion
    FileUtils.mv(tmp_phar + o_extout, output_path)
    File.unlink(tmp_phar) if File.exists?(tmp_phar)
  end

end # class FPM::Package::Phar

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    "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
  option "--fix-dependencies", :flag, "Should the package dependencies be " \
    "prefixed?", :default => true

  option "--downcase-name", :flag, "Should the target package name be in " \
    "lowercase?", :default => true
  option "--downcase-dependencies", :flag, "Should the package dependencies " \
    "be in lowercase?", :default => true

  option "--install-bin", "BIN_PATH", "The path to where python scripts " \
    "should be installed to."
  option "--install-lib", "LIB_PATH", "The path to where python libs " \
    "should be installed to (default depends on your python installation). " \
    "Want to what your target platform is using? Run this: " \
    "python -c 'from distutils.sysconfig import get_python_lib; " \
    "print get_python_lib()'"
  option "--install-data", "DATA_PATH", "The path to where data should be " \
    "installed to. This is equivalent to 'python setup.py --install-data " \
    "DATA_PATH"
  option "--dependencies", :flag, "Include requirements defined in setup.py" \
    " as dependencies.", :default => true







|



















|







28
29
30
31
32
33
34
35
36
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
    "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
  option "--fix-dependencies", :flag, "Should the package dependencies be " \
    "prefixed?", :default => true

  option "--downcase-name", :flag, "Should the target package name be in " \
    "lowercase?", :default => true
  option "--downcase-dependencies", :flag, "Should the package dependencies " \
    "be in lowercase?", :default => true

  option "--install-bin", "BIN_PATH", "The path to where python scripts " \
    "should be installed to."
  option "--install-lib", "LIB_PATH", "The path to where python libs " \
    "should be installed to (default depends on your python installation). " \
    "Want to find out what your target platform is using? Run this: " \
    "python -c 'from distutils.sysconfig import get_python_lib; " \
    "print get_python_lib()'"
  option "--install-data", "DATA_PATH", "The path to where data should be " \
    "installed to. This is equivalent to 'python setup.py --install-data " \
    "DATA_PATH"
  option "--dependencies", :flag, "Include requirements defined in setup.py" \
    " as dependencies.", :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
137
    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-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
      raise "Unexpected directory layout after easy_install. Maybe file a bug? The directory is #{build_path}"







|


















|












|




|
|







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
137
    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
      raise "Unexpected directory layout after easy_install. Maybe file a bug? The directory is #{build_path}"
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.

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    "sha256" => 8,
    "sha384" => 9,
    "sha512" => 10
  } unless defined?(DIGEST_ALGORITHM_MAP)

  COMPRESSION_MAP = {
    "none" => "w0.gzdio",
    "xz" => "w2.xzdio",
    "gzip" => "w9.gzdio",
    "bzip2" => "w9.bzdio"
  } unless defined?(COMPRESSION_MAP)

  option "--use-file-permissions", :flag, 
      "Use existing file permissions when defining ownership and modes."








|







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    "sha256" => 8,
    "sha384" => 9,
    "sha512" => 10
  } unless defined?(DIGEST_ALGORITHM_MAP)

  COMPRESSION_MAP = {
    "none" => "w0.gzdio",
    "xz" => "w9.xzdio",
    "gzip" => "w9.gzdio",
    "bzip2" => "w9.bzdio"
  } unless defined?(COMPRESSION_MAP)

  option "--use-file-permissions", :flag, 
      "Use existing file permissions when defining ownership and modes."

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
137
138
139
140
141
142
143
144
145
  option "--auto-add-exclude-directories", "DIRECTORIES",
    "Additional directories ignored by '--rpm-auto-add-directories' flag",
    :multivalued => true, :attribute_name => :auto_add_exclude_directories

  option "--autoreqprov", :flag, "Enable RPM's AutoReqProv option"
  option "--autoreq", :flag, "Enable RPM's AutoReq option"
  option "--autoprov", :flag, "Enable RPM's AutoProv option"










  rpmbuild_filter_from_provides = []
  option "--filter-from-provides", "REGEX",
    "Set %filter_from_provides to the supplied REGEX." do |filter_from_provides|
    rpmbuild_filter_from_provides << filter_from_provides
    next rpmbuild_filter_from_provides
  end
  rpmbuild_filter_from_requires = []
  option "--filter-from-requires", "REGEX",
    "Set %filter_from_requires to the supplied REGEX." do |filter_from_requires|
    rpmbuild_filter_from_requires << filter_from_requires
    next rpmbuild_filter_from_requires
  end

  option "--ignore-iteration-in-dependencies", :flag,
            "For '=' (equal) dependencies, allow iterations on the specified " \
            "version. Default is to be specific. This option allows the same " \
            "version of a package but any iteration is permitted"


































  private

  # Fix path name
  # Replace [ with [\[] to make rpm not use globs
  # Replace * with [*] to make rpm not use globs
  # Replace ? with [?] to make rpm not use globs
  # Replace % with [%] to make rpm not expand macros
  def rpm_fix_name(name)
    name = "\"#{name}\"" if name[/\s/]
    name = name.gsub("[", "[\\[]")
    name = name.gsub("*", "[*]")
    name = name.gsub("?", "[?]")
    name = name.gsub("%", "[%]")
  end

  def rpm_file_entry(file)
    original_file = file
    file = rpm_fix_name(file)

    return file unless attributes[:rpm_use_file_permissions?]










    # Stat the original filename in the relative staging path
    ::Dir.chdir(staging_path) do
      stat = File.stat(original_file.gsub(/\"/, '').sub(/^\//,''))

      # rpm_user and rpm_group attribute should override file ownership
      # otherwise use the current file user/group by name.
      user = attributes[:rpm_user] || Etc.getpwuid(stat.uid).name
      group = attributes[:rpm_group] || Etc.getgrgid(stat.gid).name
      mode = stat.mode
      return sprintf("%%attr(%o, %s, %s) %s\n", mode & 4095 , user, group, file)







>
>
>
>
>
>
>
>
>



















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

|
















>
|
>
>
>
>
>
>
>
>
>



|







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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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
  option "--auto-add-exclude-directories", "DIRECTORIES",
    "Additional directories ignored by '--rpm-auto-add-directories' flag",
    :multivalued => true, :attribute_name => :auto_add_exclude_directories

  option "--autoreqprov", :flag, "Enable RPM's AutoReqProv option"
  option "--autoreq", :flag, "Enable RPM's AutoReq option"
  option "--autoprov", :flag, "Enable RPM's AutoProv option"

  option "--attr", "ATTRFILE",
    "Set the attribute for a file (%attr).",
    :multivalued => true, :attribute_name => :attrs
  
  option "--init", "FILEPATH", "Add FILEPATH as an init script",
	:multivalued => true do |file|
    next File.expand_path(file)
  end

  rpmbuild_filter_from_provides = []
  option "--filter-from-provides", "REGEX",
    "Set %filter_from_provides to the supplied REGEX." do |filter_from_provides|
    rpmbuild_filter_from_provides << filter_from_provides
    next rpmbuild_filter_from_provides
  end
  rpmbuild_filter_from_requires = []
  option "--filter-from-requires", "REGEX",
    "Set %filter_from_requires to the supplied REGEX." do |filter_from_requires|
    rpmbuild_filter_from_requires << filter_from_requires
    next rpmbuild_filter_from_requires
  end

  option "--ignore-iteration-in-dependencies", :flag,
            "For '=' (equal) dependencies, allow iterations on the specified " \
            "version. Default is to be specific. This option allows the same " \
            "version of a package but any iteration is permitted"

  option "--verbatim-gem-dependencies", :flag,
           "When converting from a gem, leave the old (fpm 0.4.x) style " \
           "dependency names. This flag will use the old 'rubygem-foo' " \
           "names in rpm requires instead of the redhat style " \
           "rubygem(foo).", :default => false

  option "--verifyscript", "FILE",
    "a script to be run on verification" do |val|
    File.expand_path(val) # Get the full path to the script
  end # --verifyscript
  option "--pretrans", "FILE",
    "pretrans script" do |val|
    File.expand_path(val) # Get the full path to the script
  end # --pretrans
  option "--posttrans", "FILE",
    "posttrans script" do |val|
    File.expand_path(val) # Get the full path to the script
  end # --posttrans

  ["before-install","after-install","before-uninstall","after-target-uninstall"].each do |trigger_type|
     rpm_trigger = []
     option "--trigger-#{trigger_type}", "'[OPT]PACKAGE: FILEPATH'", "Adds a rpm trigger script located in FILEPATH, " \
            "having 'OPT' options and linking to 'PACKAGE'. PACKAGE can be a comma seperated list of packages. " \
            "See: http://rpm.org/api/4.4.2.2/triggers.html" do |trigger|
       match = trigger.match(/^(\[.*\]|)(.*): (.*)$/)
       @logger.fatal("Trigger '#{trigger_type}' definition can't be parsed ('#{trigger}')") unless match
       opt, pkg, file = match.captures
       @logger.fatal("File given for --trigger-#{trigger_type} does not exist (#{file})") unless File.exists?(file)
       rpm_trigger << [pkg, File.read(file), opt.tr('[]','')]
       next rpm_trigger
     end
   end
 
  private
    
  # Fix path name
  # Replace [ with [\[] to make rpm not use globs
  # Replace * with [*] to make rpm not use globs
  # Replace ? with [?] to make rpm not use globs
  # Replace % with [%] to make rpm not expand macros
  def rpm_fix_name(name)
    name = "\"#{name}\"" if name[/\s/]
    name = name.gsub("[", "[\\[]")
    name = name.gsub("*", "[*]")
    name = name.gsub("?", "[?]")
    name = name.gsub("%", "[%]")
  end

  def rpm_file_entry(file)
    original_file = file
    file = rpm_fix_name(file)

    if !attributes[:rpm_use_file_permissions?]

      if attrs[file].nil?
        return file
      else
        return sprintf("%%attr(%s) %s\n", attrs[file], file)
      end
    end

    return sprintf("%%attr(%s) %s\n", attrs[file], file) unless attrs[file].nil?

    # Stat the original filename in the relative staging path
    ::Dir.chdir(staging_path) do
      stat = File.lstat(original_file.gsub(/\"/, '').sub(/^\//,''))

      # rpm_user and rpm_group attribute should override file ownership
      # otherwise use the current file user/group by name.
      user = attributes[:rpm_user] || Etc.getpwuid(stat.uid).name
      group = attributes[:rpm_group] || Etc.getgrgid(stat.gid).name
      mode = stat.mode
      return sprintf("%%attr(%o, %s, %s) %s\n", mode & 4095 , user, group, file)
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
198
199
200
201

202
203
204
205
206
207
208

209
210
211
212
213
214
215
  def iteration
    return @iteration ? @iteration : 1
  end # def iteration

  # See FPM::Package#converted_from
  def converted_from(origin)
    if origin == FPM::Package::Gem
      # Gem dependency operator "~>" is not compatible with rpm. Translate any found.
      fixed_deps = []
      self.dependencies.collect do |dep|
        name, op, version = dep.split(/\s+/)
        if op == "~>"
          # ~> x.y means: > x.y and < (x+1).0
          fixed_deps << "#{name} >= #{version}"
          fixed_deps << "#{name} < #{version.to_i + 1}.0.0"
        else

          fixed_deps << dep
        end
      end
      self.dependencies = fixed_deps

      # Convert 'rubygem-foo' provides values to 'rubygem(foo)'
      # since that's what most rpm packagers seem to do.
      self.provides = self.provides.collect do |provides|
        # Tries to match rubygem_prefix [1], gem_name [2] and version [3] if present
        # and return it in rubygem_prefix(gem_name) form
        if name=/^(#{attributes[:gem_package_name_prefix]})-([^\s]+)\s*(.*)$/.match(provides)
          "#{name[1]}(#{name[2]})#{name[3] ? " #{name[3]}" : ""}"
        else
          provides
        end
      end

      self.dependencies = self.dependencies.collect do |dependency|
        # Tries to match rubygem_prefix [1], gem_name [2] and version [3] if present
        # and return it in rubygem_prefix(gem_name) form
        if name=/^(#{attributes[:gem_package_name_prefix]})-([^\s]+)\s*(.*)$/.match(dependency)
          "#{name[1]}(#{name[2]})#{name[3] ? " #{name[3]}" : ""}"
        else
          dependency

        end
      end
    end

    # Convert != dependency as Conflict =, as rpm doesn't understand !=
    self.dependencies = self.dependencies.select do |dep|
      name, op, version = dep.split(/\s+/)







<


<
<
<
<
<
<
>
|
<














>
|
|
|
|
|
|
|
>







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
  def iteration
    return @iteration ? @iteration : 1
  end # def iteration

  # See FPM::Package#converted_from
  def converted_from(origin)
    if origin == FPM::Package::Gem

      fixed_deps = []
      self.dependencies.collect do |dep|






        # Gem dependency operator "~>" is not compatible with rpm. Translate any found.
        fixed_deps = fixed_deps + expand_pessimistic_constraints(dep)

      end
      self.dependencies = fixed_deps

      # Convert 'rubygem-foo' provides values to 'rubygem(foo)'
      # since that's what most rpm packagers seem to do.
      self.provides = self.provides.collect do |provides|
        # Tries to match rubygem_prefix [1], gem_name [2] and version [3] if present
        # and return it in rubygem_prefix(gem_name) form
        if name=/^(#{attributes[:gem_package_name_prefix]})-([^\s]+)\s*(.*)$/.match(provides)
          "#{name[1]}(#{name[2]})#{name[3] ? " #{name[3]}" : ""}"
        else
          provides
        end
      end
      if !self.attributes[:rpm_verbatim_gem_dependencies?]
        self.dependencies = self.dependencies.collect do |dependency|
          # Tries to match rubygem_prefix [1], gem_name [2] and version [3] if present
          # and return it in rubygem_prefix(gem_name) form
          if name=/^(#{attributes[:gem_package_name_prefix]})-([^\s]+)\s*(.*)$/.match(dependency)
            "#{name[1]}(#{name[2]})#{name[3] ? " #{name[3]}" : ""}"
          else
            dependency
          end
        end
      end
    end

    # Convert != dependency as Conflict =, as rpm doesn't understand !=
    self.dependencies = self.dependencies.select do |dep|
      name, op, version = dep.split(/\s+/)
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240















241















242
243
244
245
246
247
248
    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
















  end # def converted
















  def input(path)
    rpm = ::RPM::File.new(path)

    tags = {}
    rpm.header.tags.each do |tag|
      tags[tag.tag] = tag.value







|







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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
325
    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)
  setscript.call(:rpm_posttrans)
  setscript.call(:rpm_pretrans)
  end # def converted

  def rpm_get_trigger_type(flag)
    puts "#{flag.to_s(2)}"
    if (flag & (1 << 25)) == (1 << 25)
       :rpm_trigger_before_install
    elsif (flag & (1 << 16)) == (1 << 16)
       :rpm_trigger_after_install
    elsif (flag & (1 << 17)) == (1 << 17)
       :rpm_trigger_before_uninstall
    elsif (flag & (1 << 18)) == (1 << 18)
       :rpm_trigger_after_target_uninstall
    else
       @logger.fatal("I don't know about this triggerflag ('#{flag}')")
    end
  end # def rpm_get_trigger

  def input(path)
    rpm = ::RPM::File.new(path)

    tags = {}
    rpm.header.tags.each do |tag|
      tags[tag.tag] = tag.value
260
261
262
263
264
265
266



267
268
269
270
271












272
273
274
275
276
277
278
    self.vendor = tags[:vendor]
    self.version = tags[:version]

    self.scripts[:before_install] = tags[:prein]
    self.scripts[:after_install] = tags[:postin]
    self.scripts[:before_remove] = tags[:preun]
    self.scripts[:after_remove] = tags[:postun]



    # TODO(sissel): prefix these scripts above with a shebang line if there isn't one?
    # Also taking into account the value of tags[preinprog] etc, something like:
    #    #!#{tags[:preinprog]}
    #    #{tags[prein]}
    # TODO(sissel): put 'trigger scripts' stuff into attributes













    if !attributes[:no_auto_depends?]
      self.dependencies += rpm.requires.collect do |name, operator, version|
        [name, operator, version].join(" ")
      end
    end








>
>
>




|
>
>
>
>
>
>
>
>
>
>
>
>







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
367
368
369
370
    self.vendor = tags[:vendor]
    self.version = tags[:version]

    self.scripts[:before_install] = tags[:prein]
    self.scripts[:after_install] = tags[:postin]
    self.scripts[:before_remove] = tags[:preun]
    self.scripts[:after_remove] = tags[:postun]
    self.scripts[:rpm_verifyscript] = tags[:verifyscript]
    self.scripts[:rpm_posttrans] = tags[:posttrans]
    self.scripts[:rpm_pretrans] = tags[:pretrans]
    # TODO(sissel): prefix these scripts above with a shebang line if there isn't one?
    # Also taking into account the value of tags[preinprog] etc, something like:
    #    #!#{tags[:preinprog]}
    #    #{tags[prein]}

    if !tags[:triggerindex].nil?
      val = tags[:triggerindex].zip(tags[:triggername],tags[:triggerflags],tags[:triggerversion]).group_by{ |x| x[0]}
      val = val.collect do |order,data|
        new_data = data.collect { |x| [ x[1], rpm.operator(x[2]), x[3]].join(" ").strip}.join(", ")
        [order, rpm_get_trigger_type(data[0][2]), new_data]
      end
      val.each do |order, attr,data|
        self.attributes[attr] = [] if self.attributes[attr].nil?
        scriptprog = (tags[:triggerscriptprog][order] == '/bin/sh') ? "" : "-p #{tags[:triggerscriptprog][order]}"
        self.attributes[attr] << [data,tags[:triggerscripts][order],scriptprog]
      end
    end

    if !attributes[:no_auto_depends?]
      self.dependencies += rpm.requires.collect do |name, operator, version|
        [name, operator, version].join(" ")
      end
    end

305
306
307
308
309
310
311
312
313
314
315
316
317




318
319
320
321
322
323
324
325

326
327
328
329
330
331
332

  def output(output_path)
    output_check(output_path)
    %w(BUILD RPMS SRPMS SOURCES SPECS).each { |d| FileUtils.mkdir_p(build_path(d)) }
    args = ["rpmbuild", "-bb"]

    if %x{uname -m}.chomp != self.architecture
      args += [ '--target', self.architecture ]
    end

    # issue #309
    if !attributes[:rpm_os].nil?
      rpm_target = "#{architecture}-unknown-#{attributes[:rpm_os]}"




      args += ["--target", rpm_target]
    end

    args += [
      "--define", "buildroot #{build_path}/BUILD",
      "--define", "_topdir #{build_path}",
      "--define", "_sourcedir #{build_path}",
      "--define", "_rpmdir #{build_path}/RPMS",

    ]

    args += ["--sign"] if attributes[:rpm_sign?]

    if attributes[:rpm_auto_add_directories?]
      fs_dirs_list = File.join(template_dir, "rpm", "filesystem_list")
      fs_dirs = File.readlines(fs_dirs_list).reject { |x| x =~ /^\s*#/}.map { |x| x.chomp }







|





>
>
>
>








>







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

  def output(output_path)
    output_check(output_path)
    %w(BUILD RPMS SRPMS SOURCES SPECS).each { |d| FileUtils.mkdir_p(build_path(d)) }
    args = ["rpmbuild", "-bb"]

    if %x{uname -m}.chomp != self.architecture
      rpm_target = self.architecture
    end

    # issue #309
    if !attributes[:rpm_os].nil?
      rpm_target = "#{architecture}-unknown-#{attributes[:rpm_os]}"
    end

    # issue #707
    if !rpm_target.nil?
      args += ["--target", rpm_target]
    end

    args += [
      "--define", "buildroot #{build_path}/BUILD",
      "--define", "_topdir #{build_path}",
      "--define", "_sourcedir #{build_path}",
      "--define", "_rpmdir #{build_path}/RPMS",
      "--define", "_tmppath #{attributes[:workdir]}"
    ]

    args += ["--sign"] if attributes[:rpm_sign?]

    if attributes[:rpm_auto_add_directories?]
      fs_dirs_list = File.join(template_dir, "rpm", "filesystem_list")
      fs_dirs = File.readlines(fs_dirs_list).reject { |x| x =~ /^\s*#/}.map { |x| x.chomp }
360
361
362
363
364
365
366









367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
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
      Find.find(cfg_path) do |p|
        allconfigs << p.gsub("#{staging_path}/", '') if File.file? p
      end
    end
    allconfigs.sort!.uniq!

    self.config_files = allconfigs.map { |x| File.join("/", x) }










    (attributes[:rpm_rpmbuild_define] or []).each do |define|
      args += ["--define", define]
    end

    # copy all files from staging to BUILD dir
    Find.find(staging_path) do |path|
      src = path.gsub(/^#{staging_path}/, '')
      dst = File.join(build_path, build_sub_dir, src)
      copy_entry(path, dst)
    end

    rpmspec = template("rpm.erb").result(binding)
    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)







>
>
>
>
>
>
>
>
>




















|



















|












|







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
521
522
523
524
525
526
527
528
529
530
531
532
533
      Find.find(cfg_path) do |p|
        allconfigs << p.gsub("#{staging_path}/", '') if File.file? p
      end
    end
    allconfigs.sort!.uniq!

    self.config_files = allconfigs.map { |x| File.join("/", x) }

    # add init script if present
    (attributes[:rpm_init_list] or []).each do |init|
      name = File.basename(init, ".init")
      dest_init = File.join(staging_path, "etc/init.d/#{name}")
      FileUtils.mkdir_p(File.dirname(dest_init))
      FileUtils.cp init, dest_init
      File.chmod(0755, dest_init)
    end

    (attributes[:rpm_rpmbuild_define] or []).each do |define|
      args += ["--define", define]
    end

    # copy all files from staging to BUILD dir
    Find.find(staging_path) do |path|
      src = path.gsub(/^#{staging_path}/, '')
      dst = File.join(build_path, build_sub_dir, src)
      copy_entry(path, dst)
    end

    rpmspec = template("rpm.erb").result(binding)
    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",

Added lib/fpm/package/src.rb.











































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
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
# encoding: ascii
# api: fpm
# title: generic script source
# description: Utilizes `pack:` specifier from meta/comment block in script files
# type: package
# category: source
# version: 0.7
# state: beta
# architecture: all
# license: MITL
# author: mario#include-once:org
# config: <arg name="src-only" description="Only apply main pack: spec, do not recurse.">
# depends:
# pack: src.rb, README=README.txt, src/*.png
# 
# The "src" input is intended for packaging scripting language files.
# A top-level comment can hold per-file description fields, where the
# `pack:` lines simply reference other files to recurse and include.
#
#  → Other documentation/meta fields from the origin file are used as
#    package attributes (fpm --flag values still take precedence).
#
#  → The pack: line gives a simple comma-separated list of other scripts
#    or files to include.
#
#    · For instance `pack: src.rb, readme.txt, install.sh` will package
#      those files together with the main script.
#
#    · RECURSION: referenced files can itself specify a `pack:` line
#      onto other scripts/source/binary files.
#
#    · With `pack: m.py=main.py, b=b.txt` files can be renamed.
#
#    · Glob matching is also possible `pack: plugins/*.php`
#      Limited glob-rewriting via `doc/*.txt=manual/` even.
#      It's sort of working, use with care.
#
#    · A file can even exclude itself with `pack: empty.rb=`
#
#    Binary files can be referenced from a text/source file of course.
#    
#    Alternatively a concise spec.txt can be crafted to hold default
#    packaging settings for fpm. Which is a nice alternative to a "dir"
#    source or --inputs list for small projects.
#
#  → `depends:` lines are not yet used for packaging. They aren't
#    transcribed into system package (rpm/deb) control fields either.
#    (They're mostly intended for in-application plugin management.)
#

require "fpm/package"
require "fpm/util"
require "backports"
require "fileutils"
require "find"
require "socket"
require "pathname"

# Source package.
#
# Reads in the meta data comment block from the first specified file,
# recursively includes all pack:-mentioned scripts, while honoring
# src=dest filename specifiers.
#
class FPM::Package::Src < FPM::Package  # inheriting from ::Dir doesn't work
  
  option "--depends", :flag, "Traverse source files as mentioned in depends: field.", :default => false
  option "--only", :flag, "Only apply origin files pack: directive, do not recurse.", :default => false

  
  # Start from first specified source file.
  def input(path)
  
    # The init/main file should contain usable package meta fields
    if m = get_meta(path)
      # copy attributes over, if not present/overriden
      @name = m["id"] if not @name
      @version = m["version"] if not @version
      @epoch = m["epoch"] if not @epoch
      @architecture = m["architecture"] if not attributes[:architecture_given?]
      @description = "#{m['description']}\n#{m['comment']}" if not attributes[:description_given?]
      @url = m["url"] || m["homepage"] if not attributes[:url_given?]
      @category = m["category"] if not attributes[:category_given?]
      @priority = m["priority"] if not attributes[:priority_given?]
      @license = m["license"] if not attributes[:license_given?]
      @vendor = m["author"] if not attributes[:maintainer_given?]
      @maintainer = m["author"] if not attributes[:maintainer_given?]
      # retain all available attributes (formerly :meta, now in shared :attrs)
      @attrs.merge!(m)
          #@todo preparse and collect config: structures
    end

    # Assemble rewrite map
    # {
    #   rel/src1 => { src2=>dest, src3=>dest } ,
    #   rel/src2 => { src4=> .. }
    # }
    @map = {:spec_start => {path => path}}
    ::Dir.chdir(attributes[:chdir] || ".") do
      rewrite_map(path)
      logger.debug(@map)
#p @map      
      # Copy all referenced files.
      copied = {}
      @map.each do |from,pack|
        pack.each do |src,dest|
          # Each file can hold a primary declaration and override its target filename / make itself absent.
          if dest and @map[dest] and @map[dest][dest]
            dest = @map[dest][dest]
          end
          # References above the src/.. would also cross the staging_dir, give warning
          if dest and dest.match(/^\.\.\//)
            logger.warn("Referencing above ../ the basedir from #{from}")
          end
          # Else just apply pack{} map copying verbatim, possibly overwriting or duplicating files.
          if not copied[src+">"+dest]
#p "cp  #{src} to /#{dest}"
            real_copy("#{src}", "#{staging_path}/#{attributes[:prefix]}/#{dest}") if dest != ""
            copied[src+">"+dest] = 1
          end
        end
      end
    end
  end


  protected

  
  # Scan files for file,src=dest mapping lists.
  #
  # Each source file can specify references as `pack: two.sh, sub/three.py`
  # where each scripts´ subdirectory becomes the next base directory.
  #
  # The `dest` parameter is carried over from the previous old=new
  # filename pack{} map.
  #
  def rewrite_map(from, dest="")

    # lazy workaround to prevent neverending loops/references
    return if @map[from]
    
    # check if file is present, get meta data
    if not File.exists?(from)
      logger.warn("Skipping non-existent #{from} file")
      return
    elsif File.directory?(from)
      @map[from] = {}
      return
    else 
      packlist = get_meta(from)["pack"]  # adding [splitfn(from)[1]] itself would override renames
    end
    
    # relative dir and basename
    dir, fn = splitfn(from)   # fpm/ src.rb

    # @example
    #   from = fpm/src.r
    #          `pack: exe.rb, ../README, foo=bar, test/*`
    # @result
    #   pack[fpm/src.rb] = fpm/exe.rb=>fpm/exe.rb, README=>README, fpm/foo=>fpm/bar, fpm/test/123=>...}

    # iterate over listed references
    @map[from] = pack = {}
    packlist.each do |fnspec|

      # specifier can be one of: `src, src=dest, src*, dir/src*=dest/`
      src, dest = fnspec.split("=", 2) # dest can be nil (=use src basename), or empty "" (=skip file)

      # glob expansion      
      if src.match(/[\[\{\*\?]/)
        src = ::Dir.glob(dir + src)
        logger.warn("Nothing matched for '#{fnspec}' in '#{from}'") if src.empty?
      else
        src = [dir + src]
      end

      # combine with dest, and recursively scan each referenced source file
      src.each do |src|
        pack[consolidate(src)] = consolidate(dest ? dir+dest : src)
        rewrite_map(consolidate(src)) if not @attributes[:src_only?]
      end

    end
  end # def rewrite_map


  # split dir/name/ from basename 
  def splitfn(path)
    path =~ /\A  ((?: .*\/ )?)  ([^\/]+)  \Z/x
    return [$1, $2]
  end

  
  # remove ./ and dir/../ segments
  def consolidate(path)
    path = path.gsub(/(?<=^|\/)\.\//, "")    # strip out "./" self-referenting dirs
    path = path.gsub(/(?<=^|\/)(?!\.\.\/)[\w.-]+\/\.\.\//, "")    # consolidate "sub/../" relative paths
  end

  
  # Extract meta: key/values from source file
  def get_meta(path)

    # read file (first 8K would suffice)
    src = File.read(path, 1<<13, 0, :encoding=>'ASCII-8BIT')
    fields = { "pack" => "" }

    # extract first comment block,
    if src and src =~ /( \/\*+ .+? \*\/ | (?:[ \t]* \* | \#(?!!) [^\n]*\n)+ )/mix
      src = $1.gsub(/^[ \t\*\/\#]+/, "").to_s # does not honor indentation

      # split meta block from comment (on first empty line)
      src, fields["comment"] = src.split(/\r?\n\r?\n/, 2)  # eh, Ruby, Y U NO PROVIDE \R ?
      # split key: value fields
      fields.merge!(Hash[
         src.to_s.scan(/^([\w-]+): [[:blank:]]* ([^\n]* (?:\n (?![\w-]+:) [^\n]+)* )/x).map{ |k,v| [k.downcase, v] }
      ])

      # use input basename or id: field as package name
      fields["id"] = fields["id"] || path.match(/([^\/]+?)(\.\w+)?\Z/)[1]
      # split up pack: comma-separated list, don't expand src=dest yet
#p fields
      fields["pack"] = fields["pack"].split(/(?<!\\)[,\s]+/)
    else 
      fields["pack"] = []
    end
    return fields
  end # def meta


  # automatically recurses for and creates subdirectories when copying
  def real_copy(src, dest)
    if dest[-1] == "/"  # trailing slash indicates target dir
      dest += splitfn(src)[1]   # therefore copy src basename
    end
    if File.exists?(src)
      FileUtils.mkdir_p(File.dirname(dest))
      FileUtils.cp_r(src, dest)
    else
      logger.info("'#{src}' still missing, eh?")
    end
  end

end # class FPM::Package::Src

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

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

  # Output a tarball.
  #
  # If the output path ends predictably (like in .tar.gz) it will try to obey
  # the compression type.
  def output(output_path)
    output_check(output_path)
    # Unpack the tarball to the staging path
    args = ["-cf", output_path, "-C", staging_path]
    with(tar_compression_flag(output_path)) do |flag|
      args << flag unless flag.nil?
    end
    args << "."


    safesystem("tar", *args)

  end # def output

  # Generate the proper tar flags based on the path name.
  def tar_compression_flag(path)
    case path
      when /\.tar\.bz2$/
        return "-j"
      when /\.tar\.gz$|\.tgz$/
        return "-z"
      when /\.tar\.xz$/
        return "-J"












      else
        return nil
    end
  end # def tar_compression_flag
end # class FPM::Package::Tar







|
<
|
<
<
|

>
|
>





|
|

|
|
|
>
>
>
>
>
>
>
>
>
>
>
>

|



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

  # Output a tarball.
  #
  # If the output path ends predictably (like in .tar.gz) it will try to obey
  # the compression type.
  def output(output_path)
    output_check(output_path)
    # Pack tarball in the staging path

    args = tar_compression_flag(output_path).compact \


         + [File.absolute_path(output_path), "."]

    ::Dir::chdir(staging_path) do
      safesystem(*args)
    end
  end # def output

  # Generate the proper tar flags based on the path name.
  def tar_compression_flag(path)
    case path
      when /\.tar\.bz2$|\.tbz2$/
        return ["tar", "-cjf"]
      when /\.tar\.gz$|\.tgz$/
        return ["tar", "-czf"]
      when /\.tar\.xz$|\.txz$/
        return ["tar", "-cJf"]
      when /\.pax$/
        return ["pax", "-wf"]
      when /\.pax\.gz$/
        return ["pax", "-wzf"]
      when /\.pax\.xz$/
        return ["pax", "-wJf"]
      when /\.pax\.bz2$/
        return ["pax", "-wjf"]
      when /\.cpio$/
        return ["pax", "-x" "cpio", "-wf"]
      when /\.cpio.gz$/
        return ["pax", "-x" "cpio", "-wzf"]
      else
        return ["tar", "-cf"]
    end
  end # def tar_compression_flag
end # class FPM::Package::Tar

Changes to lib/fpm/package/zip.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
  end # def input

  # Output a tarball.
  #
  # If the output path ends predictably (like in .tar.gz) it will try to obey
  # the compression type.
  def output(output_path)
    output_check(output_path)
    

    files = Find.find(staging_path).to_a
    safesystem("zip", output_path, *files)

  end # def output

  # Generate the proper tar flags based on the path name.
  def tar_compression_flag(path)
    case path
      when /\.tar\.bz2$/
        return "-j"
      when /\.tar\.gz$|\.tgz$/
        return "-z"
      when /\.tar\.xz$/
        return "-J"
      else
        return nil
    end
  end # def tar_compression_flag
end # class FPM::Package::Tar







|
|
>
|
|
>


<
<
<
<
<
<
<
<
<
<
<
<
<
|
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51













52
  end # def input

  # Output a tarball.
  #
  # If the output path ends predictably (like in .tar.gz) it will try to obey
  # the compression type.
  def output(output_path)
    output_check( output_path = File.absolute_path(output_path) )

    ::Dir.chdir(staging_path) do
      files = Find.find(".").to_a
      safesystem("zip", output_path, *files)
    end
  end # def output














end # class FPM::Package::Zip

Changes to lib/fpm/util.rb.

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
    end
    program = args[0]

    if !program_exists?(program)
      raise ExecutableNotFound.new(program)
    end

    @logger.debug("Running command", :args => args)

    # Create a pair of pipes to connect the
    # invoked process to the cabin logger
    stdout_r, stdout_w = IO.pipe
    stderr_r, stderr_w = IO.pipe

    process           = ChildProcess.build(*args)
    process.io.stdout = stdout_w
    process.io.stderr = stderr_w

    process.start
    stdout_w.close; stderr_w.close
    @logger.debug('Process is running', :pid => process.pid)
    # Log both stdout and stderr as 'info' because nobody uses stderr for
    # actually reporting errors and as a result 'stderr' is a misnomer.
    @logger.pipe(stdout_r => :info, stderr_r => :info)

    process.wait
    success = (process.exit_code == 0)

    if !success
      raise ProcessFailed.new("#{program} failed (exit code #{process.exit_code})" \
                              ". Full command was:#{args.inspect}")







|












|


|







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
    end
    program = args[0]

    if !program_exists?(program)
      raise ExecutableNotFound.new(program)
    end

    logger.debug("Running command", :args => args)

    # Create a pair of pipes to connect the
    # invoked process to the cabin logger
    stdout_r, stdout_w = IO.pipe
    stderr_r, stderr_w = IO.pipe

    process           = ChildProcess.build(*args)
    process.io.stdout = stdout_w
    process.io.stderr = stderr_w

    process.start
    stdout_w.close; stderr_w.close
    logger.debug('Process is running', :pid => process.pid)
    # Log both stdout and stderr as 'info' because nobody uses stderr for
    # actually reporting errors and as a result 'stderr' is a misnomer.
    logger.pipe(stdout_r => :info, stderr_r => :info)

    process.wait
    success = (process.exit_code == 0)

    if !success
      raise ProcessFailed.new("#{program} failed (exit code #{process.exit_code})" \
                              ". Full command was:#{args.inspect}")
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
    end
    program = args[0]

    if !program.include?("/") and !program_in_path?(program)
      raise ExecutableNotFound.new(program)
    end

    @logger.debug("Running command", :args => args)

    stdout_r, stdout_w = IO.pipe
    stderr_r, stderr_w = IO.pipe

    process           = ChildProcess.build(*args)
    process.io.stdout = stdout_w
    process.io.stderr = stderr_w

    process.start
    stdout_w.close; stderr_w.close
    stdout_r_str = stdout_r.read
    stdout_r.close; stderr_r.close
    @logger.debug("Process is running", :pid => process.pid)

    process.wait
    success = (process.exit_code == 0)

    if !success
      raise ProcessFailed.new("#{program} failed (exit code #{process.exit_code})" \
                              ". Full command was:#{args.inspect}")







|












|







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
    end
    program = args[0]

    if !program.include?("/") and !program_in_path?(program)
      raise ExecutableNotFound.new(program)
    end

    logger.debug("Running command", :args => args)

    stdout_r, stdout_w = IO.pipe
    stderr_r, stderr_w = IO.pipe

    process           = ChildProcess.build(*args)
    process.io.stdout = stdout_w
    process.io.stderr = stderr_w

    process.start
    stdout_w.close; stderr_w.close
    stdout_r_str = stdout_r.read
    stdout_r.close; stderr_r.close
    logger.debug("Process is running", :pid => process.pid)

    process.wait
    success = (process.exit_code == 0)

    if !success
      raise ProcessFailed.new("#{program} failed (exit code #{process.exit_code})" \
                              ". Full command was:#{args.inspect}")
187
188
189
190
191
192
193




































194
    #   pkg = FPM::Package::Dir...
    #   pkg.output()...
    #   pkg.output()...
    # The 2nd output call will fail or behave weirdly because @copied_entries
    # is already populated. even though this is anew round of copying.
    return @copied_entries ||= {}
  end # def copied_entries




































end # module FPM::Util







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

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
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
    #   pkg = FPM::Package::Dir...
    #   pkg.output()...
    #   pkg.output()...
    # The 2nd output call will fail or behave weirdly because @copied_entries
    # is already populated. even though this is anew round of copying.
    return @copied_entries ||= {}
  end # def copied_entries

  def expand_pessimistic_constraints(constraint)
    name, op, version = constraint.split(/\s+/)

    if op == '~>'

      new_lower_constraint = "#{name} >= #{version}"

      version_components = version.split('.').collect { |v| v.to_i }

      version_prefix = version_components[0..-3].join('.')
      portion_to_work_with = version_components.last(2)

      prefix = ''
      unless version_prefix.empty?
        prefix = version_prefix + '.'
      end

      one_to_increment = portion_to_work_with[0].to_i
      incremented = one_to_increment + 1

      new_version = ''+ incremented.to_s + '.0'

      upper_version = prefix + new_version

      new_upper_constraint = "#{name} < #{upper_version}"

      return [new_lower_constraint,new_upper_constraint]
    else
      return [constraint]
    end
  end #def expand_pesimistic_constraints

  def logger
    @logger ||= Cabin::Channel.get
  end # def logger
end # module FPM::Util

Changes to lib/fpm/version.rb.

1
2
3
module FPM
  VERSION = "1.1.0"
end

|

1
2
3
module FPM
  VERSION = "1.3.3.4"
end

Added spec/fixtures/deb/meta_test.



>
1
asdf

Added spec/fixtures/deb/triggers.



>
1
interest from-meta-file

Added spec/fixtures/gem/example/bin/example.



>
1
#!/usr/bin/env ruby

Changes to spec/fixtures/gem/example/example-1.0.gem.

cannot compute difference between binary files

Changes to spec/fixtures/gem/example/example.gemspec.

1
2
3
4
5
6
7
8
9
10
11


12
13
14
15
16
17
18
Gem::Specification.new do |spec|
  spec.name = "example"
  spec.version = "1.0"
  spec.summary = "sample summary"
  spec.description = "sample description"

  spec.add_dependency("dependency1") # license: Ruby License
  spec.add_dependency("dependency2")

  #spec.files = ["hello.txt"]
  spec.files = []


  #spec.require_paths << "lib"

  spec.author = "sample author"
  spec.email = "sample email"
  spec.homepage = "http://sample-url/"
end











|
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Gem::Specification.new do |spec|
  spec.name = "example"
  spec.version = "1.0"
  spec.summary = "sample summary"
  spec.description = "sample description"

  spec.add_dependency("dependency1") # license: Ruby License
  spec.add_dependency("dependency2")

  #spec.files = ["hello.txt"]
  spec.files = [ "bin/example" ]
  spec.executables = "example"
  spec.bindir = "bin"
  #spec.require_paths << "lib"

  spec.author = "sample author"
  spec.email = "sample email"
  spec.homepage = "http://sample-url/"
end

Added spec/fixtures/mockpackage.rb.















>
>
>
>
>
>
>
1
2
3
4
5
6
7
require "fpm/namespace"
require "fpm/package"

class FPM::Package::Mock < FPM::Package
  def input(*args); end
  def output(*args); end
end

Changes to spec/fpm/command_spec.rb.

1
2
3
4

5
6
7
8
9
10
11
require "spec_setup"
require "stud/temporary"
require "fpm" # local
require "fpm/command" # local


describe FPM::Command do
  describe "--prefix"
  describe "-C"
  describe "-p / --package"
  describe "-f"
  describe "-n"




>







1
2
3
4
5
6
7
8
9
10
11
12
require "spec_setup"
require "stud/temporary"
require "fpm" # local
require "fpm/command" # local
require "fixtures/mockpackage"

describe FPM::Command do
  describe "--prefix"
  describe "-C"
  describe "-p / --package"
  describe "-f"
  describe "-n"
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57








58


















        end
      end
    end

    context "when not set" do
      it "should write the package to the current directory." do
        Stud::Temporary.directory do |path|
          puts "PATH: #{path}"
          puts "WD: #{Dir.getwd}"
          Dir.chdir(path) do
            cmd = FPM::Command.new("fpm")
            cmd.run(["-s", "empty", "-t", "deb", "-n", "example"])
          end
          files = Dir.new(path).to_a - ['.', '..']
          puts files.inspect
          insist { files.size } == 1
          insist { files[0] } =~ /example_/
        end
      end
    end
  end








end

























<
<





<






>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
        end
      end
    end

    context "when not set" do
      it "should write the package to the current directory." do
        Stud::Temporary.directory do |path|


          Dir.chdir(path) do
            cmd = FPM::Command.new("fpm")
            cmd.run(["-s", "empty", "-t", "deb", "-n", "example"])
          end
          files = Dir.new(path).to_a - ['.', '..']

          insist { files.size } == 1
          insist { files[0] } =~ /example_/
        end
      end
    end
  end

  describe "--log" do
    subject { FPM::Command.new("fpm") }
    let (:args) { [ "-s", "mock", "-t", "mock" ] }

    context "when not given" do
      it "should not raise an exception" do
        subject.parse(args)
      end
    end
    context "when given a valid log level" do
      it "should not raise an exception" do
        subject.parse(args + ["--log", "error"])
        subject.parse(args + ["--log", "warn"])
        subject.parse(args + ["--log", "info"])
        subject.parse(args + ["--log", "debug"])
      end
    end
    context "when given an invalid log level" do
      it "should raise an exception" do
        insist { subject.parse(args + ["--log", ""]) }.raises FPM::Package::InvalidArgument
        insist { subject.parse(args + ["--log", "whatever"]) }.raises FPM::Package::InvalidArgument
        insist { subject.parse(args + ["--log", "fatal"]) }.raises FPM::Package::InvalidArgument
      end
    end
  end
end

Changes to spec/fpm/package/deb_spec.rb.

109
110
111
112
113
114
115




116
117
118
119
120
121
122
    it "should lowercase the package name" do
      insist { subject.name } == subject.name.downcase
    end

    it "should replace underscores with dashes in the package name" do
      reject { subject.name }.include?("_")
    end




  end

  describe "#output" do
    before :all do
      # output a package, use it as the input, set the subject to that input
      # package. This helps ensure that we can write and read packages
      # properly.







>
>
>
>







109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
    it "should lowercase the package name" do
      insist { subject.name } == subject.name.downcase
    end

    it "should replace underscores with dashes in the package name" do
      reject { subject.name }.include?("_")
    end

    it "should replace spaces with dashes in the package name" do
      reject { subject.name }.include?(" ")
    end
  end

  describe "#output" do
    before :all do
      # output a package, use it as the input, set the subject to that input
      # package. This helps ensure that we can write and read packages
      # properly.
146
147
148
149
150
151
152







153
154
155
156
157
158
159
160
161
162
































163
164
165
166
167
168
169
      @original.attributes[:deb_build_depends] << 'something-else > 0.0.0'
      @original.attributes[:deb_build_depends] << 'something-else < 1.0.0'

      @original.attributes[:deb_priority] = "fizzle"
      @original.attributes[:deb_field_given?] = true
      @original.attributes[:deb_field] = { "foo" => "bar" }








      @original.output(@target)

      @input = FPM::Package::Deb.new
      @input.input(@target)
    end

    after :all do
      @original.cleanup
      @input.cleanup
    end # after

































    context "package attributes" do
      it "should have the correct name" do
        insist { @input.name } == @original.name
      end

      it "should have the correct version" do







>
>
>
>
>
>
>










>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







150
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
      @original.attributes[:deb_build_depends] << 'something-else > 0.0.0'
      @original.attributes[:deb_build_depends] << 'something-else < 1.0.0'

      @original.attributes[:deb_priority] = "fizzle"
      @original.attributes[:deb_field_given?] = true
      @original.attributes[:deb_field] = { "foo" => "bar" }

      @original.attributes[:deb_meta_files] = %w[meta_test triggers].map { |fn|
        File.expand_path("../../../fixtures/deb/#{fn}", __FILE__)
      }

      @original.attributes[:deb_interest] = ['asdf', 'hjkl']
      @original.attributes[:deb_activate] = ['qwer', 'uiop']

      @original.output(@target)

      @input = FPM::Package::Deb.new
      @input.input(@target)
    end

    after :all do
      @original.cleanup
      @input.cleanup
    end # after

    context "when the deb's control section is extracted" do
      before :all do
        tmp_control = Tempfile.new("fpm-test-deb-control")
        @control_extracted = tmp_control.path
        tmp_control.unlink
        system("dpkg-deb -e '#{@target}' '#{@control_extracted}'") or \
          raise "couldn't extract test deb"
      end

      it "should have the requested meta file in the control archive" do
        File.open(File.join(@control_extracted, 'meta_test')) do |f|
          insist { f.read.chomp } == "asdf"
        end
      end

      it "should have the requested triggers in the triggers file" do
        triggers = File.open(File.join(@control_extracted, 'triggers')) do |f|
          f.read
        end
        reject { triggers =~ /^interest from-meta-file$/ }.nil?
        reject { triggers =~ /^interest asdf$/ }.nil?
        reject { triggers =~ /^interest hjkl$/ }.nil?
        reject { triggers =~ /^activate qwer$/ }.nil?
        reject { triggers =~ /^activate uiop$/ }.nil?
        insist { triggers[-1] } == ?\n
      end

      after :all do
        FileUtils.rm_rf @control_extracted
      end
    end

    context "package attributes" do
      it "should have the correct name" do
        insist { @input.name } == @original.name
      end

      it "should have the correct version" do
333
334
335
336
337
338
339

340
341
342
343
344
345
346
347
348
349
      @deb.cleanup
      FileUtils.rm_r @staging_path if File.exists? @staging_path
    end # after

    context "when run against lintian", :if => have_lintian do
      lintian_errors_to_ignore = [
        "no-copyright-file",

        "non-standard-file-permissions-for-etc-init.d-script"
      ]

      it "should return no errors" do
        lintian_output = %x{lintian #{@target} --suppress-tags #{lintian_errors_to_ignore.join(",")}}
        expect($?).to eq(0), lintian_output
      end
    end
  end
end # describe FPM::Package::Deb







>










376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
      @deb.cleanup
      FileUtils.rm_r @staging_path if File.exists? @staging_path
    end # after

    context "when run against lintian", :if => have_lintian do
      lintian_errors_to_ignore = [
        "no-copyright-file",
        "init.d-script-missing-lsb-section",
        "non-standard-file-permissions-for-etc-init.d-script"
      ]

      it "should return no errors" do
        lintian_output = %x{lintian #{@target} --suppress-tags #{lintian_errors_to_ignore.join(",")}}
        expect($?).to eq(0), lintian_output
      end
    end
  end
end # describe FPM::Package::Deb

Changes to spec/fpm/package/dir_spec.rb.

123
124
125
126
127
128
129














130
      subject.attributes[:chdir] = tmpdir
      subject.attributes[:prefix] = "/foo"
      subject.input(File.join("a", "a=b"))
      subject.output(output)
      insist { File }.exist?(File.join(output, "foo", "a", "a=b"))
    end
  end














end # describe FPM::Package::Dir







>
>
>
>
>
>
>
>
>
>
>
>
>
>

123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
      subject.attributes[:chdir] = tmpdir
      subject.attributes[:prefix] = "/foo"
      subject.input(File.join("a", "a=b"))
      subject.output(output)
      insist { File }.exist?(File.join(output, "foo", "a", "a=b"))
    end
  end

  context "SYMLINKS." do
    let(:path) { Stud::Temporary.pathname }
    let(:broken_target) { File.join("no", "such", "path", "here", rand(1000).to_s, rand(1000).to_s) }
    before do
      File.symlink(broken_target, path)
    end
    after do
      File.unlink(path)
    end
    it "should copy a broken symlink because it shouldn't be following symlinks to begin with" do
      subject.input(path)
    end
  end
end # describe FPM::Package::Dir

Changes to spec/fpm/package/gem_spec.rb.

12
13
14
15
16
17
18
19

























20
21
22
23
24
25
26
  let (:example_gem) do
    File.expand_path("../../fixtures/gem/example/example-1.0.gem", File.dirname(__FILE__))
  end

  after :each do
    subject.cleanup
  end
  

























  context "when :gem_fix_name? is true" do
    before :each do
      subject.attributes[:gem_fix_name?] = true
    end

    context "and :gem_package_name_prefix is nil/default" do
      it "should prefix the package with 'gem-'" do







|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







12
13
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
47
48
49
50
51
  let (:example_gem) do
    File.expand_path("../../fixtures/gem/example/example-1.0.gem", File.dirname(__FILE__))
  end

  after :each do
    subject.cleanup
  end

  context "when :gem_version_bins? is true" do
    before :each do
      subject.attributes[:gem_version_bins?] = true
      subject.attributes[:gem_bin_path] = '/usr/bin'
    end

    it "it should append the version to binaries" do
      subject.input(example_gem)
      insist { ::Dir.entries(File.join(subject.staging_path, "/usr/bin")) }.include?("example-1.0.0")
    end
  end

  context "when :gem_version_bins? is false" do
    before :each do
      subject.attributes[:gem_version_bins?] = false
      subject.attributes[:gem_bin_path] = '/usr/bin'
    end

    it "it should not append the version to binaries" do
      subject.input(example_gem)
      insist { ::Dir.entries(File.join(subject.staging_path, "/usr/bin")) }.include?("example")
    end

  end

  context "when :gem_fix_name? is true" do
    before :each do
      subject.attributes[:gem_fix_name?] = true
    end

    context "and :gem_package_name_prefix is nil/default" do
      it "should prefix the package with 'gem-'" do

Added spec/fpm/package/npm_spec.rb.













































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
require "spec_setup"
require "fpm" # local
require "fpm/package/npm" # local

have_npm = program_exists?("npm")
if !have_npm
  Cabin::Channel.get("rspec") \
    .warn("Skipping NPM tests because 'npm' isn't in your PATH")
end

describe FPM::Package::NPM do
  after do
    subject.cleanup
  end

  describe "::default_prefix", :if => have_npm do
    it "should provide a valid default_prefix" do
      stat = File.stat(FPM::Package::NPM.default_prefix)
      insist { stat }.directory?
    end
  end
end # describe FPM::Package::NPM

Changes to spec/fpm/package/python_spec.rb.

126
127
128
129
130
131
132

133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
        insist { subject.dependencies.sort } == ["rtxt-dep1 > 0.1", "rtxt-dep2 = 0.1"]
      end
    end
  end

  context "python_scripts_executable is set" do
    it "should have scripts with a custom hashbang line" do

      subject.attributes[:python_scripts_executable] = "fancypants"
      subject.input("django")

      # Get the default scripts install directory and use it to find django-admin.py from Django
      # Then let's make sure the scripts executable setting worked!
      python_bindir = %x{python -c 'from distutils.sysconfig import get_config_var; print get_config_var("BINDIR")'}.chomp
      path = subject.staging_path(File.join(python_bindir, "django-admin.py"))

      # Read the first line (the hashbang line) of the django-admin.py script
      fd = File.new(path, "r")
      topline = fd.readline
      fd.close

      insist { topline.chomp } == "#!fancypants"
    end
  end
end # describe FPM::Package::Python







>





|











126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
        insist { subject.dependencies.sort } == ["rtxt-dep1 > 0.1", "rtxt-dep2 = 0.1"]
      end
    end
  end

  context "python_scripts_executable is set" do
    it "should have scripts with a custom hashbang line" do
      subject.attributes[:python_install_bin] = '/usr/bin'
      subject.attributes[:python_scripts_executable] = "fancypants"
      subject.input("django")

      # Get the default scripts install directory and use it to find django-admin.py from Django
      # Then let's make sure the scripts executable setting worked!
      python_bindir = %x{python -c 'from distutils.sysconfig import get_config_var; print(get_config_var("BINDIR"))'}.chomp
      path = subject.staging_path(File.join(python_bindir, "django-admin.py"))

      # Read the first line (the hashbang line) of the django-admin.py script
      fd = File.new(path, "r")
      topline = fd.readline
      fd.close

      insist { topline.chomp } == "#!fancypants"
    end
  end
end # describe FPM::Package::Python

Changes to spec/fpm/package/rpm_spec.rb.

77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
      end

      after :each do
        subject.cleanup
      end

      it "should set the user and group of each file in the RPM" do
        subject.rpmspec.should include('%defattr(-,root,root,-')
      end
    end # context

    context "non-default user and group" do
      before :each do
        subject.attributes[:rpm_user] = "some_user"
        subject.attributes[:rpm_group] = "some_group"







|







77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
      end

      after :each do
        subject.cleanup
      end

      it "should set the user and group of each file in the RPM" do
        expect(subject.rpmspec).to include('%defattr(-,root,root,-')
      end
    end # context

    context "non-default user and group" do
      before :each do
        subject.attributes[:rpm_user] = "some_user"
        subject.attributes[:rpm_group] = "some_group"
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
      end

      after :each do
        subject.cleanup
      end

      it "should set the user and group of each file in the RPM" do
        subject.rpmspec.should include('%defattr(-,some_user,some_group,-')
      end
    end # context
  end

  describe "#output", :if => program_exists?("rpmbuild") do
    context "architecture" do
      it "can be basically anything" do







|







101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
      end

      after :each do
        subject.cleanup
      end

      it "should set the user and group of each file in the RPM" do
        expect(subject.rpmspec).to include('%defattr(-,some_user,some_group,-')
      end
    end # context
  end

  describe "#output", :if => program_exists?("rpmbuild") do
    context "architecture" do
      it "can be basically anything" do
146
147
148
149
150
151
152










153
154
155
156
157
158
159
        subject.provides << "bacon = 1.0"

        # TODO(sissel): This api sucks, yo.
        subject.scripts[:before_install] = "example before_install"
        subject.scripts[:after_install] = "example after_install"
        subject.scripts[:before_remove] = "example before_remove"
        subject.scripts[:after_remove] = "example after_remove"











        # Write the rpm out
        subject.output(@target)

        # Read the rpm
        @rpm = ::RPM::File.new(@target)








>
>
>
>
>
>
>
>
>
>







146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
        subject.provides << "bacon = 1.0"

        # TODO(sissel): This api sucks, yo.
        subject.scripts[:before_install] = "example before_install"
        subject.scripts[:after_install] = "example after_install"
        subject.scripts[:before_remove] = "example before_remove"
        subject.scripts[:after_remove] = "example after_remove"
        subject.scripts[:rpm_verifyscript] = "example rpm_verifyscript"
        subject.scripts[:rpm_posttrans] = "example rpm_posttrans"
        subject.scripts[:rpm_pretrans] = "example rpm_pretrans"


        # Test for triggers #626
        subject.attributes[:rpm_trigger_before_install] = [["test","#!/bin/sh\necho before_install trigger executed\n"]]
        subject.attributes[:rpm_trigger_after_install] = [["test","#!/bin/sh\necho after_install trigger executed\n"]]
        subject.attributes[:rpm_trigger_before_uninstall] = [["test","#!/bin/sh\necho before_uninstall trigger executed\n"]]
        subject.attributes[:rpm_trigger_after_target_uninstall] = [["test","#!/bin/sh\necho after_target_uninstall trigger executed\n"]]

        # Write the rpm out
        subject.output(@target)

        # Read the rpm
        @rpm = ::RPM::File.new(@target)

227
228
229
230
231
232
233















234
235
236
237
238
239
240
241
242
243




































244
245
246
247
248
249
250
        insist { @rpm.tags[:preunprog] } == "/bin/sh"
      end

      it "should have the correct 'postun' script" do
        insist { @rpm.tags[:postun] } == "example after_remove"
        insist { @rpm.tags[:postunprog] } == "/bin/sh"
      end
















      it "should have the correct 'prein' script" do
        insist { @rpm.tags[:prein] } == "example before_install"
        insist { @rpm.tags[:preinprog] } == "/bin/sh"
      end

      it "should have the correct 'postin' script" do
        insist { @rpm.tags[:postin] } == "example after_install"
        insist { @rpm.tags[:postinprog] } == "/bin/sh"
      end





































      it "should use md5/gzip by default" do
        insist { @rpmtags[:payloadcompressor] } == "gzip"

        # For whatever reason, the 'filedigestalgo' tag is an array of numbers.
        # I only ever see one element in this array, so just do value.first
        # 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>










>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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
        insist { @rpm.tags[:preunprog] } == "/bin/sh"
      end

      it "should have the correct 'postun' script" do
        insist { @rpm.tags[:postun] } == "example after_remove"
        insist { @rpm.tags[:postunprog] } == "/bin/sh"
      end

      it "should have the correct 'verify' script" do
        insist { @rpm.tags[:verifyscript] } == "example rpm_verifyscript"
        insist { @rpm.tags[:verifyscriptprog] } == "/bin/sh"
      end

      it "should have the correct 'pretrans' script" do
        insist { @rpm.tags[:pretrans] } == "example rpm_pretrans"
        insist { @rpm.tags[:pretransprog] } == "/bin/sh"
      end

      it "should have the correct 'posttrans' script" do
        insist { @rpm.tags[:posttrans] } == "example rpm_posttrans"
        insist { @rpm.tags[:posttransprog] } == "/bin/sh"
      end

      it "should have the correct 'prein' script" do
        insist { @rpm.tags[:prein] } == "example before_install"
        insist { @rpm.tags[:preinprog] } == "/bin/sh"
      end

      it "should have the correct 'postin' script" do
        insist { @rpm.tags[:postin] } == "example after_install"
        insist { @rpm.tags[:postinprog] } == "/bin/sh"
      end
 
      it "should have the correct 'before_install' trigger script" do
        insist { @rpm.tags[:triggername][0] } == "test"
        insist { @rpm.tags[:triggerversion][0] } == ""
        insist { @rpm.tags[:triggerflags][0] & (1 << 25)} == ( 1 << 25) # See FPM::Package::RPM#rpm_get_trigger_type
        insist { @rpm.tags[:triggerindex][0] } == 0
        insist { @rpm.tags[:triggerscriptprog][0] } == "/bin/sh"
        insist { @rpm.tags[:triggerscripts][0] } == "#!/bin/sh\necho before_install trigger executed"
      end

      it "should have the correct 'after_install' trigger script" do
        insist { @rpm.tags[:triggername][1] } == "test"
        insist { @rpm.tags[:triggerversion][1] } == ""
        insist { @rpm.tags[:triggerflags][1] & (1 << 16)} == ( 1 << 16) # See FPM::Package::RPM#rpm_get_trigger_type
        insist { @rpm.tags[:triggerindex][1] } == 1
        insist { @rpm.tags[:triggerscriptprog][1] } == "/bin/sh"
        insist { @rpm.tags[:triggerscripts][1] } == "#!/bin/sh\necho after_install trigger executed"
      end

      it "should have the correct 'before_uninstall' trigger script" do
        insist { @rpm.tags[:triggername][2] } == "test"
        insist { @rpm.tags[:triggerversion][2] } == ""
        insist { @rpm.tags[:triggerflags][2] & (1 << 17)} == ( 1 << 17) # See FPM::Package::RPM#rpm_get_trigger_type
        insist { @rpm.tags[:triggerindex][2] } == 2
        insist { @rpm.tags[:triggerscriptprog][2] } == "/bin/sh"
        insist { @rpm.tags[:triggerscripts][2] } == "#!/bin/sh\necho before_uninstall trigger executed"
      end

      it "should have the correct 'after_target_uninstall' trigger script" do
        insist { @rpm.tags[:triggername][3] } == "test"
        insist { @rpm.tags[:triggerversion][3] } == ""
        insist { @rpm.tags[:triggerflags][3] & (1 << 18)} == ( 1 << 18) # See FPM::Package::RPM#rpm_get_trigger_type
        insist { @rpm.tags[:triggerindex][3] } == 3
        insist { @rpm.tags[:triggerscriptprog][3] } == "/bin/sh"
        insist { @rpm.tags[:triggerscripts][3] } == "#!/bin/sh\necho after_target_uninstall trigger executed"
      end

      it "should use md5/gzip by default" do
        insist { @rpmtags[:payloadcompressor] } == "gzip"

        # For whatever reason, the 'filedigestalgo' tag is an array of numbers.
        # I only ever see one element in this array, so just do value.first
        # 

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|
75
76
77
78
79
80
81












82







































      it "should still run commands correctly" do
        # This will raise an exception if we can't run it at all.
        subject.safesystem("true")
      end
    end
  end












end













































>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
118
119
120
121
122
123
124
125
126
127
128
129

      it "should still run commands correctly" do
        # This will raise an exception if we can't run it at all.
        subject.safesystem("true")
      end
    end
  end

  describe "#expand_pessimistic_constraints" do
    it 'convert 2 piece versions' do
      constraint = 'bundle ~> 1.2'

      expected_lower = 'bundle >= 1.2'
      expected_upper =  'bundle < 2.0'

      derived_constraint = subject.expand_pessimistic_constraints(constraint)

      expect(derived_constraint).to include expected_lower
      expect(derived_constraint).to include expected_upper
    end

    it 'convert 3 piece versions' do
      constraint = 'zippy ~> 1.2.3'

      expected_lower = 'zippy >= 1.2.3'
      expected_upper =  'zippy < 1.3.0'

      derived_constraint = subject.expand_pessimistic_constraints(constraint)

      expect(derived_constraint).to include expected_lower
      expect(derived_constraint).to include expected_upper
    end

    it 'does not convert where not needed when the operator is > ' do
      constraint = 'zippy > 1.2.3'
      derived_constraint = subject.expand_pessimistic_constraints(constraint)
      expect(derived_constraint).to include constraint
    end

    it 'does not convert where not needed when when the operator is < ' do
      constraint = 'zippy < 1.2.3'
      derived_constraint = subject.expand_pessimistic_constraints(constraint)
      expect(derived_constraint).to include constraint
    end

    it 'does not convert where not needed when when the operator is <= ' do
      constraint = 'zippy <= 1.2.3'
      derived_constraint = subject.expand_pessimistic_constraints(constraint)
      expect(derived_constraint).to include constraint
    end

    it 'does not convert where not needed when when the operator is >= ' do
      constraint = 'zippy >= 1.2.3'
      derived_constraint = subject.expand_pessimistic_constraints(constraint)
      expect(derived_constraint).to include constraint
    end
  end
end

Changes to spec/spec_setup.rb.

12
13
14
15
16
17
18







19


20

21
22
23
24
25
26
27
28
29
30
require "fpm/util"
include FPM::Util

# Enable debug logs if requested.
if $DEBUG or ENV["DEBUG"]
  Cabin::Channel.get.level = :debug
  Cabin::Channel.get.subscribe(STDOUT)







end




spec_logger = Cabin::Channel.get("rspec")
spec_logger.subscribe(STDOUT)
spec_logger.level = :warn

# Quiet the output of all system() calls
module Kernel
  alias_method :orig_system, :system
  def system(*args)
    old_stdout = $stdout.clone
    old_stderr = $stderr.clone







>
>
>
>
>
>
>
|
>
>

>


|







12
13
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
require "fpm/util"
include FPM::Util

# Enable debug logs if requested.
if $DEBUG or ENV["DEBUG"]
  Cabin::Channel.get.level = :debug
  Cabin::Channel.get.subscribe(STDOUT)
else
  class << Cabin::Channel.get
    alias_method :subscribe_, :subscribe
    def subscribe(io)
      return if io == STDOUT
      subscribe_(io)
      #puts caller.join("\n")
    end
  end
end

Cabin::Channel.get.level = :error
spec_logger = Cabin::Channel.get("rspec")
spec_logger.subscribe(STDOUT)
spec_logger.level = :error

# Quiet the output of all system() calls
module Kernel
  alias_method :orig_system, :system
  def system(*args)
    old_stdout = $stdout.clone
    old_stderr = $stderr.clone

Added templates/appstream.erb.

















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<!-- Maintainer: <%=h maintainer %>, autogenerated by `fpm -u appdata` -->
<component type="desktop">
 <id><%=h name %>.desktop</id>
 <metadata_license>CC0-1.0</metadata_license>
 <project_license><%=h license %></project_license>
 <name><%=h name %></name>
   <% summary, *description = ((description) or "no description given").split("\n") -%>
 <summary><%=h summary %></summary>
 <description>
  <p><%=
    h description[0,25].join("\n")  # should actually generate mini-html with <p> and <ul>+<li>
  %></p>
 </description>
 <screenshots>
  <screenshot type="default">
   <image>http://freshcode.club/img/nopreview.png</image>
   <caption>Main window..</caption>
  </screenshot>
 </screenshots>
 <url type="homepage"><%=h (url or "data:,") %></url>
 <updatecontact><%=h maintainer %></updatecontact>
 <!--project_group><%=h category %></project_group-->
</component>

Changes to templates/deb.erb.

1
2
3

4

5
6
7
8
9
10
11
Package: <%= name %>
Version: <%= "#{epoch}:" if epoch %><%= version %><%= "-" + iteration.to_s if iteration %>
License: <%= license %>

Vendor: <%= vendor %>

Architecture: <%= architecture %>
Maintainer: <%= maintainer  %>
Installed-Size: <%= attributes[:deb_installed_size] %>
<% if !dependencies.empty? and !attributes[:no_depends?] -%>
Depends: <%= dependencies.collect { |d| fix_dependency(d) }.flatten.join(", ") %>
<% end -%>
<% if !conflicts.empty? -%>



>

>







1
2
3
4
5
6
7
8
9
10
11
12
13
Package: <%= name %>
Version: <%= "#{epoch}:" if epoch %><%= version %><%= "-" + iteration.to_s if iteration %>
License: <%= license %>
<% if !vendor.nil? and !vendor.empty? -%>
Vendor: <%= vendor %>
<% end -%>
Architecture: <%= architecture %>
Maintainer: <%= maintainer  %>
Installed-Size: <%= attributes[:deb_installed_size] %>
<% if !dependencies.empty? and !attributes[:no_depends?] -%>
Depends: <%= dependencies.collect { |d| fix_dependency(d) }.flatten.join(", ") %>
<% end -%>
<% if !conflicts.empty? -%>

Added templates/deb/changelog.erb.











>
>
>
>
>
1
2
3
4
5
<%= name %> (<%= "#{epoch}:" if epoch %><%= version %><%= "-" + iteration.to_s if iteration %>) whatever; urgency=medium

  * Package created with FPM.

 -- <%= maintainer %>  <%= Time.now.strftime("%a, %d %b %Y %T %z") %>

Added templates/deb/postinst_upgrade.sh.erb.































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
after_upgrade() {
<% if script?(:after_upgrade) -%>
<%=  script(:after_upgrade) %>
<% end -%>
}

after_install() {
<% if script?(:after_install) -%>
<%=  script(:after_install) %>
<% end -%>
}

if [ "${1}" = "configure" -a -z "${2}" ]
then
    # "after install" here
    after_install
elif [ "${1}" = "configure" -a -n "${2}" ]
then
    upgradeFromVersion="${2}"
    # "after upgrade" here
    # NOTE: This slot is also used when deb packages are removed,
    # but their config files aren't, but a newer version of the
    # package is installed later, called "Config-Files" state.
    # basically, that still looks a _lot_ like an upgrade to me.
    after_upgrade "${2}"
elif echo "${1}" | grep -E -q "(abort|fail)"
then
    echo "Failed to install before the post-installation script was run." >&2
    exit 1
fi

Added templates/deb/postrm_upgrade.sh.erb.











































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
after_remove() {
<% if script?(:after_remove) -%>
<%=  script(:after_remove) %>
<% end -%>
}

dummy() {
}


if [ "${1}" = "remove" ]
then
    # "after remove" goes here
    after_remove
elif [ "${1}" = "purge" -a -z "${2}" ]
then
    # like "on remove", but executes after dpkg deletes config files
    # 'apt-get purge' runs 'on remove' section, then this section.
    # Maybe we ignore this; it seems really fine-grained.
    # There is no equivalent in RPM or ARCH. A debian-specific argument
    # might perhaps be used here, but most people
    # probably don't need it.
    dummy
elif [ "${1}" = "upgrade" ]
then
    # This represents the case where the old package's postrm is called after
    # the 'preinst' script is called.
    # We should ignore this and just use 'preinst upgrade' and
    # 'postinst configure'. The newly installed package should do the
    # upgrade, not the uninstalled one, since it can't anticipate what new
    # things it will have to do to upgrade for the new version.
    dummy
elif echo "${1}" | grep -E -q '(fail|abort)'
then
    echo "Failed to install before the post-removal script was run." >&2
    exit 1
fi

Added templates/deb/preinst_upgrade.sh.erb.

































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
before_upgrade() {
<% if script?(:before_upgrade) -%>
<%=  script(:before_upgrade) %>
<% end -%>
}

before_install() {
<% if script?(:before_install) -%>
<%=  script(:before_install) %>
<% end -%>
}

if [ "${1}" = "install" -a -z "${2}" ]
then
    before_install
elif [ "${1}" = "upgrade" -a -n "${2}" ]
then
    upgradeFromVersion="${2}"
    before_upgrade "${2}"
elif [ "${1}" = "install" -a -n "${2}" ]
then
    upgradeFromVersion="${2}"
    # Executed when a package is removed but its config files aren't,
    # and a new version is installed.
    # Looks a _lot_ like an upgrade case, I say we just execute the
    # same "before upgrade" script as in the previous case
    before_upgrade "${2}"
elif echo "${1}" | grep -E -q '(fail|abort)'
then
    echo "Failed to install before the pre-installation script was run." >&2
    exit 1
fi

Added templates/deb/prerm_upgrade.sh.erb.























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
before_remove() {
<% if script?(:before_remove) -%>
<%=  script(:before_remove) %>
<% end -%>
}

dummy() {
}

if [ "${1}" = "remove" -a -z "${2}" ]
then
    # "before remove" goes here
    before_remove
elif [ "${1}" = "upgrade" ]
then
    upgradeVersionTo="${2}"
    # Executed before the old version is removed
    # upon upgrade.
    # We should generally not do anything here. The newly installed package
    # should do the upgrade, not the uninstalled one, since it can't anticipate
    # what new things it will have to do to upgrade for the new version.
    dummy "${2}"
elif echo "${1}" | grep -E -q "(fail|abort)"
then
    echo "Failed to install before the pre-removal script was run." >&2
    exit 1
fi

Added templates/desktop.erb.





















>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
[Desktop Entry]
Name=<%= @name %>
;GenericName=<%= @name %>
Comment=<%= @description.split("\n").first or "" %>
Categories=<%= @category %>
Exec=<%= @name %>
Icon=<%= @name %>
Terminal=false
Type=Application
MimeType=x-scheme-handler/<%= @name %>;

Added templates/listaller/doap.erb.



















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns="http://usefulinc.com/ns/doap#">
  <name><%=h @name %></name>
  <homepage rdf:resource="<%=h @url %>" />
  <created><%= Time.now.strftime("%Y-%m-%d") %></created>
  <shortdesc xml:lang="en">
    <%=h (description.split("\n") or ["no description"]).first %>
  </shortdesc>
  <description xml:lang="en">
    <%=h (description.split("\n") or ["", "no details available"])[1,50].join("\n    ") %>
  </description>
  <maintainer>
          <foaf:Person>
                  <foaf:name><%=h @maintainer %></foaf:name>
          </foaf:Person>
  </maintainer>
  <release>
          <Version>
                  <name>stable</name>
                  <created><%=h Time.now.strftime("%Y-%m-%d") %></created>
                  <revision><%=h @version %></revision>
          </Version>
  </release>
  <license rdf:resource="data:,<%=h @license %>" />
</Project>

Added templates/listaller/files.erb.







































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# IPK file list for <%= name %>
# very crude, does not separate data files yet

:: %APP%
<%= files.grep(/\.desktop$/).join("\n") %>

:: %INST%
<% files.each do |fn| %><%= fn %>
<% end %>

:: %ICON-16%
# none
:: %ICON-32%
# none
:: %ICON-64%
# none
:: %ICON-128%
# none
# could probably just .grep() for icons here too..?

Added templates/listaller/pkoptions.erb.









>
>
>
>
1
2
3
4
Version: 1.1

AutoFindDeps: <%= attributes[:ipk_relocatable] ? "true" : "false" %>

Changes to templates/rpm.erb.

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

%install
# noop

%clean
# noop

<%# This next section puts any %pre, %post, %preun, or %postun scripts %>
<% 
  scriptmap = {
    :before_install => "pre",
    :after_install => "post",
    :before_remove => "preun",
    :after_remove => "postun",
  }
  scriptmap.each do |name, rpmname| 
-%>






















<%   if script?(name) -%>



%<%= rpmname %>




<%= script(name) %>






















































<%   end -%>
<% end -%>

%files
%defattr(<%= attributes[:rpm_defattrfile] %>,<%= attributes[:rpm_user] || "root" %>,<%= attributes[:rpm_group] || "root" %>,<%= attributes[:rpm_defattrdir] %>)
<%# Output config files and then regular files. -%>
<% config_files.each do |path| -%>







|
|

|
|
|
<

<

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
|
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211

%install
# noop

%clean
# noop

<%# This next section puts any %pre, %post, %preun, %postun, %verifyscript, %pretrans or %posttrans scripts %>
<%
  scriptmap = {
    :rpm_verifyscript => "verifyscript",
    :rpm_posttrans => "posttrans",
    :rpm_pretrans => "pretrans"

  }

-%>
<% if script?(:before_upgrade) or script?(:after_upgrade) -%>
<%   if script?(:before_upgrade) or script?(:before_install) -%>
%pre
upgrade() {
<%     if script?(:before_upgrade) -%>
<%=      script(:before_upgrade) %>
<%     end -%>
}
install() {
<%     if script?(:before_install) -%>
<%=      script(:before_install) %>
<%     end -%>
}
if [ "${1}" -eq 1 ]
then
    install
    # "before install" goes here
elif [ "${1}" -gt 1 ]
then
    upgrade
fi
<%   end -%>
<%   if script?(:after_upgrade) or script?(:after_install) -%>
%pre
upgrade() {
<%     if script?(:after_upgrade) -%>
<%=      script(:after_upgrade) %>
<%     end -%>
}
install() {
<%     if script?(:after_install) -%>
<%=      script(:after_install) %>
<%     end -%>
}
if [ "${1}" -eq 1 ]
then
    install
    # "before install" goes here
elif [ "${1}" -gt 1 ]
then
    upgrade
fi
<%   end -%>
<%   if script?(:before_remove) -%>
%preun
if [ "${1}" -eq 0 ]
then
<%=    script(:before_remove) %>
fi
<%   end -%>
<%   if script?(:after_remove) -%>
%postun
if [ "${1}" -eq 0 ]
then
<%=    script(:after_remove) %>
fi
<%   end -%>
<% else
     other_scriptmap = {
       :before_install => "pre",
       :after_install => "post",
       :before_remove => "preun",
       :after_remove => "postun"
     }
     scriptmap.merge!(other_scriptmap)
   end
-%>
<% scriptmap.each do |name, rpmname| -%>
<%   if script?(name) -%>
%<%=   rpmname %>
<%=    script(name) %>
<%   end -%>
<% end -%>

<%# This section adds any triggers, as ordered in the command line -%>
<%
  triggermap = {
    :before_install => "prein",
    :after_install => "in",
    :before_uninstall => "un",
    :after_target_uninstall => "postun"
  } 
  triggermap.each do |name, rpmtype|
    (attributes["rpm_trigger_#{name}".to_sym] or []).each do |trigger_name, trigger_script, trigger_scriptprog| -%>
%trigger<%= rpmtype -%> <%= trigger_scriptprog -%> -- <%= trigger_name %>
<%= trigger_script %>
<%   end -%>
<% end -%>

%files
%defattr(<%= attributes[:rpm_defattrfile] %>,<%= attributes[:rpm_user] || "root" %>,<%= attributes[:rpm_group] || "root" %>,<%= attributes[:rpm_defattrdir] %>)
<%# Output config files and then regular files. -%>
<% config_files.each do |path| -%>

Changes to templates/sh.erb.

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



























47
48

49
50
51
52
53
54
55
#                is used. Then it is $INSTALL_ROOT/releases/<datestamp>
# CURRENT_DIR  = if -c argument is used, this is set to the $INSTALL_ROOT/current which is
#                symlinked to INSTALL_DIR
# VERBOSE      = is set if the package was called with -v for verbose output
function main() {
    set_install_dir

    create_pid


    wait_for_others

    kill_others

    set_owner

    unpack_payload

    if [ "$UNPACK_ONLY" == "1" ] ; then
        echo "Unpacking complete, not moving symlinks or restarting because unpack only was specified."
    else
        create_symlinks

        set +e # don't exit on errors to allow us to clean up
        if ! run_post_install ; then
            revert_symlinks
            log "Installation failed."
            exit 1
        else
            clean_out_old_releases
            log "Installation complete."



























        fi
    fi

}

# deletes the PID file for this installation
function delete_pid(){
    rm -f ${INSTALL_ROOT}/$$.pid 2> /dev/null
}








|

>
|
<
|
<
|
<
|

|
|
|
|

|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


>







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
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
#                is used. Then it is $INSTALL_ROOT/releases/<datestamp>
# CURRENT_DIR  = if -c argument is used, this is set to the $INSTALL_ROOT/current which is
#                symlinked to INSTALL_DIR
# VERBOSE      = is set if the package was called with -v for verbose output
function main() {
    set_install_dir

    if ! slug_already_current ; then

      create_pid
      wait_for_others

      kill_others

      set_owner

      unpack_payload

      if [ "$UNPACK_ONLY" == "1" ] ; then
          echo "Unpacking complete, not moving symlinks or restarting because unpack only was specified."
      else
          create_symlinks

          set +e # don't exit on errors to allow us to clean up
          if ! run_post_install ; then
              revert_symlinks
              log "Installation failed."
              exit 1
          else
              clean_out_old_releases
              log "Installation complete."
          fi
      fi

    else
        echo "This slug is already installed in 'current'. Specify -f to force reinstall. Exiting."
    fi
}

# check if this slug is already running and exit unless `force` specified
# Note: this only works with RELEASE_ID is used
function slug_already_current(){
    local this_slug=$(basename $0 .slug)
    local current=$(basename "$(readlink ${INSTALL_ROOT}/current)")
    log "'current' symlink points to slug: ${current}"

    if [ "$this_slug" == "$current" ] ; then
        if [ "$FORCE" == "1" ] ; then
        log "Force was specified. Proceeding with install after renaming live directory to allow running service to shutdown correctly."
            local real_dir=$(readlink ${INSTALL_ROOT}/current)
            if [ -f ${real_dir}.old ] ; then
                # remove that .old directory, if needed
                rm -rf ${real_dir}.old
            fi
            mv ${real_dir} ${real_dir}.old
            mkdir -p ${real_dir}
        else
            return 0;
        fi
    fi
    return 1;
}

# deletes the PID file for this installation
function delete_pid(){
    rm -f ${INSTALL_ROOT}/$$.pid 2> /dev/null
}

104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

# write out metadata for future installs
function save_environment(){
    local METADATA=$1
    echo -n "" > ${METADATA} # empty file

    # just piping env to a file doesn't quote the variables. This does
    # filter out multiline junk and _. _ is a readonly variable
    env | egrep "^[^ ]+=.*" | grep -v "^_=" | while read ENVVAR ; do
        local NAME=${ENVVAR%%=*}
        # sed is to preserve variable values with dollars (for escaped variables or $() style command replacement),
        # and command replacement backticks
        # Escaped parens captures backward reference \1 which gets replaced with backslash and \1 to esape them in the saved
        # variable value
        local VALUE=$(eval echo '$'$NAME | sed 's/\([$`]\)/\\\1/g')
        echo "export $NAME=\"$VALUE\"" >> ${METADATA}







|
|







130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145

# write out metadata for future installs
function save_environment(){
    local METADATA=$1
    echo -n "" > ${METADATA} # empty file

    # just piping env to a file doesn't quote the variables. This does
    # filter out multiline junk, _, and functions. _ is a readonly variable.
    env | grep -v "^_=" | grep -v "^[^=(]*()=" | egrep "^[^ ]+=" | while read ENVVAR ; do
        local NAME=${ENVVAR%%=*}
        # sed is to preserve variable values with dollars (for escaped variables or $() style command replacement),
        # and command replacement backticks
        # Escaped parens captures backward reference \1 which gets replaced with backslash and \1 to esape them in the saved
        # variable value
        local VALUE=$(eval echo '$'$NAME | sed 's/\([$`]\)/\\\1/g')
        echo "export $NAME=\"$VALUE\"" >> ${METADATA}

Added xpm.gemspec.



















































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
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
47
48
49
50
51
52
53
54
55
56
57
require File.join(File.dirname(__FILE__), "lib/fpm/version")
Gem::Specification.new do |spec|
  files = []
  files += Dir["{bin,lib,templates}/**/*"]

  files << "LICENSE"
  files << "CONTRIBUTORS"
  files << "CHANGELIST"

  spec.name = "xpm"
  spec.version = FPM::VERSION
  spec.summary = "fpm - package building and mangling"
  spec.description = "Convert directories, rpms, python eggs, rubygems, and " \
    "more to rpms, debs, solaris packages and more. Win at package " \
    "management without wasting pointless hours debugging bad rpm specs!"
  spec.license = "MIT-like"

  # For parsing JSON (required for some Python support, etc)
  # http://flori.github.com/json/doc/index.html
  spec.add_dependency("json", ">= 1.7.7") # license: Ruby License
  
  # For logging
  # https://github.com/jordansissel/ruby-cabin
  spec.add_dependency("cabin", ">= 0.6.0") # license: Apache 2 

  # For backports to older rubies
  # https://github.com/marcandre/backports
  spec.add_dependency("backports", ">= 2.6.2") # license: MIT

  # For reading and writing rpms
  spec.add_dependency("arr-pm", "~> 0.0.9") # license: Apache 2

  # For command-line flag support
  # https://github.com/mdub/clamp/blob/master/README.markdown
  spec.add_dependency("clamp", "~> 0.6") # license: MIT

  # For starting external processes across various ruby interpreters
  spec.add_dependency("childprocess") # license: ???

  # For calling functions in dynamic libraries
  spec.add_dependency("ffi") # license: GPL3/LGPL3

  spec.add_development_dependency("rspec", "~> 3.0.0") # license: MIT (according to wikipedia)
  spec.add_development_dependency("insist", "~> 0.0.5") # license: ???
  spec.add_development_dependency("pry")
  spec.add_development_dependency("stud")

  spec.files = files
  spec.require_paths << "lib"
  spec.bindir = "bin"
  spec.executables << "xpm"

  spec.author = "Mario Salzer"
  spec.email = "mario#include-once:org"
  spec.homepage = "http://fossil.include-once.org/xpm/"
end