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

⌈⌋ ⎇ branch:  cross package maker


Check-in [aa9ee22bad]

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

Overview
Comment:Pass other scripts through
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: aa9ee22badfc1871fe633e109333fc98a8f57687
User & Date: djhaskin987@gmail.com 2014-09-19 13:10:51
Context
2014-09-19
13:23
Merge into scriptmap in the name of elegance check-in: 7c35c9bf18 user: djhaskin987@gmail.com tags: trunk
13:10
Pass other scripts through check-in: aa9ee22bad user: djhaskin987@gmail.com tags: trunk
12:53
Grammar, contributions check-in: 7934760d27 user: djhaskin987@gmail.com tags: trunk
2014-09-18
17:44
Merge pull request #694 from vStone/bugfix/fix-python-tests-python3 Fixes 2 python issues within the tests check-in: 8d82a3922e user: jls@semicomplete.com tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

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
8
9
10





















?.?.? (???)



















































  - cpan: catch more cases of perllocal.pod and delete them (#510)






  - python: safer gathering of python module dependencies.









































0.4.42 (July 23, 2013)
  - dir: make source=destination mappings behave the same way 'rsync -a' does
    with respect to source and destination paths.

0.4.41 (July 17, 2013)
  - cpan: handle cases where modules don't specify a license
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>

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







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
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)
  - gem: Add --gem-disable-dependency for removing one or more specific rubygem
    dependencies from the automatically-generated list (#598, Derek Olsen)
  - python: Add --python-scripts-executable for setting a custom interpreter to
    use for the hashbang line at the top of may python package scripts.
    (#628, Vladimir Rutsky)
  - Allow absolute paths with --directories even when --prefix is used (Vladimir Rutsky)
  - dir: Now correctly identifies hardlinked files and creates a package correctly
    with that knowledge (#365, #623, #659; Vladimir Rutsky)
  - rpm: Add --rpm-auto-add-exclude-directories for excluding directories
    from the --rpm-auto-add-directories behavior (#640, Vladimir Rutsky)
  - general: --config-files now accepts directories and will recursively mark any
    files within as config files inside the package (#642, Vladimir Rutsky)
  - general: If you specify a --config-files path that doesn't exist, you will
    now get an error. (#654, Alan Franzoni)
  - python: Support --python-pypi when using --python-pip (#652, David Lindquist)
  - deb: Tests now try to make packages ensure we don't upset lintian (#648, Sam Crang)
  - rpm: Fix architecture targeting (#676, Rob Kinyon)
  - rpm: Allow --rpm-user and --rpm-group to override the user/group even if
    --rpm-use-file-permissions is enabled. (#679, Jordan Sissel)
  - gem: Add --gem-version-bins for appending the gem version to the file name
    of executable scripts a rubygem may install. (Jan Vansteenkiste)
  - python: Attempt to provide better error messages for known issues in python
    environments (#664, Jordan Sissel)

1.0.2 (January 10, 2013)
  - rpm: No longer converts - to _ in dependency strings (#603, Bulat
    Shakirzyanov)
  - Handle Darwin/OSX tar invocations (now tries 'gnutar' and 'gtar'). (Jordan
    Sissel)
  - Process $HOME/.fpm, and $PWD/.fpm in the correct order and allow CLI flags
    to override fpm config file settings. (#615, Jordan Sissel)
  - Don't leave empty gem bin paths in packages that don't need them (#612,
    Jordan Sissel)
  - deb: Make --deb-compression=gz work correctly (#616, #617; Evan Krall,
    Jason Yan)

1.0.1 (December 7, 2013)
  - deb: Correctly handle --config-files given with a leading / (Jordan Sissel)

1.0.0 (December 5, 2013)
  - Config file of flags is now supported. Searches for $HOME/.fpm and
    $PWD/.fpm. If both exist, $HOME is loaded first so $PWD can override.
    (Pranay Kanwar)
  - pkgin: Basic support for SmartOS/pkgsrc's pkgin format. (#567, Brian Akins)
  - cpan: catch more cases of perllocal.pod and delete them 
    (#510, Jordan Sissel)
  - cpan: Correctly support module version selection (#518, Matt Sharpe)
  - cpan: include builddeps in PERL5LIB when running cpan tests 
    (#500, Matt Sharpe)
  - cpan: Avoid old system perl modules when doing module builds 
    (#442, #513; Matt Sharpe)
  - python: safer gathering of python module dependencies.
  - python: better handling of unicode strings in python package metadata
    (#575, Bruno Renié)
  - cpan: Support 'http_proxy' env var. (#491, Patrick Cable)
  - deb: --deb-user and --deb-group both default to 'root' now 
    (#504, Pranay Kanwar)
  - deb: convert '>' to '>>' in deb version constraints 
    (#503, #439, Pranay Kanwar)
  - deb: Warn if epoch is set. Just so you know what's going on, since
    the default filename doesn't include the epoch. (#502, Pranay Kanwar)
  - deb,rpm: --config-files is now recursive if you give it a directory.
    This seems to be the most expected behavior by users. 
    (#171, #506; Pranay Kanwar)
  - dir: Respect -C when using path mapping (#498, #507; Pranay Kanwar)
  - rpm: Add --rpm-ignore-iteration-in-dependencies to let you to depend
    on any release (aka iteration) of the same version of a package.
    (#364, #508; Pranay Kanwar)
  - dir: Handle copying of special files when possible 
    (#347, #511, #539, #561; Pranay Kanwar)
  - rpm: Don't mistake symlinks as actual directories (#521, Nathan Huff)
  - npm: Choose an alternate npm registry with --npm-registry (#445, #524; 
    Matt Sharpe)
  - cpan: Choose an alternate cpan server with --cpan-mirror. Additionally, you
    can use --cpan-mirror-only to only use this mirror for metadata queries.
    (#524, Matt Sharpe)
  - deb: Fix broken --deb-changelog flag (#543, #544; Tray Torrance)
  - deb: When --deb-upstart is given, automatically create an upstart-sysv
    symlink /etc/init.d/<name> to /lib/init/upstart-job (#545, Igor Galić)
  - rpm: Fix bug when generating spec file listings on files with strange
    characters in the names. (#547, Chris Chandler)
  - dir: Fix bug where the new directory mapping feature would cause you not
    to be able to select files with '=' in the name for packaging. 
    (#556, #554; Pranay Kanwar)
  - python: Fix some unicode string issues in package metadata 
    (#575, Bruno Renié)
  - gem-rpm: Now respects the --gem-package-name-prefix when generating the
    'rubygem(name)' provides statement (#585, Stepan Stipl)
  - deb: Downcase and replace underscores with dashes in 'provides' list.
    (#591, Eric Connell)
  - deb: Fix a lintian complaint about md5sums permissions (#593, Sam Crang)
  - cpan: Modules with 'MYMETA' files are now supported (#573, Michael Donlon)

0.4.42 (July 23, 2013)
  - dir: make source=destination mappings behave the same way 'rsync -a' does
    with respect to source and destination paths.

0.4.41 (July 17, 2013)
  - cpan: handle cases where modules don't specify a license

Changes to README.md.

1
2
3
4
5
6
7
8
# Effing Package Management.

## Preface

Package maintainers work hard and take a lot of shit. You can't please
everyone. So, if you're a maintainer: Thanks for maintaining packages!

## What is fpm?
|







1
2
3
4
5
6
7
8
# Effing Package Management

## Preface

Package maintainers work hard and take a lot of shit. You can't please
everyone. So, if you're a maintainer: Thanks for maintaining packages!

## What is fpm?
58
59
60
61
62
63
64









65
66
67
68
69
70
71

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










## Get with the download

You can install fpm with gem:

    gem install fpm

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







>
>
>
>
>
>
>
>
>







58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

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 and gcc packages for your system. These can be installed with the below

    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:
91
92
93
94
95
96
97

98
99
100
101
102
103
104

Sources:

* gem (even autodownloaded for you)
* python modules (autodownload for you)
* pear (also downloads for you)
* directories

* rpm
* deb
* node packages (npm)

Targets:

* deb







>







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

Sources:

* gem (even autodownloaded for you)
* python modules (autodownload for you)
* pear (also downloads for you)
* directories
* tar(.gz) archives
* rpm
* deb
* node packages (npm)

Targets:

* deb
131
132
133
134
135
136
137
138




















































139
140
141
142
  the [mailing list](http://groups.google.com/group/fpm-users).
* If you want to send patches, best way is to fork this repo and send me a pull
  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)





















































## More Documentation

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









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




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
  the [mailing list](http://groups.google.com/group/fpm-users).
* If you want to send patches, best way is to fork this repo and send me a pull
  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:

    $ git clone git@github.com:yourusername/fpm.git

If you don't already have the bundler gem installed, install it now:

    $ gem install bundler

Now change to the root of the fpm repo and run:

    $ bundle install

This will install all of the dependencies required for running fpm from source.
Most importantly, you should see the following output from the bundle command
when it lists the fpm gem:

    ...
    Using json (1.8.1) 
    Using fpm (0.4.42) from source at .
    Using hitimes (1.2.1) 
    ...

Next, run make in root of the fpm repo.  If there are any problems (such as
missing dependencies) you should receive an error

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)

Changes to Vagrantfile.

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
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  # All Vagrant configuration is done here. The most common configuration
  # options are documented and commented below. For a complete reference,
  # please see the online documentation at vagrantup.com.

  config.vm.define "centos6" do |centos6|
    centos6.vm.box = "centos6"
    centos6.vm.box_url = "http://vagrant-jls.objects.dreamhost.com/CentOS-6.4-x86_64-minimal.box"
  end

  config.vm.define "debian6" do |centos6|
    centos6.vm.box = "debian6"


    centos6.vm.box_url = "http://vagrant-jls.objects.dreamhost.com/Debian-6.0.7-amd64-netboot.box"








  end

  config.vm.provision :puppet do |puppet|
    puppet.manifests_path = "test"
    puppet.manifest_file = "vagrant.pp"
  end

config.vm.define :smartos do |smartos|
    smartos.vm.box = "smartos-base1310-64-virtualbox-20130806.box"
    smartos.vm.box_url = "http://dlc-int.openindiana.org/aszeszo/vagrant/smartos-base1310-64-virtualbox-20130806.box"
  end

end










|


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







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


# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  # All Vagrant configuration is done here. The most common configuration
  # options are documented and commented below. For a complete reference,
  # please see the online documentation at vagrantup.com.

  config.vm.define "centos6" do |centos6|
    centos6.vm.box = "centos6"
    centos6.vm.box_url = "http://puppet-vagrant-boxes.puppetlabs.com/centos-65-x64-virtualbox-puppet.box"
  end

  config.vm.define "debian6" do |debian6|
    debian6.vm.box = "debian6"
    debian6.vm.box_url = "http://puppet-vagrant-boxes.puppetlabs.com/debian-607-x64-vbox4210.box"
  end

  config.vm.define "debian7" do |debian7|
    debian7.vm.box = "debian7"
    debian7.vm.box_url = "http://puppet-vagrant-boxes.puppetlabs.com/debian-73-x64-virtualbox-puppet.box"
  end

  config.vm.define :smartos do |smartos|
    smartos.vm.box = "smartos-base1310-64-virtualbox-20130806.box"
    smartos.vm.box_url = "http://dlc-int.openindiana.org/aszeszo/vagrant/smartos-base1310-64-virtualbox-20130806.box"
  end

  config.vm.provision :puppet do |puppet|
    puppet.manifests_path = "test"
    puppet.manifest_file = "vagrant.pp"
  end




end


Added examples/fpm-with-system-ruby/Makefile.



























































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
#
# 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
LIB_DIR=$(BUILD_DIR)/usr/lib/fpm
BIN_DIR=$(BUILD_DIR)/usr/bin
GEM_PATH:=$(shell readlink -f .)/build/gem
FPM_BIN=$(BIN_DIR)/fpm
BUNDLE_BIN=$(GEM_PATH)/bin/bundle
BUNDLE_CMD=$(RUBY_CMD) $(BUNDLE_BIN)
FPM_CMD=$(FPM_BIN)
GEM_CMD=$(RUBY_BIN) -S gem

.PHONY: clean
clean:
	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
	echo "#! $(RUBY_BIN)" > $(FPM_BIN)
	echo 'load File.dirname($$0) + "/../lib/fpm/bundle/bundler/setup.rb"' >> $(FPM_BIN)
	echo 'require "fpm"'  >> $(FPM_BIN)
	echo 'require "fpm/command"' >> $(FPM_BIN)
	echo 'exit(FPM::Command.run || 0)' >> $(FPM_BIN)
	chmod +x $(FPM_BIN)

.PHONY: install
install: $(CHECKOUT_DIR) $(BUNDLE_BIN) $(FPM_BIN)
	mkdir --parents $(LIB_DIR)
	cd $(CHECKOUT_DIR) && GEM_PATH=$(GEM_PATH) $(BUNDLE_CMD) install --without=development --standalone
	cd $(CHECKOUT_DIR) && gem build fpm.gemspec
	tar -xf $(CHECKOUT_DIR)/fpm*gem -C $(BUILD_DIR)
	tar --touch -xf $(BUILD_DIR)/data.tar.gz -C $(LIB_DIR)
	cp -r $(CHECKOUT_DIR)/bundle $(LIB_DIR)/bundle

.PHONY: package
package: install
	$(FPM_BIN) -s dir -t deb -n fpm -d $(RUBY_PACKAGE) -v $(VERSION) -C $(BUILD_DIR) usr

Added examples/fpm-with-system-ruby/README.md.





































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# About

 - build and install dependency on a ruby1.9 of some kind
 - does not need root to package
 - has its own GEM_DIR to keep its dependencies isolated
 - installation does not install any gems in to your ruby environment
 - installs in to standard locations /usr/{bin,lib}/fpm
 - doesn't depend on having fpm installed for packaging to work

# Dependencies

 - build-essential (perhaps more, but basically the standard packages you need
   for deb packaging)
 - ruby and ruby-dev

# Usage

 - $ cd examples/fpm-with-system-ruby && make package

Changes to fpm.gemspec.

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
  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.8") # license: Apache 2

  # For http stuff
  spec.add_dependency("ftw", "~> 0.0.30") # 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








|
<
<
<











|

<













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

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

Changes to lib/fpm/command.rb.

56
57
58
59
60
61
62



63
64
65
66
67
68
69
    "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'. " \







>
>
>







56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
    "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 "--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'. " \
97
98
99
100
101
102
103
104

105
106
107
108
109
110
111
112
113
    "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.",

    :multivalued => true, :attribute_name => :config_files
  option "--directories", "DIRECTORIES", "Mark a directory as being owned " \
    "by the package", :multivalued => true, :attribute_name => :directories
  option ["-a", "--architecture"], "ARCHITECTURE",
    "The architecture name. Usually matches 'uname -m'. For automatic values," \
    " you can use '-a all' or '-a native'. These two strings will be " \
    "translated into the correct value for your platform and target package type."
  option ["-m", "--maintainer"], "MAINTAINER",
    "The maintainer of this package.",







|
>

|







100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
    "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 " \
    "directory all files inside it will be recursively marked as config files.",
    :multivalued => true, :attribute_name => :config_files
  option "--directories", "DIRECTORIES", "Recursively mark a directory as being owned " \
    "by the package", :multivalued => true, :attribute_name => :directories
  option ["-a", "--architecture"], "ARCHITECTURE",
    "The architecture name. Usually matches 'uname -m'. For automatic values," \
    " you can use '-a all' or '-a native'. These two strings will be " \
    "translated into the correct value for your platform and target package type."
  option ["-m", "--maintainer"], "MAINTAINER",
    "The maintainer of this package.",
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
    "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







|
|







207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
    "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
230
231
232
233
234
235
236






237
238
239
240
241
242
243
    @dependencies = []
    @config_files = []
    @directories = []
  end # def initialize

  # Execute this command. See Clamp::Command#execute and Clamp's documentation
  def execute






    @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








>
>
>
>
>
>







234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
    @dependencies = []
    @config_files = []
    @directories = []
  end # def initialize

  # Execute this command. See Clamp::Command#execute and Clamp's documentation
  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

352
353
354
355
356
357
358










359
360
361
362
363
364
365

    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







>
>
>
>
>
>
>
>
>
>







362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385

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

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









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


    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 = [File.join(ENV['HOME'],'.fpm'), '.fpm']


    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|

          Shellwords.shellsplit(line).each do |e|


            ARGV << e
          end
        end
      end
    end

    super(*args)
  end # def run







>
>
>
>
>
>
>
>
>

|








>














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









|
>





>
|
>
>
|







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
      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
    if debug_workspace?
      # only emit them if they have files
      [input, output].each do |plugin|
        next if plugin.nil?
        [:staging_path, :build_path].each do |pathtype|
          path = plugin.send(pathtype)
          next unless Dir.open(path).to_a.size > 2
          @logger.log("plugin directory", :plugin => plugin.type, :pathtype => pathtype, :path => path)
        end
      end
    else
      input.cleanup unless input.nil?
      output.cleanup unless output.nil?
    end
  end # def execute

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

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

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

    super(*args)
  end # def run

Changes to lib/fpm/package.rb.

107
108
109
110
111
112
113


114
115
116
117
118
119
120

  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 = {}







>
>







107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122

  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
    @logger = Cabin::Channel.get

    # Attributes for this specific package 
    @attributes = {}
168
169
170
171
172
173
174

175
176
177
178
179
180
181
    @provides = []
    @conflicts = []
    @replaces = []
    @dependencies = []
    @scripts = {}
    @config_files = []
    @directories = []


    staging_path
    build_path
  end # def initialize

  # Get the 'type' for this instance.
  #







>







170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
    @provides = []
    @conflicts = []
    @replaces = []
    @dependencies = []
    @scripts = {}
    @config_files = []
    @directories = []
    @attrs = {}

    staging_path
    build_path
  end # def initialize

  # Get the 'type' for this instance.
  #
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
    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








|







197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
    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

367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
    if @attributes.include?(:prefix)
      installdir = staging_path(@attributes[:prefix])
    else
      installdir = staging_path
    end

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

      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)







|







370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
    if @attributes.include?(:prefix)
      installdir = staging_path(@attributes[:prefix])
    else
      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)
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







|

518
519
520
521
522
523
524
525
526

  # 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

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

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
        #"me know by filing an issue: " \
        #"https://github.com/jordansissel/fpm/issues"
    #end
    #require "ftw" # for http access
    require "net/http"
    require "json"




    result = search(package)
    tarball = download(result, version)
    moduledir = unpack(tarball)


    # Read package metadata (name, version, etc)
    if File.exists?(File.join(moduledir, "META.json"))
      metadata = JSON.parse(File.read(File.join(moduledir, ("META.json"))))
    elsif File.exists?(File.join(moduledir, ("META.yml")))
      require "yaml"
      metadata = YAML.load_file(File.join(moduledir, ("META.yml")))





    else
      raise FPM::InvalidPackageConfiguration, 
        "Could not find package metadata. Checked for META.json and META.yml"
    end
    self.version = metadata["version"]
    self.description = metadata["abstract"]

    self.license = case metadata["license"]
      when Array; metadata["license"].first
      when nil; "unknown"
      else; metadata["license"]
    end

    if metadata.include?("distribution")
      @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(", ") 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)

    if !attributes[:no_auto_depends?] 
      if metadata.include?("requires") && !metadata["requires"].nil?
        metadata["requires"].each do |dep_name, version|
          # Special case for representing perl core as a version.
          if dep_name == "perl"
            self.dependencies << "#{dep_name} >= #{version}"
            next
          end
          dep = search(dep_name)







>
>
>
|
|
|
>







>
>
>
>
>













|









>
>
|
>
>
>
|
>


















|







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
        #"me know by filing an issue: " \
        #"https://github.com/jordansissel/fpm/issues"
    #end
    #require "ftw" # for http access
    require "net/http"
    require "json"

    if (attributes[:cpan_local_module?])
      moduledir = package
    else
      result = search(package)
      tarball = download(result, version)
      moduledir = unpack(tarball)
    end

    # Read package metadata (name, version, etc)
    if File.exists?(File.join(moduledir, "META.json"))
      metadata = JSON.parse(File.read(File.join(moduledir, ("META.json"))))
    elsif File.exists?(File.join(moduledir, ("META.yml")))
      require "yaml"
      metadata = YAML.load_file(File.join(moduledir, ("META.yml")))
    elsif File.exists?(File.join(moduledir, "MYMETA.json"))
      metadata = JSON.parse(File.read(File.join(moduledir, ("MYMETA.json"))))
    elsif File.exists?(File.join(moduledir, ("MYMETA.yml")))
      require "yaml"
      metadata = YAML.load_file(File.join(moduledir, ("MYMETA.yml")))
    else
      raise FPM::InvalidPackageConfiguration, 
        "Could not find package metadata. Checked for META.json and META.yml"
    end
    self.version = metadata["version"]
    self.description = metadata["abstract"]

    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)

    if !attributes[:no_auto_depends?] 
      unless metadata["requires"].nil?
        metadata["requires"].each do |dep_name, version|
          # Special case for representing perl core as a version.
          if dep_name == "perl"
            self.dependencies << "#{dep_name} >= #{version}"
            next
          end
          dep = search(dep_name)
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
    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

    tarball = "#{distribution}-#{self.version}.tar.gz"


















    #url = "http://www.cpan.org/CPAN/authors/id/#{author[0,1]}/#{author[0,2]}/#{author}/#{tarball}"
    url = "http://www.cpan.org/authors/id/#{author[0,1]}/#{author[0,2]}/#{author}/#{tarball}"
    @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)







>















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

|







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

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

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
  option "--pre-depends", "DEPENDENCY",
    "Add DEPENDENCY as a Pre-Depends" do |dep|
    @pre_depends ||= []
    @pre_depends << dep
  end

  option "--compression", "COMPRESSION", "The compression type to use, must " \
    "be one of #{COMPRESSION_TYPES.join(", ")}.", :default => "gzip" do |value|
    if !COMPRESSION_TYPES.include?(value)
      raise ArgumentError, "deb compression value of '#{value}' is invalid. " \
        "Must be one of #{COMPRESSION_TYPES.join(", ")}"
    end
    value
  end








|







37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
  option "--pre-depends", "DEPENDENCY",
    "Add DEPENDENCY as a Pre-Depends" do |dep|
    @pre_depends ||= []
    @pre_depends << dep
  end

  option "--compression", "COMPRESSION", "The compression type to use, must " \
    "be one of #{COMPRESSION_TYPES.join(", ")}.", :default => "gz" do |value|
    if !COMPRESSION_TYPES.include?(value)
      raise ArgumentError, "deb compression value of '#{value}' is invalid. " \
        "Must be one of #{COMPRESSION_TYPES.join(", ")}"
    end
    value
  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
    "automatically" do |value|
    value.to_i
  end

  option "--priority", "PRIORITY", 
    "The debian package 'priority' value.", :default => "extra"




  option "--user", "USER", "The owner of files in this package", :default => 'root'

  option "--group", "GROUP", "The group owner of files in this package", :default => 'root'

  option "--changelog", "FILEPATH", "Add FILEPATH as debian changelog" do |file|
    File.expand_path(file)
  end

  option "--recommends", "PACKAGE", "Add PACKAGE to Recommends" do |pkg|
    @recommends ||= []
    @recommends << pkg
    next @recommends
  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







>
>
>



















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







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
    "automatically" do |value|
    value.to_i
  end

  option "--priority", "PRIORITY", 
    "The debian package 'priority' value.", :default => "extra"

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

  option "--user", "USER", "The owner of files in this package", :default => 'root'

  option "--group", "GROUP", "The group owner of files in this package", :default => 'root'

  option "--changelog", "FILEPATH", "Add FILEPATH as debian changelog" do |file|
    File.expand_path(file)
  end

  option "--recommends", "PACKAGE", "Add PACKAGE to Recommends" do |pkg|
    @recommends ||= []
    @recommends << pkg
    next @recommends
  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
221
222
223
224
225
226
227
228



229
230
231
232
233
234
235
      self.architecture = parse.call("Architecture")
      self.category = parse.call("Section")
      self.license = parse.call("License") || self.license
      self.maintainer = parse.call("Maintainer")
      self.name = parse.call("Package")
      self.url = parse.call("Homepage")
      self.vendor = parse.call("Vendor") || self.vendor
      self.provides = parse.call("Provides") || self.provides




      # The description field is a special flower, parse it that way.
      # The description is the first line as a normal Description field, but also continues
      # on future lines indented by one space, until the end of the file. Blank
      # lines are marked as ' .'
      description = control[/^Description: .*/m].split(": ", 2).last
      self.description = description.gsub(/^ /, "").gsub(/^\.$/, "")







|
>
>
>







242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
      self.architecture = parse.call("Architecture")
      self.category = parse.call("Section")
      self.license = parse.call("License") || self.license
      self.maintainer = parse.call("Maintainer")
      self.name = parse.call("Package")
      self.url = parse.call("Homepage")
      self.vendor = parse.call("Vendor") || self.vendor
      with(parse.call("Provides")) do |provides_str|
        next if provides_str.nil?
        self.provides = provides_str.split(/\s*,\s*/)
      end

      # The description field is a special flower, parse it that way.
      # The description is the first line as a normal Description field, but also continues
      # on future lines indented by one space, until the end of the file. Blank
      # lines are marked as ' .'
      description = control[/^Description: .*/m].split(": ", 2).last
      self.description = description.gsub(/^ /, "").gsub(/^\.$/, "")
293
294
295
296
297
298
299

300
301
302
303
304
305
306

    # unpack the data.tar.{gz,bz2,xz} from the deb package into staging_path
    safesystem("ar p #{package} #{datatar} " \
               "| tar #{compression} -xf - -C #{staging_path}")
  end # def extract_files

  def output(output_path)

    output_check(output_path)
    # 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







>







317
318
319
320
321
322
323
324
325
326
327
328
329
330
331

    # unpack the data.tar.{gz,bz2,xz} from the deb package into staging_path
    safesystem("ar p #{package} #{datatar} " \
               "| tar #{compression} -xf - -C #{staging_path}")
  end # def extract_files

  def output(output_path)
    self.provides = self.provides.collect { |p| fix_provides(p) }
    output_check(output_path)
    # 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
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
      end
    end

    write_control_tarball

    # Tar up the staging_path into data.tar.{compression type}
    case self.attributes[:deb_compression]
      when "gzip", 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

    tar_flags = []
    if !attributes[:deb_user].nil?
      if attributes[:deb_user] == 'root'
        tar_flags += [ "--numeric-owner", "--owner", "0" ]
      else
        tar_flags += [ "--owner", attributes[:deb_user] ]
      end
    end

    if !attributes[:deb_group].nil?
      if attributes[:deb_group] == 'root'
        tar_flags += [ "--numeric-owner", "--group", "0" ]
      else
        tar_flags += [ "--group", attributes[:deb_group] ]
      end
    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







|













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







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

      # Install an init.d shim that calls upstart
      dest_init = staging_path("/etc/init.d/#{name}")
      FileUtils.mkdir_p(File.dirname(dest_init))
      FileUtils.ln_s("/lib/init/upstart-job", dest_init)
    end

    args = [ tar_cmd, "-C", staging_path, compression ] + tar_flags + [ "-cf", datatar, "." ]
    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
    @logger.log("Created deb package", :path => output_path)
  end # def output

  def converted_from(origin)
    self.dependencies = self.dependencies.collect do |dep|
      fix_dependency(dep)
    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







|









<






>
>
>
>







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

      # Install an init.d shim that calls upstart
      dest_init = staging_path("/etc/init.d/#{name}")
      FileUtils.mkdir_p(File.dirname(dest_init))
      FileUtils.ln_s("/lib/init/upstart-job", dest_init)
    end

    args = [ tar_cmd, "-C", staging_path, compression ] + data_tar_flags + [ "-cf", datatar, "." ]
    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
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
    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 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]







|







462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
    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]
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
      name, version = m[1..2]
      return ["#{name} (>> #{version})"]
    else
      # otherwise the dep is probably fine
      return dep.rstrip
    end
  end # def fix_dependency


















  def control_path(path=nil)
    @control_path ||= build_path("control")
    FileUtils.mkdir(@control_path) if !File.directory?(@control_path)

    if path.nil?
      return @control_path
    else
      return File.join(@control_path, path)
    end
  end # def control_path

  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, 







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



















>
>







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
      name, version = m[1..2]
      return ["#{name} (>> #{version})"]
    else
      # otherwise the dep is probably fine
      return dep.rstrip
    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)
    @control_path ||= build_path("control")
    FileUtils.mkdir(@control_path) if !File.directory?(@control_path)

    if path.nil?
      return @control_path
    else
      return File.join(@control_path, path)
    end
  end # def control_path

  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, 
554
555
556
557
558
559
560

561
562
563
564
565
566
567
      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







>







584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
      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
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

  def write_conffiles
    return unless config_files.any?

    # scan all conf file paths for files and add them
    allconfigs = []
    config_files.each do |path|


      cfg_path = File.expand_path(path, staging_path)
      begin
        Find.find(cfg_path) do |p|
          allconfigs << p.gsub("#{staging_path}/", '') if File.file? p
        end
      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 { |cf| out.puts(cf) }





    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|







>
>












|
>
|
|
|
>
>
>
>
>







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
649

  def write_conffiles
    return unless config_files.any?

    # scan all conf file paths for files and add them
    allconfigs = []
    config_files.each do |path|
      # Strip leading /
      path = path[1..-1] if path[0,1] == "/"
      cfg_path = File.expand_path(path, staging_path)
      begin
        Find.find(cfg_path) do |p|
          allconfigs << p.gsub("#{staging_path}/", '') if File.file? p
        end
      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|
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
649
650
651
652
653






















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

    end
  end # def write_md5sums

  def to_s(format=nil)
    # Default format if nil
    # git_1.7.9.3-1_amd64.deb
    return super("NAME_FULLVERSION_ARCH.TYPE") if format.nil?
    return super(format)
  end # def to_s























  public(:input, :output, :architecture, :name, :prefix, :converted_from, :to_s)
end # class FPM::Target::Deb







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


















>










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

658
659
660
661
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
    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)
    # Default format if nil
    # git_1.7.9.3-1_amd64.deb
    return super("NAME_FULLVERSION_ARCH.TYPE") if format.nil?
    return super(format)
  end # def to_s

  def data_tar_flags
    data_tar_flags = []
    if attributes[:deb_use_file_permissions?].nil?
      if !attributes[:deb_user].nil?
        if attributes[:deb_user] == 'root'
          data_tar_flags += [ "--numeric-owner", "--owner", "0" ]
        else
          data_tar_flags += [ "--owner", attributes[:deb_user] ]
        end
      end

      if !attributes[:deb_group].nil?
        if attributes[:deb_group] == 'root'
          data_tar_flags += [ "--numeric-owner", "--group", "0" ]
        else
          data_tar_flags += [ "--group", attributes[:deb_group] ]
        end
      end
    end
    return data_tar_flags
  end # def data_tar_flags

  public(:input, :output, :architecture, :name, :prefix, :converted_from, :to_s, :data_tar_flags)
end # class FPM::Target::Deb

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

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
  def input(path)
    chdir = attributes[:chdir] || "."

    # Support mapping source=dest
    # This mapping should work the same way 'rsync -a' does
    #   Meaning 'rsync -a source dest'
    #   and 'source=dest' in fpm work the same as the above rsync
    if path =~ /.=./
      origin, destination = path.split("=", 2)

      if File.directory?(origin) && origin[-1,1] == "/"
        chdir = chdir == '.' ? origin : File.join(chdir, origin)
        source = "."
      else
        origin_dir = File.dirname(origin)







|







39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
  def input(path)
    chdir = attributes[:chdir] || "."

    # Support mapping source=dest
    # This mapping should work the same way 'rsync -a' does
    #   Meaning 'rsync -a source dest'
    #   and 'source=dest' in fpm work the same as the above rsync
    if path =~ /.=./ && !File.exists?(chdir == '.' ? path : File.join(chdir, path))
      origin, destination = path.split("=", 2)

      if File.directory?(origin) && origin[-1,1] == "/"
        chdir = chdir == '.' ? origin : File.join(chdir, origin)
        source = "."
      else
        origin_dir = File.dirname(origin)
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    @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








|







67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    @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

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

34
35
36
37
38
39
40





41
42
43
44
45
46
47
    :default => true
  option "--fix-dependencies", :flag, "Should the package dependencies be " \
    "prefixed?", :default => true
  option "--env-shebang", :flag, "Should the target package have the " \
    "shebang rewritten to use env?", :default => true

  option "--prerelease", :flag, "Allow prerelease versions of a gem", :default => false






  def input(gem)
    # 'arg'  is the name of the rubygem we should unpack.
    path_to_gem = download_if_necessary(gem, version)

    # Got a good gem now (downloaded or otherwise)
    #







>
>
>
>
>







34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
    :default => true
  option "--fix-dependencies", :flag, "Should the package dependencies be " \
    "prefixed?", :default => true
  option "--env-shebang", :flag, "Should the target package have the " \
    "shebang rewritten to use env?", :default => true

  option "--prerelease", :flag, "Allow prerelease versions of a gem", :default => false
  option "--disable-dependency", "gem_name",
    "The gem name to remove from dependency list",
    :multivalued => true, :attribute_name => :gem_disable_dependencies

  option "--version-bins", :flag, "Append the version to the bins", :default => false

  def input(gem)
    # 'arg'  is the name of the rubygem we should unpack.
    path_to_gem = download_if_necessary(gem, version)

    # Got a good gem now (downloaded or otherwise)
    #
143
144
145
146
147
148
149




150
151
152
153
154
155
156
          reqs = dep.requirement.to_s
        else
          reqs = dep.version_requirements
        end

        # Some reqs can be ">= a, < b" versions, let's handle that.
        reqs.to_s.split(/, */).each do |req|




          if attributes[:gem_fix_dependencies?]
            name = fix_name(dep.name)
          else
            name = dep.name
          end
          self.dependencies << "#{name} #{req}"
        end







>
>
>
>







148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
          reqs = dep.requirement.to_s
        else
          reqs = dep.version_requirements
        end

        # Some reqs can be ">= a, < b" versions, let's handle that.
        reqs.to_s.split(/, */).each do |req|
          if attributes[:gem_disable_dependencies]
            next if attributes[:gem_disable_dependencies].include?(dep.name)
          end

          if attributes[:gem_fix_dependencies?]
            name = fix_name(dep.name)
          else
            name = dep.name
          end
          self.dependencies << "#{name} #{req}"
        end
182
183
184
185
186
187
188

















189
190
191
192
193
194
195
196
197
198
      bin_path = File.join(staging_path, gem_bin)
    end

    args += ["--bindir", bin_path]
    ::FileUtils.mkdir_p(bin_path)
    args << gem_path
    safesystem(*args)

















  end # def install_to_staging
  
  # Sanitize package name.
  # This prefixes the package name with 'rubygem' (but depends on the attribute
  # :gem_package_name_prefix
  def fix_name(name)
    return [attributes[:gem_package_name_prefix], name].join("-")
  end # def fix_name
  public(:input, :output)
end # class FPM::Package::Gem







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










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
      bin_path = File.join(staging_path, gem_bin)
    end

    args += ["--bindir", bin_path]
    ::FileUtils.mkdir_p(bin_path)
    args << gem_path
    safesystem(*args)

    # 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.
  # This prefixes the package name with 'rubygem' (but depends on the attribute
  # :gem_package_name_prefix
  def fix_name(name)
    return [attributes[:gem_package_name_prefix], name].join("-")
  end # def fix_name
  public(:input, :output)
end # class FPM::Package::Gem

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

102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
    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
          @logger.warn("error replacing staging_path in file", :replace_in => path)
        end
      end
      FileUtils.rm_r(path) if delete_these.include?(File.basename(path))
    end

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







|
|







102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
    delete_these = [".depdb", ".depdblock", ".filemap", ".lock", ".channel", "cache", "temp", "download", ".channels", ".registry"]
    Find.find(staging_path) do |path|
      if File.file?(path)
        @logger.info("replacing staging_path in file", :replace_in => path, :staging_path => staging_path)
        begin
          content = File.read(path).gsub(/#{Regexp.escape(staging_path)}/, "")
          File.write(path, content)
        rescue ArgumentError => e
          @logger.warn("error replacing staging_path in file", :replace_in => path, :error => e)
        end
      end
      FileUtils.rm_r(path) if delete_these.include?(File.basename(path))
    end

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

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

59
60
61
62
63
64
65






66
67
68
69
70
71
72
    "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
  option "--obey-requirements-txt", :flag, "Use a requirements.txt file " \
    "in the top-level directory of the python package for dependency " \
    "detection.", :default => false







  private

  # Input a package.
  #
  # The 'package' can be any of:
  #







>
>
>
>
>
>







59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    "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
  option "--obey-requirements-txt", :flag, "Use a requirements.txt file " \
    "in the top-level directory of the python package for dependency " \
    "detection.", :default => false
  option "--scripts-executable", "PYTHON_EXECUTABLE", "Set custom python " \
    "interpreter in installing scripts. By default distutils will replace " \
    "python interpreter in installing scripts (specified by shebang) with " \
    "current python interpreter (sys.executable). This option is equivalent " \
    "to appending 'build_scripts --executable PYTHON_EXECUTABLE' arguments " \
    "to 'setup.py install' command."

  private

  # Input a package.
  #
  # The 'package' can be any of:
  #
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
      # 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",
                 "-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}"
    end
    return dirs.first
  end # def download

  # Load the package information like name, version, dependencies.
  def load_package_info(setup_py)
    if !attributes[:python_package_prefix].nil?
      attributes[:python_package_name_prefix] = attributes[:python_package_prefix]
    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
    # in the same directory.
    setup_dir = File.dirname(setup_py)







|
<
















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







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
      # 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}"
    end
    return dirs.first
  end # def download

  # Load the package information like name, version, dependencies.
  def load_package_info(setup_py)
    if !attributes[:python_package_prefix].nil?
      attributes[:python_package_name_prefix] = attributes[:python_package_prefix]
    end

    begin 
      json_test_code = [
        "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
    # in the same directory.
    setup_dir = File.dirname(setup_py)
267
268
269
270
271
272
273





274
275
276
277
278
279
280

      if !attributes[:python_install_bin].nil?
        flags += [ "--install-scripts", File.join(prefix, attributes[:python_install_bin]) ]
      elsif !attributes[:prefix].nil?
        # prefix given, but not python_install_bin, assume PREFIX/bin
        flags += [ "--install-scripts", File.join(prefix, "bin") ]
      end






      safesystem(attributes[:python_bin], "setup.py", "install", *flags)
    end
  end # def install_to_staging

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







>
>
>
>
>







292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310

      if !attributes[:python_install_bin].nil?
        flags += [ "--install-scripts", File.join(prefix, attributes[:python_install_bin]) ]
      elsif !attributes[:prefix].nil?
        # prefix given, but not python_install_bin, assume PREFIX/bin
        flags += [ "--install-scripts", File.join(prefix, "bin") ]
      end

      if !attributes[:python_scripts_executable].nil?
        # Overwrite installed python scripts shebang binary with provided executable
        flags += [ "build_scripts", "--executable", attributes[:python_scripts_executable] ]
      end

      safesystem(attributes[:python_bin], "setup.py", "install", *flags)
    end
  end # def install_to_staging

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

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

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

  option "--user", "USER",
    "Set the user to USER in the %files section.", 
    :default => 'root' do |value|
      value
  end

  option "--group", "GROUP",
    "Set the group to GROUP in the %files section.",
    :default => 'root' do |value|
      value
  end

  option "--defattrfile", "ATTR",
    "Set the default file mode (%defattr).",
    :default => '-' do |value|
      value
  end








|

<
|
<
<
<

<
|
<
<
<







26
27
28
29
30
31
32
33
34

35



36

37



38
39
40
41
42
43
44
    "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."


  option "--user", "USER", "Set the user to USER in the %files section. Overrides the user when used with use-file-permissions setting."





  option "--group", "GROUP", "Set the group to GROUP in the %files section. Overrides the group when used with use-file-permissions setting."




  option "--defattrfile", "ATTR",
    "Set the default file mode (%defattr).",
    :default => '-' do |value|
      value
  end

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
  option "--changelog", "FILEPATH", "Add changelog from FILEPATH contents" do |file|
    File.read(File.expand_path(file))
  end

  option "--sign", :flag, "Pass --sign to rpmbuild"

  option "--auto-add-directories", :flag, "Auto add directories not part of filesystem"




  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(/^\//,''))



      user = Etc.getpwuid(stat.uid).name
      group = Etc.getgrgid(stat.gid).name
      mode = stat.mode
      return sprintf("%%attr(%o, %s, %s) %s\n", mode & 4095 , user, group, file)
    end
  end


  # Handle any architecture naming conversions.







>
>
>





>
>
>
>


















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


















>
|
>
>
>
>
>
>
>
>
>




>
>
>
|
|







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
  option "--changelog", "FILEPATH", "Add changelog from FILEPATH contents" do |file|
    File.read(File.expand_path(file))
  end

  option "--sign", :flag, "Pass --sign to rpmbuild"

  option "--auto-add-directories", :flag, "Auto add directories not part of filesystem"
  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

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


  # Handle any architecture naming conversions.
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
  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|
        first, remainder = provides.split("-", 2)
        if first == "rubygem"
          name, remainder = remainder.split(" ", 2)
          # yield rubygem(name)...

          "rubygem(#{name})#{remainder ? " #{remainder}" : ""}"
        else
          provides
        end
      end

      self.dependencies = self.dependencies.collect do |dependency|
        first, remainder = dependency.split("-", 2)
        if first == "rubygem"
          name, remainder = remainder.split(" ", 2)

          "rubygem(#{name})#{remainder ? " #{remainder}" : ""}"
        else
          dependency
        end
      end
      #self.provides << "rubygem(#{self.name})"

    end

    # Convert != dependency as Conflict =, as rpm doesn't understand !=
    self.dependencies = self.dependencies.select do |dep|
      name, op, version = dep.split(/\s+/)
      dep_ok = true
      if op == '!='
        self.conflicts << "#{name} = #{version}"
        dep_ok = false
      end
      dep_ok
    end

    # Convert any dashes in version strings to underscores.
    self.dependencies = self.dependencies.collect do |dep|
      name, op, version = dep.split(/\s+/)
      if !version.nil? and version.include?("-")
        @logger.warn("Dependency package '#{name}' version '#{version}' " \
                     "includes dashes, converting to underscores")
        version = version.gsub(/-/, "_")
        "#{name} #{op} #{version}"
      else
        dep
      end
    end

    # if --ignore-iteration-in-dependencies is true convert foo = X, to
    # foo >= X , foo < X+1
    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|







<


<
<
<
<
<
<
>
|
<






<
|
<
|
>
|




>
|
<
|
|
>
|
|
|
|
|
<
>













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

















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







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
  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+/)
      dep_ok = true
      if op == '!='
        self.conflicts << "#{name} = #{version}"
        dep_ok = false
      end
      dep_ok
    end














    # if --ignore-iteration-in-dependencies is true convert foo = X, to
    # foo >= X , foo < X+1
    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 input(path)
    rpm = ::RPM::File.new(path)

    tags = {}
    rpm.header.tags.each do |tag|
277
278
279
280
281
282
283



284
285
286
287
288
289
290
    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?]







>
>
>







302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
    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]}
    # TODO(sissel): put 'trigger scripts' stuff into attributes

    if !attributes[:no_auto_depends?]
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
362
363
364
365
366

367
368
369
370
371
372
373
374
375
376
377
378
379
380
    # this feature
    #self.directories += rpm.directories

    # Extract to the staging directory
    rpm.extract(staging_path)
  end # def input





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





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


      Find.find(staging_path) do |path|
        next if path == staging_path
        if File.directory? path and !File.symlink? path
          add_path = path.gsub(/^#{staging_path}/,'')
          self.directories << add_path if not fs_dirs.include? add_path
        end
      end
    else
      self.directories = self.directories.map { |x| File.join(self.prefix, x) }
      alldirs = []
      self.directories.each do |path|
        Find.find(File.join(staging_path, path)) do |subpath|
          if File.directory? subpath and !File.symlink? subpath
            alldirs << subpath.gsub(/^#{staging_path}/, '')
          end
        end
      end
      self.directories = alldirs
    end

    # scan all conf file paths for files and add them
    allconfigs = []
    self.config_files.each do |path|
      cfg_path = File.expand_path(path, staging_path)

      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(self.prefix, 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|







>
>
>
>




>
>
>
>




>
>
>
>








>







>









|














|
>






|







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
415
416
417
418
419
420
421
422
423
    # this feature
    #self.directories += rpm.directories

    # Extract to the staging directory
    rpm.extract(staging_path)
  end # def input

  def prefixed_path(path)
    Pathname.new(path).absolute?() ? path : File.join(self.prefix, path)
  end # def prefixed_path

  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 }
      fs_dirs.concat((attributes[:auto_add_exclude_directories] or []))

      Find.find(staging_path) do |path|
        next if path == staging_path
        if File.directory? path and !File.symlink? path
          add_path = path.gsub(/^#{staging_path}/,'')
          self.directories << add_path if not fs_dirs.include? add_path
        end
      end
    else
      self.directories = self.directories.map { |x| self.prefixed_path(x) }
      alldirs = []
      self.directories.each do |path|
        Find.find(File.join(staging_path, path)) do |subpath|
          if File.directory? subpath and !File.symlink? subpath
            alldirs << subpath.gsub(/^#{staging_path}/, '')
          end
        end
      end
      self.directories = alldirs
    end

    # scan all conf file paths for files and add them
    allconfigs = []
    self.config_files.each do |path|
      cfg_path = File.join(staging_path, path)
      raise "Config file path #{cfg_path} does not exist" unless File.exist?(cfg_path)
      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|
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
    @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

    @logger.log("Created rpm", :path => output_path)
  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 prefix

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







<
<









|







437
438
439
440
441
442
443


444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
    @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
444
445
446
447
448
449
450
451
452

  def digest_algorithm
    return DIGEST_ALGORITHM_MAP[attributes[:rpm_digest]]
  end # def digest_algorithm

  public(:input, :output, :converted_from, :architecture, :to_s, :iteration,
         :payload_compression, :digest_algorithm, :prefix, :build_sub_dir,
         :epoch, :version)
end # class FPM::Package::RPM







|

485
486
487
488
489
490
491
492
493

  def digest_algorithm
    return DIGEST_ALGORITHM_MAP[attributes[:rpm_digest]]
  end # def digest_algorithm

  public(:input, :output, :converted_from, :architecture, :to_s, :iteration,
         :payload_compression, :digest_algorithm, :prefix, :build_sub_dir,
         :epoch, :version, :prefixed_path)
end # class FPM::Package::RPM

Added lib/fpm/package/sh.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
require "erb"
require "fpm/namespace"
require "fpm/package"
require "fpm/errors"
require "fpm/util"
require "backports"
require "fileutils"
require "digest"

# Support for self extracting sh files (.sh files)
#
# This class only supports output of packages.
#
# The sh package is a single sh file with a bzipped tar payload concatenated to the end.
# The script can unpack the tarball to install it and call optional post install scripts.
class FPM::Package::Sh < FPM::Package

  def output(output_path)
    create_scripts

    # Make one file. The installscript can unpack itself.
    `cat #{install_script} #{payload} > #{output_path}`
    FileUtils.chmod("+x", output_path)
  end

  def create_scripts
    if script?(:before_install)
      # the scripts are kept in the payload so what would before install be if we've already
      # unpacked the payload?
      raise "sh package does not support before install scripts."
    end

    if script?(:after_install)
      File.write(File.join(fpm_meta_path, "after_install"), script(:after_install))
    end
  end

  def install_script
    path = build_path("installer.sh")
    File.open(path, "w") do |file|
      file.write template("sh.erb").result(binding)
    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",
             "--group=0",
             "--numeric-owner",
             "." ]

    unless safesystem(*args)
      raise "Command failed while creating payload tar: #{args}"
    end
    payload_tar
  end

  # Where we keep metadata and post install scripts and such
  def fpm_meta_path
    @fpm_meta_path ||= begin
                         path = File.join(staging_path, ".fpm")
                         FileUtils.mkdir_p(path)
                         path
                       end
  end
end

Added lib/fpm/package/zip.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
require "backports" # gem backports
require "fpm/package"
require "fpm/util"
require "fileutils"
require "fpm/package/dir"

# Use a zip as a package.
#
# This provides no metadata. Both input and output are supported.
class FPM::Package::Zip < FPM::Package

  # Input a zipfile.
  def input(input_path)
    # use part of the filename as the package name
    self.name = File.extname(input_path)[1..-1]

    realpath = Pathname.new(input_path).realpath.to_s
    ::Dir.chdir(build_path) do
      safesystem("unzip", realpath)
    end

    # use dir to set stuff up properly, mainly so I don't have to reimplement
    # the chdir/prefix stuff special for zip.
    dir = convert(FPM::Package::Dir)
    if attributes[:chdir]
      dir.attributes[:chdir] = File.join(build_path, attributes[:chdir])
    else
      dir.attributes[:chdir] = build_path
    end

    cleanup_staging
    # Tell 'dir' to input "." and chdir/prefix will help it figure out the
    # rest.
    dir.input(".")
    @staging_path = dir.staging_path
    dir.cleanup_build
  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

Changes to lib/fpm/util.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
require "fpm/namespace"
require "childprocess"
require "ffi"

# Some utility functions
module FPM::Util
  extend FFI::Library
  ffi_lib FFI::Library::LIBC

  # mknod is __xmknod in glibc

  begin
    attach_function :mknod, :mknod, [:string, :uint32, :ulong], :int
  rescue FFI::NotFoundError

    attach_function :mknod, :__xmknod, [:string, :uint32, :ulong], :int
  end

  # Raised if safesystem cannot find the program to run.
  class ExecutableNotFound < StandardError; end

  # Raised if a safesystem program exits nonzero
  class ProcessFailed < StandardError; end

  # Is the given program in the system's PATH?
  def program_in_path?(program)
    # Scan path to find the executable
    # Do this to help the user get a better error message.
    envpath = ENV["PATH"].split(":")
    return envpath.select { |p| File.executable?(File.join(p, program)) }.any?
  end # def program_in_path














  # Run a command safely in a way that gets reports useful errors.
  def safesystem(*args)
    # ChildProcess isn't smart enough to run a $SHELL if there's
    # spaces in the first arg and there's only 1 arg.
    if args.size == 1
      args = [ ENV["SHELL"], "-c", args[0] ]
    end
    program = args[0]

    # Scan path to find the executable
    # Do this to help the user get a better error message.
    if !program.include?("/") and !program_in_path?(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









|
>

|

>
|















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






|



<
<
|







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
require "fpm/namespace"
require "childprocess"
require "ffi"

# Some utility functions
module FPM::Util
  extend FFI::Library
  ffi_lib FFI::Library::LIBC

  # mknod is __xmknod in glibc a wrapper around mknod to handle
  # various stat struct formats. See bits/stat.h in glibc source
  begin
    attach_function :mknod, :mknod, [:string, :uint, :ulong], :int
  rescue FFI::NotFoundError
    # glibc/io/xmknod.c int __xmknod (int vers, const char *path, mode_t mode, dev_t *dev)
    attach_function :xmknod, :__xmknod, [:int, :string, :uint, :pointer], :int
  end

  # Raised if safesystem cannot find the program to run.
  class ExecutableNotFound < StandardError; end

  # Raised if a safesystem program exits nonzero
  class ProcessFailed < StandardError; end

  # Is the given program in the system's PATH?
  def program_in_path?(program)
    # Scan path to find the executable
    # Do this to help the user get a better error message.
    envpath = ENV["PATH"].split(":")
    return envpath.select { |p| File.executable?(File.join(p, program)) }.any?
  end # def program_in_path

  def program_exists?(program)
    # Scan path to find the executable
    # Do this to help the user get a better error message.
    return program_in_path?(program) if !program.include?("/") 
    return File.executable?(program)
  end # def program_exists?

  def default_shell
    shell = ENV["SHELL"] 
    return "/bin/sh" if shell.nil? || shell.empty?
    return shell
  end

  # Run a command safely in a way that gets reports useful errors.
  def safesystem(*args)
    # ChildProcess isn't smart enough to run a $SHELL if there's
    # spaces in the first arg and there's only 1 arg.
    if args.size == 1
      args = [ default_shell, "-c", args[0] ]
    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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    if !success
      raise ProcessFailed.new("#{program} failed (exit code #{process.exit_code})" \
                              ". Full command was:#{args.inspect}")
    end
    return success
  end # def safesystem

# Run a command safely in a way that captures output and status.
  def safesystemout(*args)
    if args.size == 1
      args = [ ENV["SHELL"], "-c", args[0] ]
    end
    program = args[0]

    if !program.include?("/") and !program_in_path?(program)







|







80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
    if !success
      raise ProcessFailed.new("#{program} failed (exit code #{process.exit_code})" \
                              ". Full command was:#{args.inspect}")
    end
    return success
  end # def safesystem

  # Run a command safely in a way that captures output and status.
  def safesystemout(*args)
    if args.size == 1
      args = [ ENV["SHELL"], "-c", args[0] ]
    end
    program = args[0]

    if !program.include?("/") and !program_in_path?(program)
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
  # Get the recommended 'tar' command for this platform.
  def tar_cmd
    # Rely on gnu tar for solaris and OSX.
    case %x{uname -s}.chomp
    when "SunOS"
      return "gtar"
    when "Darwin"

      return "gnutar"



    else
      return "tar"
    end
  end # def tar_cmd

  # Run a block with a value.
  # Useful in lieu of assigning variables 
  def with(value, &block)
    block.call(value)
  end # def with














  def copy_entry(src, dst)
    case File.ftype(src)
    when 'fifo'
    when 'characterSpecial'
    when 'blockSpecial'
    when 'socket'
      st = File.stat(src)
      rc = mknod(dst, st.mode, st.dev)
      raise SystemCallError.new("mknod error", FFI.errno) if rc == -1


    else







      FileUtils.copy_entry(src, dst)

    end































  end















end # module FPM::Util







>
|
>
>
>










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



|
<
<
<

|

>
>

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

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
  # Get the recommended 'tar' command for this platform.
  def tar_cmd
    # Rely on gnu tar for solaris and OSX.
    case %x{uname -s}.chomp
    when "SunOS"
      return "gtar"
    when "Darwin"
      # Try running gnutar, it was renamed(??) in homebrew to 'gtar' at some point, I guess? I don't know.
      ["gnutar", "gtar"].each do |tar|
        system("#{tar} > /dev/null 2> /dev/null")
        return tar unless $?.exitstatus == 127
      end
    else
      return "tar"
    end
  end # def tar_cmd

  # Run a block with a value.
  # Useful in lieu of assigning variables 
  def with(value, &block)
    block.call(value)
  end # def with

  # wrapper around mknod ffi calls
  def mknod_w(path, mode, dev)
    rc = -1
    case %x{uname -s}.chomp
    when 'Linux'
      # bits/stat.h #define _MKNOD_VER_LINUX  0
      rc = xmknod(0, path, mode, FFI::MemoryPointer.new(dev))
    else
      rc = mknod(path, mode, dev)
    end
    rc
  end

  def copy_entry(src, dst)
    case File.ftype(src)
    when 'fifo', 'characterSpecial', 'blockSpecial', 'socket'



      st = File.stat(src)
      rc = mknod_w(dst, st.mode, st.dev)
      raise SystemCallError.new("mknod error", FFI.errno) if rc == -1
    when 'directory'
      FileUtils.mkdir(dst) unless File.exists? dst
    else
      # if the file with the same dev and inode has been copied already -
      # hard link it's copy to `dst`, otherwise make an actual copy
      st = File.lstat(src)
      known_entry = copied_entries[[st.dev, st.ino]]
      if known_entry
        FileUtils.ln(known_entry, dst)
      else
        FileUtils.copy_entry(src, dst)
        copied_entries[[st.dev, st.ino]] = dst
      end
    end # else...
  end # def copy_entry

  def copied_entries
    # TODO(sissel): I wonder that this entry-copy knowledge needs to be put
    # into a separate class/module. As is, calling copy_entry the same way
    # in slightly different contexts will result in weird or bad behavior.
    # What I mean is if we do:
    #   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
end # module FPM::Util

Changes to lib/fpm/version.rb.

1
2
3
module FPM
  VERSION = "0.4.42"
end

|

1
2
3
module FPM
  VERSION = "1.2.0"
end

Added spec/fixtures/deb/meta_test.



>
1
asdf

Added spec/fixtures/deb/staging/etc/init.d/test.























>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
start() {
}

stop() {
}

restart() {
}

force-reload() {
}

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

Changes to spec/fpm/command_spec.rb.

1

2
3
4
5
6
7
8
require "spec_setup"

require "fpm" # local
require "fpm/command" # local

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

>







1
2
3
4
5
6
7
8
9
require "spec_setup"
require "stud/temporary"
require "fpm" # local
require "fpm/command" # local

describe FPM::Command do
  describe "--prefix"
  describe "-C"
  describe "-p / --package"
18
19
20
21
22
23
24











25


















  describe "--no-depends"
  describe "--provides"
  describe "--conflicts"
  describe "--replaces"
  describe "--config-files"
  describe "--directories"
  describe "-a | --architecture"











end

























>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
  describe "--no-depends"
  describe "--provides"
  describe "--conflicts"
  describe "--replaces"
  describe "--config-files"
  describe "--directories"
  describe "-a | --architecture"

  describe "-p | --package" do
    context "when given a directory" do
      it "should write the package to the given directory." do
        Stud::Temporary.directory do |path|
          cmd = FPM::Command.new("fpm")
          cmd.run(["-s", "empty", "-t", "deb", "-n", "example", "-p", path])
          files = Dir.new(path).to_a - [".", ".."]

          insist { files.size } == 1
          insist { files[0] } =~ /^example_/
        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
end

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

1
2
3
4
5
6
7
8
9
10
11
12
require "spec_setup"
require "fpm" # local
require "fpm/package/cpan" # local

have_cpanm = program_in_path?("cpanm")
if !have_cpanm
  Cabin::Channel.get("rspec") \
    .warn("Skipping CPAN#input tests because 'cpanm' isn't in your PATH")
end

describe FPM::Package::CPAN, :if => have_cpanm do
  subject { FPM::Package::CPAN.new }




|







1
2
3
4
5
6
7
8
9
10
11
12
require "spec_setup"
require "fpm" # local
require "fpm/package/cpan" # local

have_cpanm = program_exists?("cpanm")
if !have_cpanm
  Cabin::Channel.get("rspec") \
    .warn("Skipping CPAN#input tests because 'cpanm' isn't in your PATH")
end

describe FPM::Package::CPAN, :if => have_cpanm do
  subject { FPM::Package::CPAN.new }

Changes to spec/fpm/package/deb_spec.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
require "spec_setup"

require "fpm" # local
require "fpm/package/deb" # local
require "fpm/package/dir" # local

describe FPM::Package::Deb do
  # dpkg-deb lets us query deb package files. 
  # Comes with debian and ubuntu systems.
  have_dpkg_deb = program_in_path?("dpkg-deb")
  if !have_dpkg_deb
    Cabin::Channel.get("rspec") \
      .warn("Skipping some deb tests because 'dpkg-deb' isn't in your PATH")
  end







  after :each do
    subject.cleanup
  end

  describe "#architecture" do
    it "should convert x86_64 to amd64" do
      subject.architecture = "x86_64"
      insist { subject.architecture } == "amd64"
    end

    it "should convert noarch to all" do
      subject.architecture = "noarch"
      insist { subject.architecture } == "all"
    end

    it "should default to native" do
      expected = ""
      if program_in_path?("dpkg")
        expected = %x{dpkg --print-architecture}.chomp
      end

      if expected.empty?
        # dpkg was missing, failed, or emitted nothing.
        expected = %x{uname -m}.chomp
      end

>





|

|




>
>
>
>
>
>


















|







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
require "spec_setup"
require 'fileutils'
require "fpm" # local
require "fpm/package/deb" # local
require "fpm/package/dir" # local

describe FPM::Package::Deb do
  # dpkg-deb lets us query deb package files.
  # Comes with debian and ubuntu systems.
  have_dpkg_deb = program_exists?("dpkg-deb")
  if !have_dpkg_deb
    Cabin::Channel.get("rspec") \
      .warn("Skipping some deb tests because 'dpkg-deb' isn't in your PATH")
  end

  have_lintian = program_exists?("lintian")
  if !have_lintian
    Cabin::Channel.get("rspec") \
      .warn("Skipping some deb tests because 'lintian' isn't in your PATH")
  end

  after :each do
    subject.cleanup
  end

  describe "#architecture" do
    it "should convert x86_64 to amd64" do
      subject.architecture = "x86_64"
      insist { subject.architecture } == "amd64"
    end

    it "should convert noarch to all" do
      subject.architecture = "noarch"
      insist { subject.architecture } == "all"
    end

    it "should default to native" do
      expected = ""
      if program_exists?("dpkg")
        expected = %x{dpkg --print-architecture}.chomp
      end

      if expected.empty?
        # dpkg was missing, failed, or emitted nothing.
        expected = %x{uname -m}.chomp
      end
57
58
59
60
61
62
63






64
65
66
67
68
69
70
  end

  describe "priority" do
    it "should default to 'extra'" do
      insist { subject.attributes[:deb_priority] } == "extra"
    end
  end







  describe "#to_s" do
    it "should have a default output usable as a filename" do
      subject.name = "name"
      subject.version = "123"
      subject.architecture = "all"
      subject.iteration = "100"







>
>
>
>
>
>







64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
  end

  describe "priority" do
    it "should default to 'extra'" do
      insist { subject.attributes[:deb_priority] } == "extra"
    end
  end

  describe "use-file-permissions" do
    it "should be nil by default" do
      insist { subject.attributes[:deb_use_file_permissions?] }.nil?
    end
  end

  describe "#to_s" do
    it "should have a default output usable as a filename" do
      subject.name = "name"
      subject.version = "123"
      subject.architecture = "all"
      subject.iteration = "100"
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
    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.
      tmpfile = Tempfile.new("fpm-test-deb")
      @target = tmpfile.path
      # The target file must not exist.
      tmpfile.unlink

      @original = FPM::Package::Deb.new
      @original.name = "name"
      @original.version = "123"
      @original.iteration = "100"
      @original.epoch = "5"
      @original.architecture = "all"
      @original.dependencies << "something > 10"
      @original.dependencies << "hello >= 20"
      @original.provides = "#{@original.name} = #{@original.version}"




      @original.conflicts = ["foo < 123"]
      @original.attributes[:deb_breaks] = ["baz < 123"]

      @original.attributes[:deb_build_depends_given?] = true
      @original.attributes[:deb_build_depends] ||= []
      @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







|

















|
>
>
>












>
>
>
>
>
>
>
>










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







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
    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.
      tmpfile = Tempfile.new("fpm-test-deb")
      @target = tmpfile.path
      # The target file must not exist.
      tmpfile.unlink

      @original = FPM::Package::Deb.new
      @original.name = "name"
      @original.version = "123"
      @original.iteration = "100"
      @original.epoch = "5"
      @original.architecture = "all"
      @original.dependencies << "something > 10"
      @original.dependencies << "hello >= 20"
      @original.provides << "#{@original.name} = #{@original.version}"

      # Test to cover PR#591 (fix provides names)
      @original.provides << "Some-SILLY_name"

      @original.conflicts = ["foo < 123"]
      @original.attributes[:deb_breaks] = ["baz < 123"]

      @original.attributes[:deb_build_depends_given?] = true
      @original.attributes[:deb_build_depends] ||= []
      @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
165
166
167
168
169
170
171
172




173
174
175
176
177
178
179
        @original.dependencies.each do |dep|
          insist { @input.dependencies }.include?(dep)
        end
      end

      it "should ignore versions and conditions in 'provides' (#280)" do
        # Provides is an array because rpm supports multiple 'provides'
        insist { @input.provides } == [ @original.name ]




      end
    end # package attributes

    # This section mainly just verifies that 'dpkg-deb' can parse the package.
    context "when read with dpkg", :if => have_dpkg_deb do
      def dpkg_field(field)
        return %x{dpkg-deb -f #{@target} #{field}}.chomp







|
>
>
>
>







221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
        @original.dependencies.each do |dep|
          insist { @input.dependencies }.include?(dep)
        end
      end

      it "should ignore versions and conditions in 'provides' (#280)" do
        # Provides is an array because rpm supports multiple 'provides'
        insist { @input.provides }.include?(@original.name)
      end

      it "should fix capitalization and underscores-to-dashes (#591)" do
        insist { @input.provides }.include?("some-silly-name")
      end
    end # package attributes

    # This section mainly just verifies that 'dpkg-deb' can parse the package.
    context "when read with dpkg", :if => have_dpkg_deb do
      def dpkg_field(field)
        return %x{dpkg-deb -f #{@target} #{field}}.chomp
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
      it "should have the correct build dependency list" do
        insist { dpkg_field("Build-Depends") } == "something-else (>> 0.0.0), something-else (<< 1.0.0)"
      end

      it "should have a custom field 'foo: bar'" do
        insist { dpkg_field("foo") } == "bar"
      end
      
      it "should have the correct Conflicts" do
        insist { dpkg_field("Conflicts") } == "foo (<< 123)"
      end

      it "should have the correct Breaks" do
        insist { dpkg_field("Breaks") } == "baz (<< 123)"
      end
    end
  end # #output

  describe "#output with no depends" 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.
      tmpfile = Tempfile.new("fpm-test-deb")
      @target = tmpfile.path
      # The target file must not exist.







|










|







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
      it "should have the correct build dependency list" do
        insist { dpkg_field("Build-Depends") } == "something-else (>> 0.0.0), something-else (<< 1.0.0)"
      end

      it "should have a custom field 'foo: bar'" do
        insist { dpkg_field("foo") } == "bar"
      end

      it "should have the correct Conflicts" do
        insist { dpkg_field("Conflicts") } == "foo (<< 123)"
      end

      it "should have the correct Breaks" do
        insist { dpkg_field("Breaks") } == "baz (<< 123)"
      end
    end
  end # #output

  describe "#output with no depends" 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.
      tmpfile = Tempfile.new("fpm-test-deb")
      @target = tmpfile.path
      # The target file must not exist.
244
245
246
247
248
249
250














































































251
      @input.cleanup
    end # after

    it "should have no dependencies" do
      insist { @input.dependencies }.empty?
    end
  end # #output with no dependencies














































































end # describe FPM::Package::Deb







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

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
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
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
      @input.cleanup
    end # after

    it "should have no dependencies" do
      insist { @input.dependencies }.empty?
    end
  end # #output with no dependencies

  describe "#tar_flags" do
    before :each do
      tmpfile = Tempfile.new("fpm-test-deb")
      @target = tmpfile.path
      # The target file must not exist.
      tmpfile.unlink
      @package = FPM::Package::Deb.new
      @package.name = "name"
    end

    after :each do
      @package.cleanup
    end # after

    it "should set the user for the package's data files" do
      @package.attributes[:deb_user] = "nobody"
      # output a package so that @data_tar_flags is computed
      insist { @package.data_tar_flags } == ["--owner", "nobody", "--numeric-owner", "--group", "0"]
    end

    it "should set the group for the package's data files" do
      @package.attributes[:deb_group] = "nogroup"
      # output a package so that @data_tar_flags is computed
      insist { @package.data_tar_flags } == ["--numeric-owner", "--owner", "0", "--group", "nogroup"]
    end

    it "should not set the user or group for the package's data files if :deb_use_file_permissions? is not nil" do
      @package.attributes[:deb_use_file_permissions?] = true
      # output a package so that @data_tar_flags is computed
      @package.output(@target)
      insist { @package.data_tar_flags } == []
    end
  end # #tar_flags

  describe "#output with lintian" do
    before :all do
      @staging_path = Dir.mktmpdir
      tmpfile = Tempfile.new(["fpm-test-deb", ".deb"])
      @target = tmpfile.path

      # The target file must not exist.
      tmpfile.unlink

      FileUtils.cp_r(Dir['spec/fixtures/deb/staging/*'], @staging_path)

      @deb = FPM::Package::Deb.new
      @deb.name = "name"
      @deb.version = "0.0.1"
      @deb.maintainer = "Jordan Sissel <jls@semicomplete.com>"
      @deb.description = "Test package\nExtended description."
      @deb.attributes[:deb_user] = "root"
      @deb.attributes[:deb_group] = "root"

      @deb.instance_variable_set(:@config_files, ["/etc/init.d/test"])
      @deb.instance_variable_set(:@staging_path, @staging_path)

      @deb.output(@target)
    end

    after :all do
      @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.

1
2
3
4












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













describe FPM::Package::Dir do
  let(:tmpdir) { Stud::Temporary.directory("tmpdir") }
  let(:output) { Stud::Temporary.directory("output") }
  #let(:tmpdir) { ::Dir.mkdir("/tmp/tmpdir"); "/tmp/tmpdir" }
  #let(:output) { ::Dir.mkdir("/tmp/output"); "/tmp/output" }





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







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

if RUBY_VERSION =~ /^1\.8/
  # The following method copied from ruby 1.9.3
  module SecureRandom
    def self.uuid
      ary = self.random_bytes(16).unpack("NnnnnN")
      ary[2] = (ary[2] & 0x0fff) | 0x4000
      ary[3] = (ary[3] & 0x3fff) | 0x8000
      "%08x-%04x-%04x-%04x-%04x%08x" % ary
    end
  end
end

describe FPM::Package::Dir do
  let(:tmpdir) { Stud::Temporary.directory("tmpdir") }
  let(:output) { Stud::Temporary.directory("output") }
  #let(:tmpdir) { ::Dir.mkdir("/tmp/tmpdir"); "/tmp/tmpdir" }
  #let(:output) { ::Dir.mkdir("/tmp/output"); "/tmp/output" }

93
94
95
96
97
98
99






100











101
        Dir.mkdir(File.join(tmpdir, "a"))
        File.write(File.join(tmpdir, "a", "b"), "hello world")
        subject.input(File.join(tmpdir, input))
        subject.output(output)
        insist { File }.exist?(File.join(output, expected_file))
      end
    end






  end











end # describe FPM::Package::Dir







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

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
        Dir.mkdir(File.join(tmpdir, "a"))
        File.write(File.join(tmpdir, "a", "b"), "hello world")
        subject.input(File.join(tmpdir, input))
        subject.output(output)
        insist { File }.exist?(File.join(output, expected_file))
      end
    end

    it "should not map existing paths with = in them" do
      File.write(File.join(tmpdir, "a=b"), "hello world")
      subject.input(File.join(tmpdir, "a=b"))
      subject.output(output)
      insist { File }.exist?(File.join(output, tmpdir, "a=b"))
    end

    it "should not map existing paths with = in them and obey :chdir and :prefix attributes" do
      Dir.mkdir(File.join(tmpdir, "a"))
      File.write(File.join(tmpdir,"a",  "a=b"), "hello world")
      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

Changes to spec/fpm/package/gem_spec.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
require "spec_setup"
require "fpm" # local
require "fpm/package/gem" # local

have_gem = program_in_path?("gem")
if !have_gem
  Cabin::Channel.get("rspec") \
    .warn("Skipping Gem#input tests because 'gem' isn't in your PATH")
end

describe FPM::Package::Gem, :if => have_gem do
  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




|













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







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
require "spec_setup"
require "fpm" # local
require "fpm/package/gem" # local

have_gem = program_exists?("gem")
if !have_gem
  Cabin::Channel.get("rspec") \
    .warn("Skipping Gem#input tests because 'gem' isn't in your PATH")
end

describe FPM::Package::Gem, :if => have_gem do
  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

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

1
2
3

4
5
6
7
8
9
10
11
12
13
require "spec_setup"
require "fpm" # local
require "fpm/package/python" # local


def python_usable?
  return program_in_path?("python") && program_in_path?("easy_install")
end

if !python_usable?
  Cabin::Channel.get("rspec").warn("Skipping Python#input tests because " \
    "'python' and/or 'easy_install' isn't in your PATH")
end




>


|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
require "spec_setup"
require "fpm" # local
require "fpm/package/python" # local
require "find" # stdlib

def python_usable?
  return program_exists?("python") && program_exists?("easy_install")
end

if !python_usable?
  Cabin::Channel.get("rspec").warn("Skipping Python#input tests because " \
    "'python' and/or 'easy_install' isn't in your PATH")
end

122
123
124
125
126
127
128




















129

      it "it should load requirements.txt" do
        subject.input(example_dir)
        insist { subject.dependencies.sort } == ["rtxt-dep1 > 0.1", "rtxt-dep2 = 0.1"]
      end
    end
  end




















end # describe FPM::Package::Python







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

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

      it "it should load requirements.txt" do
        subject.input(example_dir)
        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.

1
2
3
4
5

6
7
8
9
10
11
12
13
14
require "spec_setup"
require "fpm" # local
require "fpm/package/rpm" # local
require "fpm/package/dir" # local
require "arr-pm/file" # gem 'arr-pm'


if !program_in_path?("rpmbuild")
  Cabin::Channel.get("rspec") \
    .warn("Skipping RPM#output tests because 'rpmbuild' isn't in your PATH")
end

describe FPM::Package::RPM do
  after :each do
    subject.cleanup





>

|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require "spec_setup"
require "fpm" # local
require "fpm/package/rpm" # local
require "fpm/package/dir" # local
require "arr-pm/file" # gem 'arr-pm'
require "stud/temporary" # gem 'stud'

if !program_exists?("rpmbuild")
  Cabin::Channel.get("rspec") \
    .warn("Skipping RPM#output tests because 'rpmbuild' isn't in your PATH")
end

describe FPM::Package::RPM do
  after :each do
    subject.cleanup
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
      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"
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
      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_in_path?("rpmbuild") do

















    context "package attributes" do
      before :each do
        @target = Tempfile.new("fpm-test-rpm").path
        File.delete(@target)
        subject.name = "name"
        subject.version = "123"
        subject.architecture = "all"
        subject.iteration = "100"
        subject.epoch = "5"
        subject.dependencies << "something > 10"
        subject.dependencies << "hello >= 20"
        subject.conflicts << "bad < 2"
        subject.attributes[:rpm_os] = "fancypants"

        # Make sure multi-line licenses are hacked to work in rpm (#252)
        subject.license = "this\nis\nan\example"
        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)








|




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


|
<



















>
>
>
>







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
      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
        subject.name = "example"
        subject.architecture = "fancypants"
        subject.version = "1.0"
        target = Stud::Temporary.pathname

        # Should not fail.
        subject.output(target)

        # Verify the arch tag.
        rpm = ::RPM::File.new(target)
        insist { rpm.tags[:arch] } == subject.architecture

        File.unlink(target)
      end
    end
    context "package attributes" do
      before :each do
        @target = Stud::Temporary.pathname

        subject.name = "name"
        subject.version = "123"
        subject.architecture = "all"
        subject.iteration = "100"
        subject.epoch = "5"
        subject.dependencies << "something > 10"
        subject.dependencies << "hello >= 20"
        subject.conflicts << "bad < 2"
        subject.attributes[:rpm_os] = "fancypants"

        # Make sure multi-line licenses are hacked to work in rpm (#252)
        subject.license = "this\nis\nan\example"
        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"


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

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

210
211
212
213
214
215
216















217
218
219
220
221
222
223
        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







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







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
        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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
        insist { [nil, FPM::Package::RPM::DIGEST_ALGORITHM_MAP["md5"]] } \
          .include?((@rpmtags[:filedigestalgo].first rescue nil))
      end
    end # package attributes

    context "package default attributes" do
      before :each do
        @target = Tempfile.new("fpm-test-rpm").path
        File.delete(@target)
        subject.name = "name"
        subject.version = "123"
        # Write the rpm out
        subject.output(@target)

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







|
<







274
275
276
277
278
279
280
281

282
283
284
285
286
287
288
        insist { [nil, FPM::Package::RPM::DIGEST_ALGORITHM_MAP["md5"]] } \
          .include?((@rpmtags[:filedigestalgo].first rescue nil))
      end
    end # package attributes

    context "package default attributes" do
      before :each do
        @target = Stud::Temporary.pathname

        subject.name = "name"
        subject.version = "123"
        # Write the rpm out
        subject.output(@target)

        # Read the rpm
        @rpm = ::RPM::File.new(@target)
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
        subject.provides.each do |dep|
          insist { provides }.include?(dep)
        end
      end
    end # package attributes
  end # #output

  describe "regressions should not occur", :if => program_in_path?("rpmbuild") do
    before :each do
      @target = Tempfile.new("fpm-test-rpm").path
      File.delete(@target)
      subject.name = "name"
      subject.version = "1.23"
    end

    after :each do
      subject.cleanup
      File.delete(@target) rescue nil







|

|
|







344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
        subject.provides.each do |dep|
          insist { provides }.include?(dep)
        end
      end
    end # package attributes
  end # #output

  describe "regressions should not occur", :if => program_exists?("rpmbuild") do
    before :each do
      @tempfile_handle = 
      @target = Stud::Temporary.pathname
      subject.name = "name"
      subject.version = "1.23"
    end

    after :each do
      subject.cleanup
      File.delete(@target) rescue nil
389
390
391
392
393
394
395


































396
397
398
399
400
401
402
403
404
405
406
407
      insist { rpmtags[:epoch] } == nil

      # Default release must be '1'
      insist { rpmtags[:release] } == "1"
    end
  end # regression stuff



































  describe "#output with digest and compression settings", :if => program_in_path?("rpmbuild") do
    context "bzip2/sha1" do
      before :each do
        @target = Tempfile.new("fpm-test-rpm").path
        File.delete(@target)
        subject.name = "name"
        subject.version = "123"
        subject.architecture = "all"
        subject.iteration = "100"
        subject.epoch = "5"
        subject.attributes[:rpm_compression] = "bzip2"
        subject.attributes[:rpm_digest] = "sha1"







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


|
<







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
      insist { rpmtags[:epoch] } == nil

      # Default release must be '1'
      insist { rpmtags[:release] } == "1"
    end
  end # regression stuff

  describe "rpm_use_file_permissions" do
    let(:target) { Stud::Temporary.pathname }
    let(:rpm) { ::RPM::File.new(target) }
    let(:path) { "hello.txt" }
    let(:path_stat) { File.lstat(subject.staging_path(path)) }

    before :each do
      File.write(subject.staging_path(path), "Hello world")
      subject.name = "example"
      subject.version = "1.0"
    end

    after :each do
      subject.cleanup
      File.delete(target) rescue nil
    end

    it "should respect file user and group ownership", :if => program_exists?("rpmbuild") do
      subject.attributes[:rpm_use_file_permissions?] = true
      subject.output(target)
      insist { rpm.tags[:fileusername].first } == Etc.getpwuid(path_stat.uid).name
      insist { rpm.tags[:filegroupname].first } == Etc.getgrgid(path_stat.gid).name
    end

    it "rpm_group should override rpm_use_file_permissions-derived owner", :if => program_exists?("rpmbuild") do
      subject.attributes[:rpm_use_file_permissions?] = true
      subject.attributes[:rpm_user] = "hello"
      subject.attributes[:rpm_group] = "world"
      subject.output(target)
      insist { rpm.tags[:fileusername].first } == subject.attributes[:rpm_user]
      insist { rpm.tags[:filegroupname].first } == subject.attributes[:rpm_group]
    end
  end

  describe "#output with digest and compression settings", :if => program_exists?("rpmbuild") do
    context "bzip2/sha1" do
      before :each do
        @target = Stud::Temporary.pathname

        subject.name = "name"
        subject.version = "123"
        subject.architecture = "all"
        subject.iteration = "100"
        subject.epoch = "5"
        subject.attributes[:rpm_compression] = "bzip2"
        subject.attributes[:rpm_digest] = "sha1"

Changes to spec/fpm/package_convert_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

describe "FPM::Package#convert" do

    let(:gem_package_name_prefix) { 'rubygem19' }
    let(:default_rpm_compression) { 'gzip' }

    subject do
      source = FPM::Package::Gem.new
      source.attributes[:gem_package_name_prefix ] = 'rubygem19'



      source.convert(FPM::Package::RPM)
    end

    it "applies the default attributes for target format" do
      insist { subject.attributes[:rpm_compression] } == default_rpm_compression
    end

    it "remembers attributes applied to source" do
      insist { subject.attributes[:gem_package_name_prefix] } == gem_package_name_prefix
    end



end






|
|

|
|
|
>
>
>
|
|

|
|
|

|
|
|
>
>
>
|
>
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
require "spec_setup"
require "fpm" # local

describe "FPM::Package#convert" do

  let(:gem_package_name_prefix) { 'rubygem19' }
  let(:default_rpm_compression) { 'gzip' }

  subject do
    source = FPM::Package::Gem.new
    prefix = source.attributes[:gem_package_name_prefix ] = 'rubygem19'
    name = source.name = "whatever"
    version = source.version = "1.0"
    source.provides << "#{prefix}-#{name} = #{version}"
    source.convert(FPM::Package::RPM)
  end

  it "applies the default attributes for target format" do
    insist { subject.attributes[:rpm_compression] } == default_rpm_compression
  end

  it "remembers attributes applied to source" do
    insist { subject.attributes[:gem_package_name_prefix] } == gem_package_name_prefix
  end

  it "should list provides matching the gem_package_name_prefix (#585)" do
    insist { subject.provides }.include?("rubygem19(whatever) = 1.0")
  end
end

Added 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
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
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|
          a = File.join(path, "a")
          b = File.join(path, "b")
          File.write(a, "hello")
          File.link(a, b)

          Stud::Temporary.directory do |target|
            ta = File.join(target, "a")
            tb = File.join(target, "b")
            subject.copy_entry(a, ta)
            subject.copy_entry(b, tb)

            # This seems to work to compare file stat calls.
            # target 'a' and 'b' should have the same stat result because
            # they are linked to the same file.
            insist { File.lstat(ta) } == File.lstat(tb)
          end
        end
      end
    end
  end # #copy_entry

  describe "#safesystem" do
    context "with a missing $SHELL" do
      before do
        @orig_shell = ENV["SHELL"]
        ENV.delete("SHELL")
      end

      after do
        ENV["SHELL"] = @orig_shell unless @orig_shell.nil?
      end

      it "should assume /bin/sh"  do
        insist { subject.default_shell } == "/bin/sh"
      end

      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
    context "with $SHELL set to an empty string" do 
      before do
        @orig_shell = ENV["SHELL"]
        ENV["SHELL"] = ""
      end

      after do
        ENV["SHELL"] = @orig_shell unless @orig_shell.nil?
      end

      it "should assume /bin/sh"  do
        insist { subject.default_shell } == "/bin/sh"
      end

      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.

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
require "rubygems" # for ruby 1.8
require "insist" # gem "insist"
require "cabin" # gem "cabin"
require "tmpdir" # stdlib
require "tempfile" # stdlib
require "fileutils" # stdlib

# put "lib" in RUBYLIB
$: << File.join(File.dirname(File.dirname(__FILE__)), "lib")

# for method "program_in_path?" etc
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










|







>
>
>
>
>
>
>
|
>
>

>


|







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
require "rubygems" # for ruby 1.8
require "insist" # gem "insist"
require "cabin" # gem "cabin"
require "tmpdir" # stdlib
require "tempfile" # stdlib
require "fileutils" # stdlib

# put "lib" in RUBYLIB
$: << File.join(File.dirname(File.dirname(__FILE__)), "lib")

# for method "program_exists?" etc
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

Changes to templates/rpm.erb.

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Version: <%= version %>
<% if epoch -%>
Epoch: <%= epoch %>
<% end -%>
Release: <%= iteration or 1 %>
<%# use the first line of the description as the summary -%>
Summary: <%= description.split("\n").first.empty? ? "_" :  description.split("\n").first %>
BuildArch: <%= architecture %>
<% if !attributes[:rpm_autoreqprov?] -%>
AutoReqProv: no
<% else -%>
AutoReqProv: yes
<% end -%>
<% if attributes[:rpm_autoreq?] -%>
AutoReq: yes







<







37
38
39
40
41
42
43

44
45
46
47
48
49
50
Version: <%= version %>
<% if epoch -%>
Epoch: <%= epoch %>
<% end -%>
Release: <%= iteration or 1 %>
<%# use the first line of the description as the summary -%>
Summary: <%= description.split("\n").first.empty? ? "_" :  description.split("\n").first %>

<% if !attributes[:rpm_autoreqprov?] -%>
AutoReqProv: no
<% else -%>
AutoReqProv: yes
<% end -%>
<% if attributes[:rpm_autoreq?] -%>
AutoReq: yes
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119

%install
# noop

%clean
# noop

<%# This next section puts any %pre, %post, %preun, or %postun scripts %>
<% 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 -%>







|







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

%install
# noop

%clean
# noop

<%# This next section puts any %pre, %post, %preun, %postun, %verifyscript, %pretrans or %posttrans scripts %>
<% 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 -%>
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 script?(:after_remove) -%>
%postun
if [ "${1}" -eq 0 ]
then
<%=    script(:after_remove) %>
fi
<%   end -%>













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

%files
%defattr(<%= attributes[:rpm_defattrfile] %>,<%= attributes[:rpm_user] %>,<%= attributes[:rpm_group] %>,<%= attributes[:rpm_defattrdir] %>)
<%# Output config files and then regular files. -%>
<% config_files.each do |path| -%>
%config(noreplace) <%= rpm_file_entry(path) %>
<% end -%>
<%# list directories %>
<% directories.each do |path| -%>
%dir <%= rpm_file_entry(path) %>







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


|
|
|
|
|
>
>
>
|
|







>

|







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
<%   if script?(:after_remove) -%>
%postun
if [ "${1}" -eq 0 ]
then
<%=    script(:after_remove) %>
fi
<%   end -%>
<%
  scriptmap = {
    :rpm_verifyscript => "verifyscript",
    :rpm_posttrans => "posttrans",
    :rpm_pretrans => "pretrans"
  }
  scriptmap.each do |name, rpmname| 
-%>
<%     if script?(name) -%>
%<%=     rpmname %>
<%=      script(name) %>
<%     end -%>
<%   end -%>
<% else -%>
<%
  scriptmap = {
    :before_install => "pre",
    :after_install => "post",
    :before_remove => "preun",
    :after_remove => "postun",
    :rpm_verifyscript => "verifyscript",
    :rpm_posttrans => "posttrans",
    :rpm_pretrans => "pretrans"
  }
  scriptmap.each do |name, rpmname| 
-%>
<%     if script?(name) -%>
%<%=     rpmname %>
<%=      script(name) %>
<%     end -%>
<%   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| -%>
%config(noreplace) <%= rpm_file_entry(path) %>
<% end -%>
<%# list directories %>
<% directories.each do |path| -%>
%dir <%= rpm_file_entry(path) %>

Added templates/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
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
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
#!/bin/bash

# bail out if any part of this fails
set -e

# This is the self-extracting installer script for an FPM shell installer package.
# It contains the logic to unpack a tar archive appended to the end of this script
# and, optionally, to run post install logic.
# Run the package file with -h to see a usage message or look at the print_usage method.
#
# The post install scripts are called with INSTALL_ROOT, INSTALL_DIR and VERBOSE exported
# into the environment for their use.
#
# INSTALL_ROOT = the path passed in with -i or a relative directory of the name of the package
#                file with no extension
# INSTALL_DIR  = the same as INSTALL_ROOT unless -c (capistrano release directory) argumetn
#                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
}

# creates a PID file for this installation
function create_pid(){
    trap "delete_pid" EXIT
    echo $$> ${INSTALL_ROOT}/$$.pid
}


# checks for other PID files and sleeps for a grace period if found
function wait_for_others(){
    local count=`ls ${INSTALL_ROOT}/*.pid | wc -l`

    if [ $count -gt 1 ] ; then
        sleep 10
    fi
}

# kills other running installations
function kill_others(){
    for PID_FILE in $(ls ${INSTALL_ROOT}/*.pid) ; do
        local p=`cat ${PID_FILE}`
        if ! [ $p == $$ ] ; then
            kill -9 $p
            rm -f $PID_FILE 2> /dev/null
        fi
    done
}

# echos metadata file. A function so that we can have it change after we set INSTALL_ROOT
function fpm_metadata_file(){
    echo "${INSTALL_ROOT}/.install-metadata"
}

# if this package was installed at this location already we will find a metadata file with the details
# about the installation that we left here. Load from that if available but allow command line args to trump
function load_environment(){
    local METADATA=$(fpm_metadata_file)
    if [ -r "${METADATA}" ] ; then
        log "Found existing metadata file '${METADATA}'. Loading previous install details. Env vars in current environment will take precedence over saved values."
        local TMP="/tmp/$(basename $0).$$.tmp"
        # save existing environment, load saved environment from previous run from install-metadata and then
        # overlay current environment so that anything set currencly will take precedence
        # but missing values will be loaded from previous runs.
        save_environment "$TMP"
        source "${METADATA}"
        source $TMP
        rm "$TMP"
    fi
}

# 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}
    done

    if [ -n "${OWNER}" ] ; then
        chown ${OWNER} ${METADATA}
    fi
}

function set_install_dir(){
    # if INSTALL_ROOT isn't set by parsed args, use basename of package file with no extension
    DEFAULT_DIR=$(echo $(basename $0) | sed -e 's/\.[^\.]*$//')
    INSTALL_DIR=${INSTALL_ROOT:-$DEFAULT_DIR}

    DATESTAMP=$(date +%Y%m%d%H%M%S)
    if [ -z "$USE_FLAT_RELEASE_DIRECTORY" ] ; then
        <%= "RELEASE_ID=#{release_id}" if respond_to?(:release_id) %>
        INSTALL_DIR="${RELEASES_DIR}/${RELEASE_ID:-$DATESTAMP}"
    fi

    mkdir -p "$INSTALL_DIR" || die "Unable to create install directory $INSTALL_DIR"

    export INSTALL_DIR

    log "Installing package to '$INSTALL_DIR'"
}

function set_owner(){
    export OWNER=${OWNER:-$USER}
    log "Installing as user $OWNER"
}

function unpack_payload(){
    if [ "$FORCE" == "1" ] || [ ! "$(ls -A $INSTALL_DIR)" ] ; then
        log "Unpacking payload . . ."
        local archive_line=$(grep -a -n -m1 '__ARCHIVE__$' $0 | sed 's/:.*//')
        tail -n +$((archive_line + 1)) $0 | tar -C $INSTALL_DIR -xf - > /dev/null || die "Failed to unpack payload from the end of '$0' into '$INSTALL_DIR'"
    else
        # Files are already here, just move symlinks
        log "Directory already exists and has contents ($INSTALL_DIR). Not unpacking payload."
    fi
}

function run_post_install(){
    local AFTER_INSTALL=$INSTALL_DIR/.fpm/after_install
    if [ -r $AFTER_INSTALL ] ; then
        chmod +x $AFTER_INSTALL
        log "Running post install script"
        output=$($AFTER_INSTALL 2>&1)
        errorlevel=$?
        log $output
        return $errorlevel
    fi
    return 0
}

function create_symlinks(){
    [ -n "$USE_FLAT_RELEASE_DIRECTORY" ] && return

    export CURRENT_DIR="$INSTALL_ROOT/current"
    if [ -e "$CURRENT_DIR" ] ; then
        OLD_CURRENT_TARGET=$(readlink $CURRENT_DIR)
        rm "$CURRENT_DIR"
    fi
    ln -s "$INSTALL_DIR" "$CURRENT_DIR"

    log "Symlinked '$INSTALL_DIR' to '$CURRENT_DIR'"
}

# in case post install fails we may have to back out switching the symlink to current
# We can't switch the symlink after because post install may assume that it is in the
# exact state of being installed (services looking to current for their latest code)
function revert_symlinks(){
    if [ -n "$OLD_CURRENT_TARGET" ] ; then
        log "Putting current symlink back to '$OLD_CURRENT_TARGET'"
        if [ -e "$CURRENT_DIR" ] ; then
            rm "$CURRENT_DIR"
        fi
        ln -s "$OLD_CURRENT_TARGET" "$CURRENT_DIR"
    fi
}

function clean_out_old_releases(){
    [ -n "$USE_FLAT_RELEASE_DIRECTORY" ] && return

    if [ -n "$OLD_CURRENT_TARGET" ] ; then
        # exclude old 'current' from deletions
        while [ $(ls -tr "${RELEASES_DIR}" | grep -v ^$(basename "${OLD_CURRENT_TARGET}")$ | wc -l) -gt 2 ] ; do
            OLDEST_RELEASE=$(ls -tr "${RELEASES_DIR}" | grep -v ^$(basename "${OLD_CURRENT_TARGET}")$ | head -1)
            log "Deleting old release '${OLDEST_RELEASE}'"
            rm -rf "${RELEASES_DIR}/${OLDEST_RELEASE}"
        done
    else
        while [ $(ls -tr "${RELEASES_DIR}" | wc -l) -gt 2 ] ; do
            OLDEST_RELEASE=$(ls -tr "${RELEASES_DIR}" | head -1)
            log "Deleting old release '${OLDEST_RELEASE}'"
            rm -rf "${RELEASES_DIR}/${OLDEST_RELEASE}"
        done
    fi
}

function print_usage(){
    echo "Usage: `basename $0` [options]"
    echo "Install this package"
    echo "  -i <DIRECTORY> : install_root - an optional directory to install to."
    echo "      Default is package file name without file extension"
    echo "  -o <USER>     : owner - the name of the user that will own the files installed"
    echo "                   by the package. Defaults to current user"
    echo "  -r: disable capistrano style release directories - Default behavior is to create a releases directory inside"
    echo "      install_root and unpack contents into a date stamped (or build time id named) directory under the release"
    echo "      directory. Then create a 'current' symlink under install_root to the unpacked"
    echo "      directory once installation is complete replacing the symlink if it already "
    echo "      exists. If this flag is set just install into install_root directly"
    echo "  -u: Unpack the package, but do not install and symlink the payload"
    echo "  -f: force - Always overwrite existing installations"
    echo "  -y: yes - Don't prompt to clobber existing installations"
    echo "  -v: verbose - More output on installation"
    echo "  -h: help -  Display this message"
}

function die () {
    local message=$*
    echo "Error: $message : $!"
    exit 1
}

function log(){
    local message=$*
    if [ -n "$VERBOSE" ] ; then
        echo "$*"
    fi
}

function parse_args() {
    args=`getopt i:o:rfuyvh $*`

    if [ $? != 0 ] ; then
        print_usage
        exit 2
    fi
    set -- $args
    for i
    do
        case "$i"
            in
            -r)
                USE_FLAT_RELEASE_DIRECTORY=1
                shift;;
            -i)
                shift;
                export INSTALL_ROOT="$1"
                export RELEASES_DIR="${INSTALL_ROOT}/releases"
                shift;;
            -o)
                shift;
                export OWNER="$1"
                shift;;
            -v)
                export VERBOSE=1
                shift;;
            -u)
                UNPACK_ONLY=1
                shift;;
            -f)
                FORCE=1
                shift;;
            -y)
                CONFIRM="y"
                shift;;
            -h)
                print_usage
                exit 0
                shift;;
            --)
                shift; break;;
        esac
    done
}

# parse args first to get install root
parse_args $*
# load environment from previous installations so we get defaults from that
load_environment
# reparse args so they can override any settings from previous installations if provided on the command line
parse_args $*

main
save_environment $(fpm_metadata_file)
exit 0

__ARCHIVE__

Changes to test/vagrant.pp.

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
case $operatingsystem {
  centos, redhat, fedora: { 
    $pkgupdate = "yum clean all"
    $devsuffix = "devel"
  }
  debian, ubuntu: {
    $pkgupdate = "apt-get update"
    $devsuffix = "dev"



  }
}

exec {
  "update-packages":
    command => $pkgupdate,
    path => [ "/bin", "/usr/bin", "/sbin", "/usr/sbin" ];
}


file {
  # Sometimes veewee leaves behind this...
  "/EMPTY": ensure => absent, backup => false;
}

package {
  "git": ensure => latest;
  "bundler": provider => "gem", ensure => latest;
  "ruby-$devsuffix": ensure => latest;
}

File["/EMPTY"] -> Exec["update-packages"] -> Package <| |>

|






>
>
>









<
<
<
<
<
<






|
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
case $operatingsystem {
  centos, redhat, fedora: {
    $pkgupdate = "yum clean all"
    $devsuffix = "devel"
  }
  debian, ubuntu: {
    $pkgupdate = "apt-get update"
    $devsuffix = "dev"
    package {
      "lintian": ensure => latest
    }
  }
}

exec {
  "update-packages":
    command => $pkgupdate,
    path => [ "/bin", "/usr/bin", "/sbin", "/usr/sbin" ];
}







package {
  "git": ensure => latest;
  "bundler": provider => "gem", ensure => latest;
  "ruby-$devsuffix": ensure => latest;
}

Exec["update-packages"] -> Package <| |>