├── .github └── workflows │ └── ruby.yml ├── .gitignore ├── .readthedocs.yaml ├── .rubocop.yml ├── Brewfile ├── CHANGELOG.rst ├── CODE_OF_CONDUCT.md ├── CONTRIBUTORS ├── Dockerfile ├── Gemfile ├── LICENSE ├── Makefile ├── NOTES.md ├── README.rst ├── Vagrantfile ├── bin └── fpm ├── docs ├── Dockerfile ├── Makefile ├── Makefile.sphinx ├── changelog.rst ├── changelog_links.rst ├── cli-reference.rst ├── conf.py ├── contributing.rst ├── docker.rst ├── generate-cli-reference.rb ├── getting-started.rst ├── index.rst ├── installation.rst ├── packages │ ├── apk.rst │ ├── cli │ │ ├── apk.rst │ │ ├── cpan.rst │ │ ├── deb.rst │ │ ├── dir.rst │ │ ├── empty.rst │ │ ├── freebsd.rst │ │ ├── gem.rst │ │ ├── npm.rst │ │ ├── osxpkg.rst │ │ ├── p5p.rst │ │ ├── pacman.rst │ │ ├── pear.rst │ │ ├── pkgin.rst │ │ ├── pleaserun.rst │ │ ├── puppet.rst │ │ ├── python.rst │ │ ├── rpm.rst │ │ ├── sh.rst │ │ ├── snap.rst │ │ ├── solaris.rst │ │ ├── tar.rst │ │ ├── virtualenv.rst │ │ └── zip.rst │ ├── cpan.rst │ ├── deb.rst │ ├── dir.rst │ ├── empty.rst │ ├── freebsd.rst │ ├── gem.rst │ ├── npm.rst │ ├── osxpkg.rst │ ├── p5p.rst │ ├── pacman.rst │ ├── pear.rst │ ├── pkgin.rst │ ├── pleaserun.rst │ ├── puppet.rst │ ├── python.rst │ ├── rpm.rst │ ├── sh.rst │ ├── snap.rst │ ├── solaris.rst │ ├── tar.rst │ ├── virtualenv.rst │ └── zip.rst ├── packaging-types.rst └── requirements.txt ├── examples ├── api │ ├── dir-to-deb-rpm-with-init.rb │ ├── gem-to-rpm.rb │ └── multiple-to-rpm.rb ├── fpm-with-system-ruby │ ├── Makefile │ └── README.md ├── fpm │ ├── Makefile │ └── README.md ├── jruby │ ├── Makefile │ └── README.md └── python │ └── twisted │ └── Makefile ├── fpm.gemspec ├── lib ├── fpm.rb └── fpm │ ├── command.rb │ ├── errors.rb │ ├── namespace.rb │ ├── package.rb │ ├── package │ ├── apk.rb │ ├── cpan.rb │ ├── deb.rb │ ├── dir.rb │ ├── empty.rb │ ├── freebsd.rb │ ├── gem.rb │ ├── npm.rb │ ├── osxpkg.rb │ ├── p5p.rb │ ├── pacman.rb │ ├── pear.rb │ ├── pkgin.rb │ ├── pleaserun.rb │ ├── puppet.rb │ ├── pyfpm │ │ ├── __init__.py │ │ └── get_metadata.py │ ├── python.rb │ ├── rpm.rb │ ├── sh.rb │ ├── snap.rb │ ├── solaris.rb │ ├── tar.rb │ ├── virtualenv.rb │ └── zip.rb │ ├── rake_task.rb │ ├── util.rb │ ├── util │ └── tar_writer.rb │ └── version.rb ├── misc └── pkgsrc.sh ├── notify-failure.sh ├── singularity.def ├── spec ├── acceptance │ └── puppet │ │ └── manifests │ │ ├── install.pp │ │ └── remove.pp ├── conversion │ └── gem_to_deb.rb ├── fixtures │ ├── deb │ │ ├── meta_test │ │ ├── staging │ │ │ └── etc │ │ │ │ └── init.d │ │ │ │ └── test │ │ └── triggers │ ├── gem │ │ └── example │ │ │ ├── bin │ │ │ └── example │ │ │ ├── example-1.0.gem │ │ │ └── example.gemspec │ ├── mockpackage.rb │ ├── python │ │ ├── easy_install_default.py │ │ ├── requirements.txt │ │ └── setup.py │ └── virtualenv │ │ └── requirements.txt ├── fpm │ ├── command_spec.rb │ ├── package │ │ ├── cpan_spec.rb │ │ ├── deb_spec.rb │ │ ├── dir_spec.rb │ │ ├── empty_spec.rb │ │ ├── freebsd_spec.rb │ │ ├── gem_spec.rb │ │ ├── npm_spec.rb │ │ ├── osxpkg_spec.rb │ │ ├── pacman_spec.rb │ │ ├── python_spec.rb │ │ ├── rpm_spec.rb │ │ ├── sh_spec.rb │ │ ├── snap_spec.rb │ │ ├── tar_spec.rb │ │ └── virtualenv_spec.rb │ ├── package_convert_spec.rb │ ├── package_spec.rb │ ├── rake_task_spec.rb │ └── util_spec.rb └── spec_setup.rb ├── templates ├── deb.erb ├── deb │ ├── changelog.erb │ ├── deb.changes.erb │ ├── ldconfig.sh.erb │ ├── postinst_upgrade.sh.erb │ ├── postrm_upgrade.sh.erb │ ├── preinst_upgrade.sh.erb │ └── prerm_upgrade.sh.erb ├── osxpkg.erb ├── p5p_metadata.erb ├── pacman.erb ├── pacman │ └── INSTALL.erb ├── pleaserun │ ├── generate-cleanup.sh │ ├── install-path.sh │ ├── install.sh │ └── scripts │ │ ├── after-install.sh │ │ └── before-remove.sh ├── puppet │ ├── package.pp.erb │ └── package │ │ └── remove.pp.erb ├── rpm.erb ├── rpm │ └── filesystem_list ├── sh.erb └── solaris.erb └── test └── vagrant.pp /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | name: Ruby 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-22.04 11 | strategy: 12 | matrix: 13 | ruby-version: ['2.7', '3.0', '3.1'] 14 | steps: 15 | - run: | 16 | sudo apt install -y libarchive-tools lintian cpanminus 17 | - uses: actions/checkout@v3 18 | - uses: ruby/setup-ruby@v1 19 | with: 20 | ruby-version: ${{ matrix.ruby-version }} 21 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 22 | - run: | 23 | bundle exec rspec 24 | env: 25 | SHELL: /usr/bin/bash 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vim 2 | .*.sw[a-z] 3 | 4 | # emacs 5 | \#*# 6 | *~ 7 | .#* 8 | 9 | # package builds 10 | *.rpm 11 | *.deb 12 | *.pkg.tar* 13 | 14 | # build byproducts 15 | build-*/* 16 | fpm.wiki 17 | *.gem 18 | *.pkg 19 | 20 | # python 21 | *.pyc 22 | *.egg-info 23 | 24 | # RVM 25 | .rvmrc 26 | .ruby-gemset 27 | .ruby-version 28 | 29 | .yardoc 30 | coverage 31 | test/tmp 32 | Gemfile.lock 33 | 34 | # OS X 35 | .DS_Store 36 | 37 | .rbx 38 | .vagrant 39 | 40 | # RubyMine 41 | .idea/ 42 | 43 | docs/_build 44 | docs/.work 45 | 46 | .docker-test-everything 47 | .docker-test-minimal 48 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # https://blog.readthedocs.com/build-errors-docutils-0-18/ 2 | version: 2 3 | 4 | python: 5 | install: 6 | - requirements: docs/requirements.txt 7 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # Let's not argue over this... 2 | StringLiterals: 3 | Enabled: false 4 | 5 | # I can't find a reason for raise vs fail. 6 | SignalException: 7 | Enabled: false 8 | 9 | # I can't find a reason to prefer 'map' when 'collect' is what I mean. 10 | # I'm collecting things from a list. Maybe someone can help me understand the 11 | # semantics here. 12 | CollectionMethods: 13 | Enabled: false 14 | 15 | # Why do you even *SEE* trailing whitespace? Because your editor was 16 | # misconfigured to highlight trailing whitespace, right? Maybe turn that off? 17 | # ;) 18 | TrailingWhitespace: 19 | Enabled: false 20 | 21 | # Line length is another weird problem that somehow in the past 40 years of 22 | # computing we don't seem to have solved. It's a display problem :( 23 | LineLength: 24 | Max: 9000 25 | 26 | # %w() vs [ "x", "y", ... ] 27 | # The complaint is on lib/pleaserun/detector.rb's map of OS=>Runner, 28 | # i'll ignore it. 29 | WordArray: 30 | MinSize: 5 31 | 32 | # A 20-line method isn't too bad. 33 | MethodLength: 34 | Max: 20 35 | 36 | # Hash rockets (=>) forever. Why? Not all of my hash keys are static symbols. 37 | HashSyntax: 38 | EnforcedStyle: hash_rockets 39 | 40 | # I prefer explicit return. It makes it clear in the code that the 41 | # code author intended to return a value from a method. 42 | RedundantReturn: 43 | Enabled: false 44 | 45 | # My view on a readable case statement seems to disagree with 46 | # what rubocop wants and it doesn't let me configure it other than 47 | # enable/disable. 48 | CaseIndentation: 49 | Enabled: false 50 | 51 | # module This::Module::Definition is good. 52 | Style/ClassAndModuleChildren: 53 | Enabled: true 54 | EnforcedStyle: compact 55 | 56 | # "in interpolation #{use.some("double quotes is ok")}" 57 | Style/StringLiteralsInInterpolation: 58 | Enabled: true 59 | EnforcedStyle: double_quotes 60 | 61 | # Long-block `if !something ... end` are more readable to me than `unless something ... end` 62 | Style/NegatedIf: 63 | Enabled: false 64 | -------------------------------------------------------------------------------- /Brewfile: -------------------------------------------------------------------------------- 1 | brew 'dpkg' 2 | brew 'rpm' 3 | brew 'gnu-tar' 4 | brew 'xz' 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jls@semicomplete.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: https://contributor-covenant.org 46 | [version]: https://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | The following awesome folks have contributed ideas, 2 | bug reports, code, or other to fpm: 3 | 4 | anthezium 5 | Curt Micol 6 | Jeanine Adkisson 7 | Jordan Sissel 8 | Marc Fournier 9 | Michael Blume 10 | Pierre-Yves Ritschard 11 | sabowski 12 | Thomas Haggett 13 | Pieter Loubser 14 | Aleix Conchillo Flaqué (github: aconchillo) 15 | Luke Macken (github: lmacken) 16 | Matt Blair (github: mblair) 17 | Thomas Meson (github: zllak) 18 | Oliver Hookins (github: ohookins) 19 | llasram 20 | sbuss 21 | Brett Gailey (github: dnbert) 22 | Daniel Haskin (github: djhaskin987) 23 | Richard Grainger (github: liger1978) 24 | seph (github: directionless) 25 | 26 | If you have contributed (bug reports, feature requests, help in IRC, blog 27 | posts, code, etc) and aren't listed here, please let me know if you wish to be 28 | added! 29 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | # Are we running against the minimal container, or the everything 4 | # container? Minimal is mostly the compiled package tools. Everything 5 | # pulls in scripting langauges. 6 | ARG BASE_ENV=everything 7 | 8 | # Are we running tests, or a release? Tests build and run against the 9 | # CWD, where release will use the downloaded gem. 10 | ARG TARGET=test 11 | 12 | # Container to throw an error if called with a bare `docker build .` 13 | FROM ubuntu:20.04 as error 14 | RUN <=45' 'wheel>=0.34' 'virtualenv>=20' 'virtualenv-tools3>=2' 57 | update-alternatives --install /usr/bin/python python /usr/bin/python3 10 58 | EOF 59 | 60 | # hadolint ignore=DL3006 61 | FROM ${BASE_ENV}-base as base 62 | RUN <= a version you have available. 17 | 18 | Further, debian blocks 'gem update --system' which you can get around by doing: 19 | 20 | % gem install rubygems-update 21 | % ruby /var/lib/gems/1.8/gems/rubygems-update-1.3.1/bin/update_rubygems 22 | 23 | I recommend packaging 'rubygems-update' (fpm -s gem -t deb rubygems-update) and 24 | possibly running the update_rubygems as a postinstall, even though I don't like 25 | postinstalls. I haven't looked yet to see what is required to mimic (if 26 | possible) the actions of that script simply in a tarball. 27 | 28 | ## Python 29 | 30 | https://www.debian.org/doc/packaging-manuals/python-policy/ap-packaging_tools.html 31 | 32 | Debian python packages all rely on some form of python-central or 33 | python-support (different tools that do similar/same things? I don't know) 34 | 35 | As I found, disabling postinst scripts in Debian causes Python to stop working. 36 | The postinst scripts generally look like this: 37 | 38 | if which update-python-modules >/dev/null 2>&1; then 39 | update-python-modules SOMEPACKAGENAME.public 40 | fi 41 | 42 | I don't believe in postinst scripts, and I also feel like requiring a 43 | postinstall step to make a python module work is quite silly - though I'm sure 44 | (I hope) Debian had good reason. 45 | 46 | So, I'm going to try working on a howto for recommended ways to build python 47 | packages with fpm in debian. It will likely require a one-time addition to 48 | site.py (/usr/lib/python2.6/site.py) or some other PYTHONPATH hackery, though 49 | I don't know just yet. 50 | 51 | It will also require special setup.py invocations as Debian has patched distutils to 52 | install python packages, by default, to a place that requires again the 53 | python-central/support tools to run to make them work. 54 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | fpm 2 | === 3 | 4 | |Chat| |Gem| 5 | 6 | The goal of fpm is to make it easy and quick to build packages such as rpms, 7 | debs, OSX packages, etc. 8 | 9 | fpm, as a project, exists to help you build packages, therefore: 10 | 11 | * If fpm is not helping you make packages easily, then there is a bug in fpm. 12 | * If you are having a bad time with fpm, then there is a bug in fpm. 13 | * If the documentation is confusing, then this is a bug in fpm. 14 | 15 | If there is a bug in fpm, then we can work together to fix it. If you wish to 16 | report a bug/problem/whatever, I welcome you to do on `the project issue tracker`_. 17 | 18 | .. _the project issue tracker: https://github.com/jordansissel/fpm/issues 19 | 20 | You can find out how to use fpm in the `documentation`_. 21 | 22 | .. _documentation: https://fpm.readthedocs.io/en/latest/ 23 | 24 | You can learn how to install fpm on your platform in the `installation guide`_. 25 | 26 | .. _installation guide: https://fpm.readthedocs.io/en/latest/installation.html 27 | 28 | Project Principles 29 | ------------------ 30 | 31 | * Community: If a newbie has a bad time, it's a bug. 32 | * Engineering: Make it work, then make it right, then make it fast. 33 | * Capabilities: If it doesn't do a thing today, we can make it do it tomorrow. 34 | 35 | 36 | Backstory 37 | --------- 38 | 39 | Sometimes packaging is done wrong (because you can't do it right for all 40 | situations), but small tweaks can fix it. 41 | 42 | And sometimes, there isn't a package available for the tool you need. 43 | 44 | And sometimes if you ask "How do I get python 3.9 on RHEL 8?" some unhelpful 45 | trolls will tell you to "Use another distro" 46 | 47 | Further, job switches have me flipping between Ubuntu and CentOS. These use 48 | two totally different package systems with completely different packaging 49 | policies and support tools. Learning both was painful and confusing. I want to 50 | save myself (and you) that pain in the future. 51 | 52 | It should be easy to say "here's my install dir and here's some dependencies; 53 | please make a package" 54 | 55 | The Solution - FPM 56 | ------------------ 57 | 58 | I wanted a simple way to create packages without needing to memorize too much. 59 | 60 | I wanted a tool to help me deliver software with minimal steps or training. 61 | 62 | The goal of FPM is to be able to easily build platform-native packages. 63 | 64 | With fpm, you can do many things, including: 65 | 66 | * Creating packages easily (deb, rpm, freebsd, etc) 67 | * Tweaking existing packages (removing files, changing metadata/dependencies) 68 | * Stripping pre/post/maintainer scripts from packages 69 | 70 | .. include: docs/installing 71 | 72 | Things that should work 73 | ----------------------- 74 | 75 | Sources: 76 | 77 | * gem (even autodownloaded for you) 78 | * python modules (autodownload for you) 79 | * pear (also downloads for you) 80 | * directories 81 | * tar(.gz) archives 82 | * rpm 83 | * deb 84 | * node packages (npm) 85 | * pacman (ArchLinux) packages 86 | 87 | Targets: 88 | 89 | * deb 90 | * rpm 91 | * solaris 92 | * freebsd 93 | * tar 94 | * directories 95 | * Mac OS X `.pkg` files (`osxpkg`) 96 | * pacman (ArchLinux) packages 97 | 98 | .. include: docs/contributing 99 | 100 | .. |Chat| image:: https://img.shields.io/badge/irc-%23fpm%20on%20freenode-brightgreen.svg 101 | :target: https://webchat.freenode.net/?channels=fpm 102 | .. |Gem| image:: https://img.shields.io/gem/v/fpm.svg 103 | :target: https://rubygems.org/gems/fpm 104 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | # All Vagrant configuration is done here. The most common configuration 6 | # options are documented and commented below. For a complete reference, 7 | # please see the online documentation at vagrantup.com. 8 | 9 | 10 | config.vm.define "centos6" do |centos6| 11 | centos6.vm.box = "puppetlabs/centos-6.6-64-puppet" 12 | end 13 | 14 | config.vm.define "centos7" do |centos7| 15 | centos7.vm.box = "puppetlabs/centos-7.0-64-puppet" 16 | end 17 | 18 | config.vm.define "debian6" do |debian6| 19 | debian6.vm.box = "puppetlabs/debian-6.0.10-64-puppet" 20 | end 21 | 22 | config.vm.define "debian7" do |debian7| 23 | debian7.vm.box = "puppetlabs/centos-7.0-64-puppet" 24 | end 25 | 26 | config.vm.define "arch" do |arch| 27 | arch.vm.box = "jfredett/arch-puppet" 28 | end 29 | 30 | config.vm.define "freebsd10" do |freebsd10| 31 | freebsd10.vm.box = "tjay/freebsd-10.1" 32 | end 33 | 34 | config.vm.define :smartos do |smartos| 35 | smartos.vm.box = "smartos-base1310-64-virtualbox-20130806.box" 36 | smartos.vm.box_url = "http://dlc-int.openindiana.org/aszeszo/vagrant/smartos-base1310-64-virtualbox-20130806.box" 37 | end 38 | 39 | config.vm.provision :puppet do |puppet| 40 | puppet.manifests_path = "test" 41 | puppet.manifest_file = "vagrant.pp" 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /bin/fpm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $: << File.join(File.dirname(__FILE__), "..", "lib") 4 | require "fpm" 5 | require "fpm/command" 6 | 7 | exit(FPM::Command.run || 0) 8 | -------------------------------------------------------------------------------- /docs/Dockerfile: -------------------------------------------------------------------------------- 1 | # This Dockerfile produces a docker image which is used to build the fpm docs. 2 | FROM debian:latest 3 | RUN apt-get update 4 | RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python3-pip 5 | RUN pip3 install Sphinx 6 | #==1.8 7 | RUN pip3 install sphinx_rtd_theme 8 | RUN pip3 install alabaster 9 | RUN pip3 install sphinx-autobuild 10 | 11 | CMD ["/bin/bash"] 12 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | include Makefile.sphinx 2 | 3 | IMAGE=fpm-sphinx 4 | WORKDIR=./.work 5 | GITROOT=$(shell git rev-parse --show-toplevel) 6 | GITREMOTE=$(shell git remote -v | awk '/(push)/ {print $$2}') 7 | 8 | GENERATED_FILES=cli-reference.rst changelog_links.rst 9 | 10 | $(WORKDIR): 11 | mkdir $(WORKDIR) 12 | 13 | # A task to generate reST syntax for issue links mentioned in CHANGELOG.rst 14 | changelog_links.rst: ../CHANGELOG.rst Makefile 15 | grep -Eo '#[0-9]+' $< \ 16 | | tr -d '#' \ 17 | | awk '{printf ".. _#%s: https://github.com/jordansissel/fpm/issues/%s\n", $$1, $$1 }' \ 18 | | sort -u > $@ 19 | 20 | # CLI reference is generated based on the the command line flags 21 | cli-reference.rst: generate-cli-reference.rb Makefile 22 | cli-reference.rst: ../lib/fpm/package/*.rb ../lib/fpm/package.rb 23 | ruby -I ../lib generate-cli-reference.rb > $@ 24 | 25 | package-type-cli: 26 | $(MAKE) $(addprefix packages/cli/,$(addsuffix .rst,$(notdir $(basename $(wildcard ../lib/fpm/package/*.rb))))) 27 | 28 | packages/cli: 29 | mkdir $@ 30 | 31 | packages/cli/%.rst: ../lib/fpm/package/%.rb packages/cli generate-cli-reference.rb Makefile 32 | ruby -I ../lib generate-cli-reference.rb $* > $@ 33 | 34 | 35 | .PHONY: podman-prep 36 | podman-prep: Dockerfile 37 | @podman images fpm-sphinx | grep -q '^fpm-sphinx ' \ 38 | || podman build -t $(IMAGE) . 39 | 40 | .PHONY: build 41 | build: $(GENERATED_FILES) | podman-prep 42 | podman run -it -v $$PWD/../:/project:z $(IMAGE) sh -xc 'make -C /project/docs html && chown -R 1000:1000 /project/docs' 43 | 44 | 45 | .PHONY: build 46 | view: $(GENERATED_FILES) | podman-prep 47 | podman run -p 127.0.0.1:8000:8000 -it -v $$PWD/../:/project:z $(IMAGE) sh -xc 'make -C /project/docs livehtml' 48 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: changelog_links.rst 2 | .. include:: ../CHANGELOG.rst 3 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing/Issues 2 | =================== 3 | 4 | Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. See the `Code of Conduct`_ for details. 5 | 6 | .. _Code of Conduct: https://github.com/jordansissel/fpm/blob/master/CODE_OF_CONDUCT.md 7 | 8 | All contributions are welcome: ideas, patches, documentation, bug reports, complaints, and even something you drew up on a napkin :) 9 | 10 | It is more important that you are able to contribute and get help if you need it than it is how you contribute or get help. 11 | 12 | That said, some points to get started: 13 | 14 | * Have a problem you want FPM to solve for you? You can email the `mailing list`_, or join the IRC channel #fpm on irc.freenode.org, or email me personally (jls@semicomplete.com) 15 | * Have an idea or a feature request? File a ticket on `github`_, or email the `mailing list`_, or email me personally (jls@semicomplete.com) if that is more comfortable. 16 | * If you think you found a bug, it probably is a bug. File it on `github`_ or send details to the `mailing list`_. 17 | * 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. 18 | * 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`_. 19 | 20 | .. _mailing list: https://groups.google.com/group/fpm-users 21 | .. _github: https://github.com/jordansissel/fpm 22 | 23 | Contributing changes by forking from GitHub 24 | ------------------------------------------- 25 | 26 | First, create a GitHub account if you do not already have one. Log in to 27 | GitHub and go to [the main FPM GitHub page](https://github.com/jordansissel/fpm). 28 | 29 | At the top right, click on the button labeled "Fork". This will put a forked 30 | copy of the main FPM repo into your account. Next, clone your account's GitHub 31 | repo of FPM. For example: 32 | 33 | $ git clone git@github.com:yourusername/fpm.git 34 | 35 | Development Environment 36 | ----------------------- 37 | 38 | If you don't already have the bundler gem installed, install it now: 39 | 40 | $ gem install bundler 41 | 42 | Now change to the root of the FPM repo and run: 43 | 44 | $ bundle install 45 | 46 | This will install all of the dependencies required for running FPM from source. 47 | Most importantly, you should see the following output from the bundle command 48 | when it lists the FPM gem: 49 | 50 | ... 51 | Using json (1.8.1) 52 | Using fpm (0.4.42) from source at . 53 | Using hitimes (1.2.1) 54 | ... 55 | 56 | If your system doesn't have `bsdtar` by default, make sure to install it or some 57 | tests will fail: 58 | 59 | apt-get install bsdtar || apt install libarchive-tools 60 | 61 | yum install bsdtar 62 | 63 | 64 | You also need these tools: 65 | 66 | apt-get install lintian cpanminus 67 | 68 | Next, run make in root of the FPM repo. If there are any problems (such as 69 | missing dependencies) you should receive an error 70 | 71 | At this point, the FPM command should run directly from the code in your cloned 72 | repo. Now simply make whatever changes you want, commit the code, and push 73 | your commit back to master. 74 | 75 | If you think your changes are ready to be merged back to the main FPM repo, you 76 | can generate a pull request on the GitHub website for your repo and send it in 77 | for review. 78 | 79 | Problems running bundle install? 80 | -------------------------------- 81 | 82 | If you are installing on Mac OS 10.9 (Mavericks) you will need to make sure that 83 | you have the standalone command line tools separate from Xcode: 84 | 85 | $ xcode-select --install 86 | 87 | Finally, click the install button on the prompt that appears. 88 | 89 | Editing Documentation 90 | --------------------- 91 | 92 | If you want to edit the documentation, here's a quick guide to getting started: 93 | 94 | * Install `docker`_. 95 | * All documentation is located in the `docs` folder. ``cd`` into the docs folder and run the following command once:: 96 | 97 | make docker-prep 98 | 99 | * Once that is done, run ``make build`` whenever you want to build the site. It will generate the html in the `_build/html` directory. 100 | * You can use any tool like `serve _build/html` (npm package) or ``python -m http.server -d _build/html 5000`` to serve the static html on your machine (http://localhost:5000). 101 | 102 | .. _docker: https://docs.docker.com/engine/install/ 103 | 104 | Now you can simply make whatever changes you want, commit the code, and push your commit back to master. 105 | 106 | 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. 107 | -------------------------------------------------------------------------------- /docs/docker.rst: -------------------------------------------------------------------------------- 1 | FPM and Docker 2 | ============== 3 | 4 | Because fpm depends on so many underlying system tools, docker can 5 | alleviate the need to install them locally. 6 | 7 | An end user may use a docker container in lieu of installing 8 | locally. And a developer can use docker to run the test suite. 9 | 10 | 11 | Running FPM inside docker 12 | ------------------------- 13 | 14 | First, build a container will all the dependencies:: 15 | 16 | make docker-release-everything 17 | 18 | Now, run it as you would the fpm command. Note that you will have to 19 | mount your source directly into the docker volume:: 20 | 21 | docker run -v $(pwd):/src fpm --help 22 | 23 | As a full example:: 24 | 25 | mkdir /tmp/fpm-test 26 | mkdir /tmp/fpm-test/files 27 | touch /tmp/fpm-test/files/one 28 | touch /tmp/fpm-test/files/two 29 | 30 | docker run -v /tmp/fpm-test/files:/src -v /tmp/fpm-test:/out fpm -s dir -t tar -n example -p /out/out.tar . 31 | 32 | tar tf /tmp/fpm-test/out.tar 33 | 34 | Depending on your needs, you will have to adjust the volume mounts and 35 | relative paths to fit your particular situation. 36 | 37 | Running rpsec inside docker 38 | --------------------------- 39 | 40 | The Makefile provides some targets for testing. They will build a 41 | docker container with the dependencies, and then invoked `rspec` 42 | inside it. The makefile uses a sentinel file to indicate that the 43 | docker image has been build, and can be reused. 44 | 45 | make docker-test-everything 46 | 47 | 48 | 49 | How does this work 50 | ------------------ 51 | 52 | The Dockerfile makes heavy use of multistage 53 | builds. This allows the various output containers to build on the same 54 | earlier stages. 55 | 56 | There are two ``base`` images. A ``minimal`` image, which contains 57 | compiled dependencies and ruby. And an ``everything`` image which brings 58 | in scripting systems like ``python`` and ``perl``. These are split to 59 | allow a smaller ``minimal`` image in cases where building scripting 60 | language packages are not needed. 61 | 62 | The Dockerfile the argument ``BASE_ENV`` to specify what base image to 63 | use. This can be set to either ``minimal`` or ``everything``. If 64 | unspecified, it defaults to ``everything`` 65 | 66 | We want to use the same set of base images for both the ``rspec`` 67 | testing, as well as the run time containerization. We do this by using 68 | the ``TARGET`` argument to select which container to build. 69 | 70 | The makefile encodes this logic with two pattern rules. 71 | -------------------------------------------------------------------------------- /docs/generate-cli-reference.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../lib/fpm/command" 4 | 5 | flagsort = lambda { |x| x.sub(/^--(?:\[no-\])?/, "") } 6 | 7 | if ARGV.length == 0 8 | puts "Command-line Reference" 9 | puts "==========================" 10 | puts 11 | 12 | puts "This page documents the command-line flags available in FPM. You can also see this content in your terminal by running ``fpm --help``" 13 | puts 14 | 15 | puts "General Options" 16 | puts "---------------" 17 | 18 | FPM::Command.instance_variable_get(:@declared_options).sort_by { |o| flagsort.call(o.switches.first) }.each do |option| 19 | text = option.description.gsub("\n", " ") 20 | 21 | if option.type == :flag 22 | # it's a flag which means there are no parameters to the option 23 | puts "* ``#{option.switches.first}``" 24 | else 25 | puts "* ``#{option.switches.first} #{option.type}``" 26 | end 27 | 28 | if option.switches.length > 1 29 | puts " - Alternate option spellings: ``#{option.switches[1..-1].join(", ")}``" 30 | end 31 | puts " - #{text}" 32 | puts 33 | end 34 | end 35 | 36 | 37 | FPM::Package.types.sort_by { |k,v| k }.each do |name, type| 38 | next if ARGV.length > 0 && ARGV[0].downcase != name.downcase 39 | 40 | options = type.instance_variable_get(:@options) 41 | 42 | # Only print the section header if no arguments are given 43 | # -- aka, generate the list of all flags grouped by package type. 44 | if ARGV.length == 0 45 | puts "#{name}" 46 | puts "-" * name.size 47 | puts 48 | end 49 | 50 | if options.empty? 51 | puts "This package type has no additional options" 52 | end 53 | 54 | options.sort_by { |flag, _| flagsort.call(flag.first) }.each do |flag, param, help, options, block| 55 | if param == :flag 56 | puts "* ``#{flag.first}``" 57 | else 58 | puts "* ``#{flag.first} #{param}``" 59 | end 60 | 61 | text = help.sub(/^\([^)]+\) /, "") 62 | puts " - #{text}" 63 | end 64 | 65 | puts 66 | end 67 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | fpm - packaging made simple 2 | =========================== 3 | 4 | .. note:: 5 | The documentation here is a work-in-progress; it is by no means extensive. If you want to contribute new docs or report problems, you are invited to do so on `the project issue tracker`_. 6 | 7 | Welcome to the fpm documentation! 8 | 9 | fpm is a tool which lets you easily create packages for Debian, Ubuntu, Fedora, CentOS, RHEL, Arch Linux, FreeBSD, macOS, and more! 10 | 11 | fpm isn't a new packaging system, it's a tool to help you make packages for existing systems with less effort. It does this by offering a command-line interface to allow you to create packages easily. Here are some examples using fpm: 12 | 13 | * ``fpm -s npm -t deb express`` -- Make a Debian package for the nodejs `express` library 14 | * ``fpm -s cpan -t rpm Fennec`` -- Make an rpm for the perl Fennec module 15 | * ``fpm -s dir -t pacman -n fancy ~/.zshrc`` -- Put your ~/.zshrc into an Arch Linux pacman package named "fancy" 16 | * ``fpm -s python -t freebsd Django`` -- Create a FreeBSD package containing the Python Django library 17 | * ``fpm -s rpm -t deb mysql.rpm`` -- Convert an rpm to deb 18 | 19 | This project has a few important principles which guide development: 20 | 21 | * Community: If a newbie has a bad time, it's a bug. 22 | * Engineering: Make it work, make it right, then make it fast. 23 | * Capabilities: If it doesn't do a thing today, we can make it do it tomorrow. 24 | 25 | `Install fpm `_ and you'll quickly begin making packages for whatever you need! 26 | 27 | You can view the changelog `here`_. 28 | 29 | Table of Contents 30 | ----------------- 31 | 32 | .. toctree:: 33 | :includehidden: 34 | 35 | installation 36 | getting-started 37 | packaging-types 38 | cli-reference 39 | docker 40 | contributing 41 | changelog 42 | 43 | .. _here: /changelog.html 44 | 45 | .. _the project issue tracker: https://github.com/jordansissel/fpm/issues 46 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | FPM is written in ruby and can be installed using `gem`. For some package formats (like rpm and snap), you will need certain packages installed to build them. 5 | 6 | Installing FPM 7 | -------------- 8 | 9 | .. note:: 10 | You must have ruby installed on your machine before installing fpm. `Here`_ are instructions to install Ruby on your machine. 11 | 12 | .. _Here: https://www.ruby-lang.org/en/documentation/installation/ 13 | 14 | You can install FPM with the ``gem`` tool:: 15 | 16 | gem install fpm 17 | 18 | To make sure fpm is installed correctly, try running the following command:: 19 | 20 | fpm --version 21 | 22 | You should get some output like this, although the exact output will depend on which version of FPM you have installed.:: 23 | 24 | % fpm --version 25 | 1.14.0 26 | 27 | Now you can go on to `using FPM! `_ 28 | 29 | Installing optional dependencies 30 | -------------------------------- 31 | 32 | .. warning:: 33 | This section may be imperfect; please make sure you are installing the right package for your OS. 34 | 35 | Some package formats require other tools to be installed on your machine to be built; especially if you are building a package for another operating system/distribution. 36 | 37 | * RPM: rpm/rpm-tools/rpm-build [This dependency might be removed in the future, see `issue #54`_ on github] 38 | * Snap: squashfs/squashfs-tools 39 | 40 | .. _issue #54: https://github.com/jordansissel/fpm/issues/54 41 | 42 | .. note:: 43 | You will not be able to build an osxpkg package (.pkg) for MacOS unless you are running MacOS. 44 | 45 | Here are instructions to install these dependencies on your machine: 46 | 47 | On OSX/macOS:: 48 | 49 | brew install rpm squashfs 50 | 51 | On Arch Linux and Arch-based systems (Manjaro, EndeavourOS, etc):: 52 | 53 | pacman -S rpm-tools squashfs-tools 54 | 55 | On Debian and Debian-based systems (Ubuntu, Linux Mint, Pop!_OS, etc):: 56 | 57 | apt-get install squashfs-tools 58 | 59 | On Red Hat systems (Fedora 22 or older, CentOS, Rocky Linux, etc):: 60 | 61 | yum install rpm-build squashfs-tools 62 | 63 | On Fedora 23 or newer:: 64 | 65 | dnf install rpm-build squashfs-tools 66 | 67 | On Oracle Linux 7.x systems:: 68 | 69 | yum-config-manager --enable ol7_optional_latest 70 | yum install rpm-build squashfs-tools 71 | -------------------------------------------------------------------------------- /docs/packages/apk.rst: -------------------------------------------------------------------------------- 1 | apk - Alpine package format 2 | =========================== 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports using ``apk`` only as an output type. This means you can create ``apk`` packages from input types like ``deb``, ``dir``, or ``npm`` 8 | 9 | apk-specific command line flags 10 | ------------------------------- 11 | 12 | .. include:: cli/apk.rst 13 | -------------------------------------------------------------------------------- /docs/packages/cli/apk.rst: -------------------------------------------------------------------------------- 1 | This package type has no additional options 2 | 3 | -------------------------------------------------------------------------------- /docs/packages/cli/cpan.rst: -------------------------------------------------------------------------------- 1 | * ``--cpan-cpanm-bin CPANM_EXECUTABLE`` 2 | - The path to the cpanm executable you wish to run. 3 | * ``--[no-]cpan-cpanm-force`` 4 | - Pass the --force parameter to cpanm 5 | * ``--cpan-mirror CPAN_MIRROR`` 6 | - The CPAN mirror to use instead of the default. 7 | * ``--[no-]cpan-mirror-only`` 8 | - Only use the specified mirror for metadata. 9 | * ``--cpan-package-name-prefix NAME_PREFIX`` 10 | - Name to prefix the package name with. 11 | * ``--cpan-perl-bin PERL_EXECUTABLE`` 12 | - The path to the perl executable you wish to run. 13 | * ``--cpan-perl-lib-path PERL_LIB_PATH`` 14 | - Path of target Perl Libraries 15 | * ``--[no-]cpan-sandbox-non-core`` 16 | - Sandbox all non-core modules, even if they're already installed 17 | * ``--[no-]cpan-test`` 18 | - Run the tests before packaging? 19 | * ``--[no-]cpan-verbose`` 20 | - Produce verbose output from cpanm? 21 | 22 | -------------------------------------------------------------------------------- /docs/packages/cli/deb.rst: -------------------------------------------------------------------------------- 1 | * ``--deb-activate EVENT`` 2 | - Package activates EVENT trigger 3 | * ``--deb-activate-noawait EVENT`` 4 | - Package activates EVENT trigger 5 | * ``--deb-after-purge FILE`` 6 | - A script to be run after package removal to purge remaining (config) files (a.k.a. postrm purge within apt-get purge) 7 | * ``--[no-]deb-auto-config-files`` 8 | - Init script and default configuration files will be labeled as configuration files for Debian packages. 9 | * ``--deb-build-depends DEPENDENCY`` 10 | - Add DEPENDENCY as a Build-Depends 11 | * ``--deb-changelog FILEPATH`` 12 | - Add FILEPATH as debian changelog 13 | * ``--deb-compression COMPRESSION`` 14 | - The compression type to use, must be one of gz, bzip2, xz, zst, none. 15 | * ``--deb-compression-level [0-9]`` 16 | - Select a compression level. 0 is none or minimal. 9 is max compression. 17 | * ``--deb-config SCRIPTPATH`` 18 | - Add SCRIPTPATH as debconf config file. 19 | * ``--deb-custom-control FILEPATH`` 20 | - Custom version of the Debian control file. 21 | * ``--deb-default FILEPATH`` 22 | - Add FILEPATH as /etc/default configuration 23 | * ``--deb-dist DIST-TAG`` 24 | - Set the deb distribution. 25 | * ``--deb-field 'FIELD: VALUE'`` 26 | - Add custom field to the control file 27 | * ``--[no-]deb-generate-changes`` 28 | - Generate PACKAGENAME.changes file. 29 | * ``--deb-group GROUP`` 30 | - The group owner of files in this package 31 | * ``--[no-]deb-ignore-iteration-in-dependencies`` 32 | - 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 33 | * ``--deb-init FILEPATH`` 34 | - Add FILEPATH as an init script 35 | * ``--deb-installed-size KILOBYTES`` 36 | - The installed size, in kilobytes. If omitted, this will be calculated automatically 37 | * ``--deb-interest EVENT`` 38 | - Package is interested in EVENT trigger 39 | * ``--deb-interest-noawait EVENT`` 40 | - Package is interested in EVENT trigger without awaiting 41 | * ``--[no-]deb-maintainerscripts-force-errorchecks`` 42 | - Activate errexit shell option according to lintian. https://lintian.debian.org/tags/maintainer-script-ignores-errors.html 43 | * ``--deb-meta-file FILEPATH`` 44 | - Add FILEPATH to DEBIAN directory 45 | * ``--[no-]deb-no-default-config-files`` 46 | - Do not add all files in /etc as configuration files by default for Debian packages. 47 | * ``--deb-pre-depends DEPENDENCY`` 48 | - Add DEPENDENCY as a Pre-Depends 49 | * ``--deb-priority PRIORITY`` 50 | - The debian package 'priority' value. 51 | * ``--deb-recommends PACKAGE`` 52 | - Add PACKAGE to Recommends 53 | * ``--deb-shlibs SHLIBS`` 54 | - Include control/shlibs content. This flag expects a string that is used as the contents of the shlibs file. See the following url for a description of this file and its format: http://www.debian.org/doc/debian-policy/ch-sharedlibs.html#s-shlibs 55 | * ``--deb-suggests PACKAGE`` 56 | - Add PACKAGE to Suggests 57 | * ``--deb-systemd FILEPATH`` 58 | - Add FILEPATH as a systemd script 59 | * ``--[no-]deb-systemd-auto-start`` 60 | - Start service after install or upgrade 61 | * ``--[no-]deb-systemd-enable`` 62 | - Enable service on install or upgrade 63 | * ``--deb-systemd-path FILEPATH`` 64 | - Relative path to the systemd service directory 65 | * ``--[no-]deb-systemd-restart-after-upgrade`` 66 | - Restart service after upgrade 67 | * ``--deb-templates FILEPATH`` 68 | - Add FILEPATH as debconf templates file. 69 | * ``--deb-upstart FILEPATH`` 70 | - Add FILEPATH as an upstart script 71 | * ``--deb-upstream-changelog FILEPATH`` 72 | - Add FILEPATH as upstream changelog 73 | * ``--[no-]deb-use-file-permissions`` 74 | - Use existing file permissions when defining ownership and modes 75 | * ``--deb-user USER`` 76 | - The owner of files in this package 77 | 78 | -------------------------------------------------------------------------------- /docs/packages/cli/dir.rst: -------------------------------------------------------------------------------- 1 | This package type has no additional options 2 | 3 | -------------------------------------------------------------------------------- /docs/packages/cli/empty.rst: -------------------------------------------------------------------------------- 1 | This package type has no additional options 2 | 3 | -------------------------------------------------------------------------------- /docs/packages/cli/freebsd.rst: -------------------------------------------------------------------------------- 1 | * ``--freebsd-origin ABI`` 2 | - Sets the FreeBSD 'origin' pkg field 3 | * ``--freebsd-osversion VERSION`` 4 | - Sets the FreeBSD 'version' pkg field, ie. 12 or 13, use '*' for all. 5 | 6 | -------------------------------------------------------------------------------- /docs/packages/cli/gem.rst: -------------------------------------------------------------------------------- 1 | * ``--gem-bin-path DIRECTORY`` 2 | - The directory to install gem executables 3 | * ``--gem-disable-dependency gem_name`` 4 | - The gem name to remove from dependency list 5 | * ``--[no-]gem-embed-dependencies`` 6 | - Should the gem dependencies be installed? 7 | * ``--[no-]gem-env-shebang`` 8 | - Should the target package have the shebang rewritten to use env? 9 | * ``--[no-]gem-fix-dependencies`` 10 | - Should the package dependencies be prefixed? 11 | * ``--[no-]gem-fix-name`` 12 | - Should the target package name be prefixed? 13 | * ``--gem-gem PATH_TO_GEM`` 14 | - The path to the 'gem' tool (defaults to 'gem' and searches your $PATH) 15 | * ``--gem-git-branch GIT_BRANCH`` 16 | - When using a git repo as the source of the gem instead of rubygems.org, use this git branch. 17 | * ``--gem-git-repo GIT_REPO`` 18 | - Use this git repo address as the source of the gem instead of rubygems.org. 19 | * ``--gem-package-name-prefix PREFIX`` 20 | - Name to prefix the package name with. 21 | * ``--gem-package-prefix NAMEPREFIX`` 22 | - (DEPRECATED, use --package-name-prefix) Name to prefix the package name with. 23 | * ``--[no-]gem-prerelease`` 24 | - Allow prerelease versions of a gem 25 | * ``--gem-shebang SHEBANG`` 26 | - Replace the shebang in the executables in the bin path with a custom string 27 | * ``--gem-stagingdir STAGINGDIR`` 28 | - The directory where fpm installs the gem temporarily before conversion. Normally a random subdirectory of workdir. 29 | * ``--[no-]gem-version-bins`` 30 | - Append the version to the bins 31 | 32 | -------------------------------------------------------------------------------- /docs/packages/cli/npm.rst: -------------------------------------------------------------------------------- 1 | * ``--npm-bin NPM_EXECUTABLE`` 2 | - The path to the npm executable you wish to run. 3 | * ``--npm-package-name-prefix PREFIX`` 4 | - Name to prefix the package name with. 5 | * ``--npm-registry NPM_REGISTRY`` 6 | - The npm registry to use instead of the default. 7 | 8 | -------------------------------------------------------------------------------- /docs/packages/cli/osxpkg.rst: -------------------------------------------------------------------------------- 1 | * ``--osxpkg-dont-obsolete DONT_OBSOLETE_PATH`` 2 | - A file path for which to 'dont-obsolete' in the built PackageInfo. Can be specified multiple times. 3 | * ``--osxpkg-identifier-prefix IDENTIFIER_PREFIX`` 4 | - Reverse domain prefix prepended to package identifier, ie. 'org.great.my'. If this is omitted, the identifer will be the package name. 5 | * ``--osxpkg-ownership OWNERSHIP`` 6 | - --ownership option passed to pkgbuild. Defaults to 'recommended'. See pkgbuild(1). 7 | * ``--[no-]osxpkg-payload-free`` 8 | - Define no payload, assumes use of script options. 9 | * ``--osxpkg-postinstall-action POSTINSTALL_ACTION`` 10 | - Post-install action provided in package metadata. Optionally one of 'logout', 'restart', 'shutdown'. 11 | 12 | -------------------------------------------------------------------------------- /docs/packages/cli/p5p.rst: -------------------------------------------------------------------------------- 1 | * ``--p5p-group GROUP`` 2 | - Set the group to GROUP in the prototype file. 3 | * ``--[no-]p5p-lint`` 4 | - Check manifest with pkglint 5 | * ``--p5p-publisher PUBLISHER`` 6 | - Set the publisher name for the repository 7 | * ``--p5p-user USER`` 8 | - Set the user to USER in the prototype files. 9 | * ``--[no-]p5p-validate`` 10 | - Validate with pkg install 11 | * ``--p5p-zonetype ZONETYPE`` 12 | - Set the allowed zone types (global, nonglobal, both) 13 | 14 | -------------------------------------------------------------------------------- /docs/packages/cli/pacman.rst: -------------------------------------------------------------------------------- 1 | * ``--pacman-compression COMPRESSION`` 2 | - The compression type to use, must be one of gz, bzip2, xz, zstd, none. 3 | * ``--pacman-group GROUP`` 4 | - The group owner of files in this package 5 | * ``--pacman-optional-depends PACKAGE`` 6 | - Add an optional dependency to the pacman package. 7 | * ``--[no-]pacman-use-file-permissions`` 8 | - Use existing file permissions when defining ownership and modes 9 | * ``--pacman-user USER`` 10 | - The owner of files in this package 11 | 12 | -------------------------------------------------------------------------------- /docs/packages/cli/pear.rst: -------------------------------------------------------------------------------- 1 | * ``--pear-bin-dir BIN_DIR`` 2 | - Directory to put binaries in 3 | * ``--pear-channel CHANNEL_URL`` 4 | - The pear channel url to use instead of the default. 5 | * ``--[no-]pear-channel-update`` 6 | - call 'pear channel-update' prior to installation 7 | * ``--pear-data-dir DATA_DIR`` 8 | - Specify php dir relative to prefix if differs from pear default (pear/data) 9 | * ``--pear-package-name-prefix PREFIX`` 10 | - Name prefix for pear package 11 | * ``--pear-php-bin PHP_BIN`` 12 | - Specify php executable path if differs from the os used for packaging 13 | * ``--pear-php-dir PHP_DIR`` 14 | - Specify php dir relative to prefix if differs from pear default (pear/php) 15 | 16 | -------------------------------------------------------------------------------- /docs/packages/cli/pkgin.rst: -------------------------------------------------------------------------------- 1 | This package type has no additional options 2 | 3 | -------------------------------------------------------------------------------- /docs/packages/cli/pleaserun.rst: -------------------------------------------------------------------------------- 1 | * ``--pleaserun-chdir CHDIR`` 2 | - The working directory used by the service 3 | * ``--pleaserun-name SERVICE_NAME`` 4 | - The name of the service you are creating 5 | * ``--pleaserun-user USER`` 6 | - The user to use for executing this program. 7 | 8 | -------------------------------------------------------------------------------- /docs/packages/cli/puppet.rst: -------------------------------------------------------------------------------- 1 | This package type has no additional options 2 | 3 | -------------------------------------------------------------------------------- /docs/packages/cli/python.rst: -------------------------------------------------------------------------------- 1 | * ``--python-bin PYTHON_EXECUTABLE`` 2 | - The path to the python executable you wish to run. 3 | * ``--[no-]python-dependencies`` 4 | - Include requirements defined in setup.py as dependencies. 5 | * ``--python-disable-dependency python_package_name`` 6 | - The python package name to remove from dependency list 7 | * ``--[no-]python-downcase-dependencies`` 8 | - Should the package dependencies be in lowercase? 9 | * ``--[no-]python-downcase-name`` 10 | - Should the target package name be in lowercase? 11 | * ``--python-easyinstall EASYINSTALL_EXECUTABLE`` 12 | - The path to the easy_install executable tool 13 | * ``--[no-]python-fix-dependencies`` 14 | - Should the package dependencies be prefixed? 15 | * ``--[no-]python-fix-name`` 16 | - Should the target package name be prefixed? 17 | * ``--python-install-bin BIN_PATH`` 18 | - The path to where python scripts should be installed to. 19 | * ``--python-install-data DATA_PATH`` 20 | - The path to where data should be installed to. This is equivalent to 'python setup.py --install-data DATA_PATH 21 | * ``--python-install-lib LIB_PATH`` 22 | - The path to where python libs should be installed to (default depends on your python installation). Want to find out what your target platform is using? Run this: python -c 'from distutils.sysconfig import get_python_lib; print get_python_lib()' 23 | * ``--[no-]python-internal-pip`` 24 | - Use the pip module within python to install modules - aka 'python -m pip'. This is the recommended usage since Python 3.4 (2014) instead of invoking the 'pip' script 25 | * ``--[no-]python-obey-requirements-txt`` 26 | - Use a requirements.txt file in the top-level directory of the python package for dependency detection. 27 | * ``--python-package-name-prefix PREFIX`` 28 | - Name to prefix the package name with. 29 | * ``--python-package-prefix NAMEPREFIX`` 30 | - (DEPRECATED, use --package-name-prefix) Name to prefix the package name with. 31 | * ``--python-pip PIP_EXECUTABLE`` 32 | - The path to the pip executable tool. If not specified, easy_install is used instead 33 | * ``--python-pypi PYPI_URL`` 34 | - PyPi Server uri for retrieving packages. 35 | * ``--python-scripts-executable PYTHON_EXECUTABLE`` 36 | - 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. 37 | * ``--python-setup-py-arguments setup_py_argument`` 38 | - Arbitrary argument(s) to be passed to setup.py 39 | * ``--python-trusted-host PYPI_TRUSTED`` 40 | - Mark this host or host:port pair as trusted for pip 41 | 42 | -------------------------------------------------------------------------------- /docs/packages/cli/rpm.rst: -------------------------------------------------------------------------------- 1 | * ``--rpm-attr ATTRFILE`` 2 | - Set the attribute for a file (%attr), e.g. --rpm-attr 750,user1,group1:/some/file 3 | * ``--[no-]rpm-auto-add-directories`` 4 | - Auto add directories not part of filesystem 5 | * ``--rpm-auto-add-exclude-directories DIRECTORIES`` 6 | - Additional directories ignored by '--rpm-auto-add-directories' flag 7 | * ``--[no-]rpm-autoprov`` 8 | - Enable RPM's AutoProv option 9 | * ``--[no-]rpm-autoreq`` 10 | - Enable RPM's AutoReq option 11 | * ``--[no-]rpm-autoreqprov`` 12 | - Enable RPM's AutoReqProv option 13 | * ``--rpm-changelog FILEPATH`` 14 | - Add changelog from FILEPATH contents 15 | * ``--rpm-compression none|xz|xzmt|gzip|bzip2`` 16 | - Select a compression method. gzip works on the most platforms. 17 | * ``--rpm-compression-level [0-9]`` 18 | - Select a compression level. 0 is store-only. 9 is max compression. 19 | * ``--rpm-defattrdir ATTR`` 20 | - Set the default dir mode (%defattr). 21 | * ``--rpm-defattrfile ATTR`` 22 | - Set the default file mode (%defattr). 23 | * ``--rpm-digest md5|sha1|sha256|sha384|sha512`` 24 | - Select a digest algorithm. md5 works on the most platforms. 25 | * ``--rpm-dist DIST-TAG`` 26 | - Set the rpm distribution. 27 | * ``--rpm-filter-from-provides REGEX`` 28 | - Set %filter_from_provides to the supplied REGEX. 29 | * ``--rpm-filter-from-requires REGEX`` 30 | - Set %filter_from_requires to the supplied REGEX. 31 | * ``--rpm-group GROUP`` 32 | - Set the group to GROUP in the %files section. Overrides the group when used with use-file-permissions setting. 33 | * ``--[no-]rpm-ignore-iteration-in-dependencies`` 34 | - 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 35 | * ``--rpm-init FILEPATH`` 36 | - Add FILEPATH as an init script 37 | * ``--[no-]rpm-macro-expansion`` 38 | - install-time macro expansion in %pre %post %preun %postun scripts (see: https://rpm.org/user_doc/scriptlet_expansion.html) 39 | * ``--[no-]rpm-old-perl-dependency-name`` 40 | - Use older 'perl' depdency name. Newer Red Hat (and derivatives) use a dependency named 'perl-interpreter'. 41 | * ``--rpm-os OS`` 42 | - The operating system to target this rpm for. You want to set this to 'linux' if you are using fpm on OS X, for example 43 | * ``--rpm-posttrans FILE`` 44 | - posttrans script 45 | * ``--rpm-pretrans FILE`` 46 | - pretrans script 47 | * ``--rpm-rpmbuild-define DEFINITION`` 48 | - Pass a --define argument to rpmbuild. 49 | * ``--[no-]rpm-sign`` 50 | - Pass --sign to rpmbuild 51 | * ``--rpm-summary SUMMARY`` 52 | - Set the RPM summary. Overrides the first line on the description if set 53 | * ``--rpm-tag TAG`` 54 | - Adds a custom tag in the spec file as is. Example: --rpm-tag 'Requires(post): /usr/sbin/alternatives' 55 | * ``--rpm-trigger-after-install '[OPT]PACKAGE: FILEPATH'`` 56 | - Adds a rpm trigger script located in FILEPATH, having 'OPT' options and linking to 'PACKAGE'. PACKAGE can be a comma seperated list of packages. See: http://rpm.org/api/4.4.2.2/triggers.html 57 | * ``--rpm-trigger-after-target-uninstall '[OPT]PACKAGE: FILEPATH'`` 58 | - Adds a rpm trigger script located in FILEPATH, having 'OPT' options and linking to 'PACKAGE'. PACKAGE can be a comma seperated list of packages. See: http://rpm.org/api/4.4.2.2/triggers.html 59 | * ``--rpm-trigger-before-install '[OPT]PACKAGE: FILEPATH'`` 60 | - Adds a rpm trigger script located in FILEPATH, having 'OPT' options and linking to 'PACKAGE'. PACKAGE can be a comma seperated list of packages. See: http://rpm.org/api/4.4.2.2/triggers.html 61 | * ``--rpm-trigger-before-uninstall '[OPT]PACKAGE: FILEPATH'`` 62 | - Adds a rpm trigger script located in FILEPATH, having 'OPT' options and linking to 'PACKAGE'. PACKAGE can be a comma seperated list of packages. See: http://rpm.org/api/4.4.2.2/triggers.html 63 | * ``--[no-]rpm-use-file-permissions`` 64 | - Use existing file permissions when defining ownership and modes. 65 | * ``--rpm-user USER`` 66 | - Set the user to USER in the %files section. Overrides the user when used with use-file-permissions setting. 67 | * ``--[no-]rpm-verbatim-gem-dependencies`` 68 | - 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). 69 | * ``--rpm-verifyscript FILE`` 70 | - a script to be run on verification 71 | 72 | -------------------------------------------------------------------------------- /docs/packages/cli/sh.rst: -------------------------------------------------------------------------------- 1 | This package type has no additional options 2 | 3 | -------------------------------------------------------------------------------- /docs/packages/cli/snap.rst: -------------------------------------------------------------------------------- 1 | * ``--snap-confinement CONFINEMENT`` 2 | - Type of confinement to use for this snap. 3 | * ``--snap-grade GRADE`` 4 | - Grade of this snap. 5 | * ``--snap-yaml FILEPATH`` 6 | - Custom version of the snap.yaml file. 7 | 8 | -------------------------------------------------------------------------------- /docs/packages/cli/solaris.rst: -------------------------------------------------------------------------------- 1 | * ``--solaris-group GROUP`` 2 | - Set the group to GROUP in the prototype file. 3 | * ``--solaris-user USER`` 4 | - Set the user to USER in the prototype files. 5 | 6 | -------------------------------------------------------------------------------- /docs/packages/cli/tar.rst: -------------------------------------------------------------------------------- 1 | This package type has no additional options 2 | 3 | -------------------------------------------------------------------------------- /docs/packages/cli/virtualenv.rst: -------------------------------------------------------------------------------- 1 | * ``--virtualenv-find-links PIP_FIND_LINKS`` 2 | - If a url or path to an html file, then parse for links to archives. If a local path or file:// url that's a directory, then look for archives in the directory listing. 3 | * ``--[no-]virtualenv-fix-name`` 4 | - Should the target package name be prefixed? 5 | * ``--virtualenv-install-location DIRECTORY`` 6 | - DEPRECATED: Use --prefix instead. Location to which to install the virtualenv by default. 7 | * ``--virtualenv-other-files-dir DIRECTORY`` 8 | - Optionally, the contents of the specified directory may be added to the package. This is useful if the virtualenv needs configuration files, etc. 9 | * ``--virtualenv-package-name-prefix PREFIX`` 10 | - Name to prefix the package name with. 11 | * ``--virtualenv-pypi PYPI_URL`` 12 | - PyPi Server uri for retrieving packages. 13 | * ``--virtualenv-pypi-extra-url PYPI_EXTRA_URL`` 14 | - PyPi extra-index-url for pointing to your priviate PyPi 15 | * ``--[no-]virtualenv-setup-install`` 16 | - After building virtualenv run setup.py install useful when building a virtualenv for packages and including their requirements from 17 | * ``--[no-]virtualenv-system-site-packages`` 18 | - Give the virtual environment access to the global site-packages 19 | 20 | -------------------------------------------------------------------------------- /docs/packages/cli/zip.rst: -------------------------------------------------------------------------------- 1 | This package type has no additional options 2 | 3 | -------------------------------------------------------------------------------- /docs/packages/cpan.rst: -------------------------------------------------------------------------------- 1 | cpan - Perl packages from CPAN 2 | =============================== 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports using ``cpan`` only as an input type. This means you can convert 8 | ``cpan`` input packages to output packages like ``deb``, ``rpm``, and more. 9 | 10 | Arguments when used as input type 11 | --------------------------------- 12 | 13 | Any number of arguments are supported and behave as follows: 14 | 15 | * A name to search on MetaCPAN_. If a module is found on MetaCPAN_, it will be downloaded and used when building the package. 16 | * or, a local directory containing a Perl module to build. 17 | 18 | .. _MetaCPAN: https://metacpan.org/ 19 | 20 | Sample Usage 21 | ------------ 22 | 23 | Let's take the `Regexp::Common `_ Perl module and package it as a deb. We can let fpm do the hard work here of finding the module on cpan and downloading it:: 24 | 25 | % fpm -s cpan -t deb Regexp::Common 26 | Downcasing provides 'perl-Regexp-Common' because deb packages don't work so good with uppercase names {:level=>:warn} 27 | Downcasing provides 'perl-Regexp-Common-Entry' because deb packages don't work so good with uppercase names {:level=>:warn} 28 | Debian tools (dpkg/apt) don't do well with packages that use capital letters in the name. In some cases it will automatically downcase them, in others it will not. It is confusing. Best to not use any capital letters at all. I have downcased the package name for you just to be safe. {:oldname=>"perl-Regexp-Common", :fixedname=>"perl-regexp-common", :level=>:warn} 29 | Debian packaging tools generally labels all files in /etc as config files, as mandated by policy, so fpm defaults to this behavior for deb packages. You can disable this default behavior with --deb-no-default-config-files flag {:level=>:warn} 30 | Created package {:path=>"perl-regexp-common_2017060201_all.deb"} 31 | 32 | Fpm did a bunch of nice work for you. First, it searched MetaCPAN_ for Regexp::Common. Then it downloaded the latest version. If you wanted to specify a version, you can use the ``-v`` flag, such as ``-v 2016060201``. 33 | 34 | In the example above, a few warning messages appear. Fpm's job is to help you convert packages. In this case, we're converting a Perl module named "Regexp::Common" to a Debian package. In this situation, we need to make sure our Debian package is accepted by Debian's tools! This means fpm will do the following: 35 | 36 | * Debian package names appear to all use lowercase names, so fpm does this for you. 37 | * Debian package names also cannot have "::" in the names, so fpm replaces these with a dash "-" 38 | 39 | Let's try to use our new package! First, installing it:: 40 | 41 | % sudo dpkg -i perl-regexp-common_2017060201_all.deb 42 | Selecting previously unselected package perl-regexp-common. 43 | (Reading database ... 81209 files and directories currently installed.) 44 | Preparing to unpack perl-regexp-common_2017060201_all.deb ... 45 | Unpacking perl-regexp-common (2017060201) ... 46 | Setting up perl-regexp-common (2017060201) ... 47 | Processing triggers for man-db (2.9.1-1) ... 48 | 49 | And try to use it. Let's ask Regexp::Common for a regular expression that matches real numbers:: 50 | 51 | % perl -MRegexp::Common -e 'print $RE{num}{real}' 52 | (?:(?i)(?:[-+]?)(?:(?=[.]?[0123456789])(?:[0123456789]*)(?:(?:[.])(?:[0123456789]{0,}))?)(?:(?:[E])(?:(?:[-+]?)(?:[0123456789]+))|)) 53 | 54 | Nice! 55 | 56 | Fun Examples 57 | ------------ 58 | 59 | .. note:: 60 | Do you have any examples you want to share that use the ``cpan`` package type? Share your knowledge here: https://github.com/jordansissel/fpm/issues/new 61 | 62 | cpan-specific command line flags 63 | ------------------------------- 64 | 65 | .. include:: cli/cpan.rst 66 | -------------------------------------------------------------------------------- /docs/packages/deb.rst: -------------------------------------------------------------------------------- 1 | deb - Debian package format 2 | =========================== 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports input and output for Debian package (deb). This means you can read a deb and convert it to a different output type (such as a `dir` or `rpm`). It also means you can create a deb package. 8 | 9 | Arguments when used as input type 10 | --------------------------------- 11 | 12 | For the sample command reading a deb file as input and outputting an rpm package:: 13 | 14 | fpm -s deb -t rpm file.deb 15 | 16 | The argument is used as a file and read as a debian package file. 17 | 18 | Sample Usage 19 | ------------ 20 | 21 | Let's create a Debian package of Hashicorp's Terraform. To do this, we'll need to download it and put the files into a Debian package:: 22 | 23 | # Download Terraform 1.0.10 24 | % wget https://releases.hashicorp.com/terraform/1.0.10/terraform_1.0.10_linux_amd64.zip 25 | 26 | The Terraform release .zip file contains a single file, `terraform` itself. You can see the files in this zip by using `unzip -l`:: 27 | 28 | % unzip -l ~/build/z/terraform_1.0.10_linux_amd64.zip 29 | Archive: /home/jls/build/z/terraform_1.0.10_linux_amd64.zip 30 | Length Date Time Name 31 | --------- ---------- ----- ---- 32 | 79348596 2021-10-28 07:15 terraform 33 | --------- ------- 34 | 79348596 1 file 35 | 36 | We can use fpm to convert this zip file into a debian package with one step:: 37 | 38 | % fpm -s zip -t deb --prefix /usr/bin -n terraform -v 1.0.10 terraform_1.0.10_linux_amd64.zip 39 | Created package {:path=>"terraform_1.0.10_amd64.deb"} 40 | 41 | Nice! We just converted a zip file into a debian package. Let's talk through the command-line flags here: 42 | 43 | * ``-s zip`` tells fpm to use "zip" as the input type. This allows fpm to read zip files. 44 | * ``-t deb`` tells fpm to output a Debian package. 45 | * ``--prefix /usr/bin`` tells fpm to move all files in the .zip file to the /usr/bin file path. In this case, it results in a single file in the path `/usr/bin/terraform` 46 | * ``-n terraform`` names the package "terraform" 47 | * ``-v 1.0.10`` sets the package version. This is useful to package systems when considering whether a given package is an upgrade, downgrade, or already installed. 48 | * Finally, the last argument, `terraform_1.0.10_linux_amd64.zip`. This is given to the fpm to process as a zip file. 49 | 50 | You can inspect the package contents with `dpkg --contents terraform_1.0.10_amd64.deb`:: 51 | 52 | % dpkg --contents terraform_1.0.10_amd64.deb 53 | drwxr-xr-x 0/0 0 2021-11-02 23:33 ./ 54 | drwxr-xr-x 0/0 0 2021-11-02 23:33 ./usr/ 55 | drwxr-xr-x 0/0 0 2021-11-02 23:33 ./usr/share/ 56 | drwxr-xr-x 0/0 0 2021-11-02 23:33 ./usr/share/doc/ 57 | drwxr-xr-x 0/0 0 2021-11-02 23:33 ./usr/share/doc/terraform/ 58 | -rw-r--r-- 0/0 141 2021-11-02 23:33 ./usr/share/doc/terraform/changelog.gz 59 | drwxr-xr-x 0/0 0 2021-11-02 23:33 ./usr/bin/ 60 | -rwxr-xr-x 0/0 79348596 2021-10-28 07:15 ./usr/bin/terraform 61 | 62 | The ``changelog.gz`` file is a recommended Debian practice for packaging. FPM will provide a generated changelog for you, by default. You can provide your own with the ``--deb-changelog`` flag. 63 | 64 | Lets install our terraform package and try it out:: 65 | 66 | % sudo apt install ./terraform_1.0.10_amd64.deb 67 | ... 68 | 69 | % dpkg -l terraform 70 | Desired=Unknown/Install/Remove/Purge/Hold 71 | | Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend 72 | |/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad) 73 | ||/ Name Version Architecture Description 74 | +++-========================-=================-=================-===================================================== 75 | ii terraform 1.0.10 amd64 no description given 76 | 77 | % terraform -version 78 | Terraform v1.0.10 79 | on linux_amd64 80 | 81 | You may remove the package at any time:: 82 | 83 | % sudo apt remove terraform 84 | ... 85 | Removing terraform (1.0.10) ... 86 | 87 | 88 | Fun Examples 89 | ------------ 90 | 91 | Hi! The fpm project would love to have any fun examples you have for using this package type. Please consider contributing your ideas by submitting them on the fpm issue tracker: https://github.com/jordansissel/fpm/issues/new 92 | 93 | Changing an existing deb 94 | ~~~~~~~~~~~~~~~~~~~~~~~~ 95 | 96 | fpm supports deb as both an input and output type (``-s`` and ``-t`` flags), so you can use this to modify an existing deb. 97 | 98 | For example, let's create an deb to use for our example:: 99 | 100 | % fpm -s empty -t deb -n example 101 | Created package {:path=>"example_1.0_amd64.deb"} 102 | 103 | Lets say we made a mistake and want to rename the package:: 104 | 105 | % fpm -s deb -t deb -n newname example_1.0_amd64.deb 106 | Created package {:path=>"newname_1.0_amd64.deb"} 107 | 108 | And maybe the architecture is wrong. fpm defaulted to amd64 (what fpm calls 109 | "native"), and we really want what Debian calls "all":: 110 | 111 | % fpm -s deb -t deb -a all newname_1.0_amd64.deb 112 | Created package {:path=>"newname_1.0_all.deb"} 113 | 114 | Deb-specific command line flags 115 | ------------------------------- 116 | 117 | .. include:: cli/deb.rst 118 | -------------------------------------------------------------------------------- /docs/packages/dir.rst: -------------------------------------------------------------------------------- 1 | dir - Local Files 2 | ================= 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports using ``dir`` as an input type and output type. This means you can use ``dir`` to put files into other package types (like Debian or Red Hat packages). You can also use this as an output type to extract files from packages. 8 | 9 | Arguments when used as input type 10 | --------------------------------- 11 | 12 | Any number of arguments are supported and behave as follows: 13 | 14 | 1) A path to a local file or directory will be put into the output package as-is with the same path, contents, and metadata (file owner, modification date, etc) 15 | 2) A syntax of "localpath=destinationpath" to copy local paths into the output package with the destination path. 16 | 17 | The local file paths are modified by the ``--chdir`` flag. The destination file paths are modified by the ``--prefix`` flag. 18 | 19 | Sample Usage 20 | ------------ 21 | 22 | For this example, let's look at packaging the Kubernetes tool, ``kubectl``. The installation for ``kubectl`` recommends downloading a pre-compiled binary. Let's do this and then package it into a Debian package. 23 | 24 | First, we download the ``kubectl`` binary, according to the kubernetes documentation for kubectl installation on Linux:: 25 | 26 | # Query the latest version of kubectl and store the value in the 'version' variable 27 | % version="$(curl -L -s https://dl.k8s.io/release/stable.txt)" 28 | 29 | # Download the Linux amd64 binary 30 | % curl -LO "https://dl.k8s.io/release/${version}/bin/linux/amd64/kubectl" 31 | 32 | # Make it executable 33 | % chmod 755 kubectl 34 | 35 | The above shell will find the latest version of ``kubectl`` and download it. We'll use the file and the version number next to make our package:: 36 | 37 | # Create the package that installs kubectl as /usr/bin/kubectl 38 | % fpm -s dir -t deb -n kubectl -a amd64 -v ${version#v*} kubectl=/usr/bin/kubectl 39 | Created package {:path=>"kubectl_v1.22.3_amd64.deb"} 40 | 41 | .. note:: 42 | We use ``${version#v*}`` in our shell to set the package version. This is 43 | because Kuberenetes versions have a text that starts with "v" and this is not 44 | valid in Debian packages. This will turn "v1.2.3" into "1.2.3" for our package. 45 | 46 | Now we can check our package to make sure it looks the way we want:: 47 | 48 | % dpkg --contents kubectl_1.22.3_amd64.deb 49 | [ ... output abbreviated for easier reading ... ] 50 | -rw-r--r-- 0/0 46907392 2021-11-05 20:09 ./usr/bin/kubectl 51 | 52 | % dpkg --field kubectl_1.22.3_amd64.deb Package Version Architecture 53 | Package: kubectl 54 | Version: 1.22.3 55 | Architecture: amd64 56 | 57 | And install it to test things and make sure it's what we wanted:: 58 | 59 | % sudo dpkg -i kubectl_1.22.3_amd64.deb 60 | Selecting previously unselected package kubectl. 61 | (Reading database ... 58110 files and directories currently installed.) 62 | Preparing to unpack kubectl_1.22.3_amd64.deb ... 63 | Unpacking kubectl (1.22.3) ... 64 | Setting up kubectl (1.22.3) ... 65 | 66 | And try to use it:: 67 | 68 | % which kubectl 69 | /usr/bin/kubectl 70 | 71 | % kubectl version 72 | Client Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.3", GitCommit:"c92036820499fedefec0f847e2054d824aea6cd1", GitTreeState:"clean", BuildDate:"2021-10-27T18:41:28Z", GoVersion:"go1.16.9", Compiler:"gc", Platform:"linux/amd64"} 73 | 74 | Cool :) 75 | 76 | Fun Examples 77 | ------------ 78 | 79 | .. note:: 80 | Do you have any examples you want to share that use the ``dir`` package type? Share your knowledge here: https://github.com/jordansissel/fpm/issues/new 81 | -------------------------------------------------------------------------------- /docs/packages/empty.rst: -------------------------------------------------------------------------------- 1 | empty - A package with no files 2 | =============================== 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports using ``empty`` only as an input type. 8 | 9 | Arguments when used as input type 10 | --------------------------------- 11 | 12 | Extra arguments are ignored for this type. As an example, where with ``fpm -s dir ...`` the arguments are file paths, ``fpm -s empty`` takes no input arguments because there's no file contents to use. 13 | 14 | Sample Usage 15 | ------------ 16 | 17 | The ``empty`` package type is great for creating "meta" packages which are used to group dependencies together. 18 | 19 | For example, if you want to make it easier to install a collection of developer tools, you could create a single package that depends on all of your desired developer tools. 20 | 21 | Let's create a Debian package named 'devtools' which installs the following: 22 | 23 | * git 24 | * curl 25 | * nodejs 26 | 27 | Here's the fpm command to do this:: 28 | 29 | % fpm -s empty -t rpm -n devtools -a all -d git -d curl -d nodejs 30 | Created package {:path=>"devtools-1.0-1.noarch.rpm"} 31 | 32 | We can check the dependencies on this package:: 33 | 34 | % rpm -qp devtools-1.0-1.noarch.rpm --requires 35 | curl 36 | git 37 | nodejs 38 | rpmlib(CompressedFileNames) <= 3.0.4-1 39 | rpmlib(PayloadFilesHavePrefix) <= 4.0-1 40 | 41 | And see that there are no files:: 42 | 43 | % rpm -ql devtools-1.0-1.noarch.rpm 44 | (contains no files) 45 | 46 | 47 | Fun Examples 48 | ------------ 49 | 50 | Hi! The fpm project would love to have any fun examples you have for using this package type. Please consider contributing your ideas by submitting them on the fpm issue tracker: https://github.com/jordansissel/fpm/issues/new 51 | -------------------------------------------------------------------------------- /docs/packages/freebsd.rst: -------------------------------------------------------------------------------- 1 | freebsd - FreeBSD pkg 2 | =============================== 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports using ``freebsd`` only as an output type. This means you can create ``freebsd`` packages from input types like ``deb``, ``dir``, or ``npm`` 8 | 9 | freebsd-specific command line flags 10 | ------------------------------- 11 | 12 | .. include:: cli/freebsd.rst 13 | -------------------------------------------------------------------------------- /docs/packages/gem.rst: -------------------------------------------------------------------------------- 1 | gem - Ruby's rubygem packages 2 | ============================= 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports using ``gem`` only as an input type. This means you can convert 8 | ``gem`` input packages to output packages like ``deb``, ``rpm``, and more. 9 | 10 | gem-specific command line flags 11 | ------------------------------- 12 | 13 | .. include:: cli/gem.rst 14 | -------------------------------------------------------------------------------- /docs/packages/npm.rst: -------------------------------------------------------------------------------- 1 | npm - Packages for NodeJS 2 | =============================== 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports using ``npm`` only as an input type. 8 | 9 | Arguments when used as input type 10 | --------------------------------- 11 | 12 | Any number of arguments are supported and behave as follows: 13 | 14 | * ``name@version`` -- a specific named package at the given version. 15 | * ``name`` -- the name of a node package. In this use, the ``--version`` flag is used to pick the version to download. If no version is given, the latest version of the package is downloaded. 16 | 17 | Sample Usage 18 | ------------ 19 | 20 | You'll need ``npm`` installed for this example. 21 | 22 | Let's turn the ``ascii-art`` npm package into a Debian package. For this example, we'll pick a specific version, 2.8.5:: 23 | 24 | % fpm --debug -s npm -t deb --depends nodejs ascii-art@2.8.5 25 | Created package {:path=>"node-ascii-art_2.8.5_amd64.deb"} 26 | 27 | Fpm uses ``npm`` to download the correct package. Additionally, the package name is given a ``node-`` prefix because this is common in distribution packages to prefix a library with the platform name, such as ``python-foo`` or ``node-foo``. 28 | 29 | It also parses the package's ``package.json`` to collect any useful data such as the package name, author, homepage, description, etc:: 30 | 31 | % dpkg --field node-ascii-art_2.8.5_amd64.deb Package Version Vendor Homepage Description 32 | Package: node-ascii-art 33 | Version: 2.8.5 34 | Vendor: Abbey Hawk Sparrow <@khrome> 35 | Homepage: git://github.com/khrome/ascii-art.git 36 | Description: Ansi codes, figlet fonts, and ascii art. 100% JS 37 | 38 | Let's install the package and try to use it:: 39 | 40 | % sudo apt-get install ./node-ascii-art_2.8.5_amd64.deb 41 | 42 | And now we can use this package:: 43 | 44 | % ascii-art text -F Doom "Hello World" 45 | _ _ _ _ _ _ _ _ 46 | | | | | | || | | | | | | | | | 47 | | |_| | ___ | || | ___ | | | | ___ _ __ | | __| | 48 | | _ | / _ \| || | / _ \ | |/\| | / _ \ | '__|| | / _` | 49 | | | | || __/| || || (_) | \ /\ /| (_) || | | || (_| | 50 | \_| |_/ \___||_||_| \___/ \/ \/ \___/ |_| |_| \__,_| 51 | 52 | Fpm asked ``npm`` where to install things using ``npm prefix -g``. On my system, this caused the package to install to ``/usr/local/lib/node_modules``. You can change the default prefix with the fpm ``--prefix`` flag or by changing the default global prefix in the ``npm`` tool. 53 | 54 | Let's try to invoke ``ascii-art`` from node:: 55 | 56 | % export NODE_PATH=/usr/local/lib/node_modules 57 | % node 58 | > let art = require("ascii-art") 59 | > art.font("Hello", "Doom", (err, rendered) => console.log(rendered)) 60 | _ _ _ _ 61 | | | | | | || | 62 | | |_| | ___ | || | ___ 63 | | _ | / _ \| || | / _ \ 64 | | | | || __/| || || (_) | 65 | \_| |_/ \___||_||_| \___/ 66 | 67 | Nice :) 68 | 69 | Fun Examples 70 | ------------ 71 | 72 | .. note:: 73 | Do you have any examples you want to share that use the ``npm`` package type? Share your knowledge here: https://github.com/jordansissel/fpm/issues/new 74 | 75 | npm-specific command line flags 76 | ------------------------------- 77 | 78 | .. include:: cli/npm.rst 79 | -------------------------------------------------------------------------------- /docs/packages/osxpkg.rst: -------------------------------------------------------------------------------- 1 | osxpkg - Apple macOS and OSX packages 2 | ===================================== 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports input and output for Apple's package files. These files typically 8 | end in ".pkg". This means you can read a ``.pkg`` file and convert it to a different 9 | output type (such as a `dir` or `rpm`). It also means you can create a ``.pkg`` 10 | package file. 11 | 12 | osxpkg-specific command line flags 13 | ------------------------------- 14 | 15 | .. include:: cli/osxpkg.rst 16 | -------------------------------------------------------------------------------- /docs/packages/p5p.rst: -------------------------------------------------------------------------------- 1 | p5p - Oracle Solaris 11 p5p packages 2 | ==================================== 3 | 4 | This package format for the Solaris Image Packaging System (IPS) and should be 5 | useful for OpenSolaris, Solaris 11, and Illumos. 6 | 7 | Supported Uses in FPM 8 | --------------------- 9 | 10 | fpm supports using ``p5p`` only as an output type. This means you can create ``p5p`` packages from input types like ``deb``, ``dir``, or ``npm`` 11 | 12 | p5p-specific command line flags 13 | ------------------------------- 14 | 15 | .. include:: cli/p5p.rst 16 | -------------------------------------------------------------------------------- /docs/packages/pacman.rst: -------------------------------------------------------------------------------- 1 | pacman - Arch Linux package format 2 | ================================== 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports input and output for Arch Linux's package format, pacman. This means you can read a pacman and convert it to a different output type (such as a `dir` or `rpm`). It also means you can create a pacman package. 8 | 9 | pacman-specific command line flags 10 | ------------------------------- 11 | 12 | .. include:: cli/pacman.rst 13 | -------------------------------------------------------------------------------- /docs/packages/pear.rst: -------------------------------------------------------------------------------- 1 | pear - PHP PEAR Packages 2 | ======================== 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports using ``pear`` only as an input type. This means you can convert 8 | ``pear`` input packages to output packages like ``deb``, ``rpm``, and more. 9 | 10 | pear-specific command line flags 11 | -------------------------------- 12 | 13 | .. include:: cli/pear.rst 14 | -------------------------------------------------------------------------------- /docs/packages/pkgin.rst: -------------------------------------------------------------------------------- 1 | pkgin - NetBSD's pkgsrc binary packages 2 | ======================================= 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports using ``pkgin`` only as an output type. This means you can create ``pkgin`` packages from input types like ``deb``, ``dir``, or ``npm`` 8 | 9 | pkgin-specific command line flags 10 | ------------------------------- 11 | 12 | .. include:: cli/pkgin.rst 13 | -------------------------------------------------------------------------------- /docs/packages/pleaserun.rst: -------------------------------------------------------------------------------- 1 | pleaserun - Pleaserun services 2 | ============================== 3 | 4 | Pleaserun helps generate service definitions for a variety of service manangers 5 | such as systemd and sysv. 6 | 7 | When used as an input, fpm will generate a package that include multiple service 8 | definitions, one for each type (systemd, sysv, etc). At package installation, the package 9 | will attempt to detect the best service manager used on the system and will 10 | install only that definition. 11 | 12 | You can learn more on the project website: https://github.com/jordansissel/pleaserun#readme 13 | 14 | Supported Uses in FPM 15 | --------------------- 16 | 17 | fpm supports using ``pleaserun`` only as an input type. This means you can convert 18 | ``pleaserun`` input packages to output packages like ``deb``, ``rpm``, and more. 19 | 20 | pleaserun-specific command line flags 21 | ------------------------------- 22 | 23 | .. include:: cli/pleaserun.rst 24 | -------------------------------------------------------------------------------- /docs/packages/puppet.rst: -------------------------------------------------------------------------------- 1 | puppet - Puppet Manifests (deprecated) 2 | ====================================== 3 | 4 | Note: This package type hasn't been maintained since 2012 and generates puppet 5 | manifests that target Puppet 2.4 or 3.x. 6 | 7 | Supported Uses in FPM 8 | --------------------- 9 | 10 | fpm supports using ``puppet`` only as an output type. This means you can create ``puppet`` packages from input types like ``deb``, ``dir``, or ``npm`` 11 | 12 | puppet-specific command line flags 13 | ---------------------------------- 14 | 15 | .. include:: cli/puppet.rst 16 | -------------------------------------------------------------------------------- /docs/packages/python.rst: -------------------------------------------------------------------------------- 1 | python - Python packages, PyPI, etc 2 | =================================== 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports using ``python`` only as an input type. This means you can convert 8 | ``python`` input packages to output packages like ``deb``, ``rpm``, and more. 9 | 10 | python-specific command line flags 11 | ---------------------------------- 12 | 13 | .. include:: cli/python.rst 14 | -------------------------------------------------------------------------------- /docs/packages/rpm.rst: -------------------------------------------------------------------------------- 1 | rpm - RedHat Package Manager 2 | ============================ 3 | 4 | rpm is the package format used on RedHat Enterprise (RHEL), Fedora, CentOS, and 5 | a number of other Linux distributions. 6 | 7 | You may be familiar with tools such as `dnf` and `yum` for installing packages from repositories. The package files that these tools install are rpms. 8 | 9 | Supported Uses in FPM 10 | --------------------- 11 | 12 | fpm supports input and output for rpms. This means you can read an rpm and convert it to a different output type (such as a `dir` or `deb`). It also means you can write an rpm. 13 | 14 | Arguments when used as input type 15 | --------------------------------- 16 | 17 | For the sample command reading an rpm as input and outputting a debian package:: 18 | 19 | fpm -s rpm -t deb file.rpm 20 | 21 | The the argument is used as a file and read as an rpm. 22 | 23 | Sample Usage 24 | ------------ 25 | 26 | Create a package with no files but having dependencies:: 27 | 28 | % fpm -s empty -t rpm -n example --depends nginx 29 | Created package {:path=>"example-1.0-1.x86_64.rpm"} 30 | 31 | We can now inspect the package with rpm's tools if you wish:: 32 | 33 | % rpm -qp example-1.0-1.x86_64.rpm -i 34 | Name : example 35 | Version : 1.0 36 | Release : 1 37 | Architecture: x86_64 38 | Install Date: (not installed) 39 | Group : default 40 | Size : 0 41 | License : unknown 42 | Signature : (none) 43 | Source RPM : example-1.0-1.src.rpm 44 | Build Date : Wed 20 Oct 2021 09:43:25 PM PDT 45 | Build Host : snickerdoodle.localdomain 46 | Relocations : / 47 | Packager : 48 | Vendor : none 49 | URL : http://example.com/no-uri-given 50 | Summary : no description given 51 | Description : 52 | no description given 53 | 54 | Fun Examples 55 | ------------ 56 | 57 | Changing an existing RPM 58 | ~~~~~~~~~~~~~~~~~~~~~~~~ 59 | 60 | fpm supports rpm as both an input and output type (`-s` and `-t` flags), so you can use this to modify an existing rpm. 61 | 62 | For example, let's create an rpm to use for our example:: 63 | 64 | % fpm -s empty -t rpm -n example 65 | Created package {:path=>"example-1.0-1.x86_64.rpm"} 66 | 67 | Lets say we made a mistake and want to rename the package:: 68 | 69 | % fpm -s rpm -t rpm -n newname example-1.0-1.x86_64.rpm 70 | Created package {:path=>"newname-1.0-1.x86_64.rpm"} 71 | 72 | And maybe the architecture is wrong. fpm defaulted to x86_64 (what fpm calls 73 | "native"), and we really want what rpm calls "noarch":: 74 | 75 | % fpm -s rpm -t rpm -a noarch newname-1.0-1.x86_64.rpm 76 | Created package {:path=>"newname-1.0-1.noarch.rpm"} 77 | 78 | RPM-specific command line flags 79 | ------------------------------- 80 | 81 | .. include:: cli/rpm.rst 82 | -------------------------------------------------------------------------------- /docs/packages/sh.rst: -------------------------------------------------------------------------------- 1 | sh - Self-managing shell archive 2 | ================================ 3 | 4 | The 'sh' output in fpm will generate a shell script that is, itself, an archive. 5 | 6 | The resulting shell script will install the files you provided. You can run the 7 | resulting shell script to see more helpful information. 8 | 9 | # Create an example.sh package 10 | % fpm -s empty -t sh -n example 11 | 12 | # Get help. 13 | % ./example.sh -h 14 | 15 | 16 | Supported Uses in FPM 17 | --------------------- 18 | 19 | fpm supports using ``sh`` only as an output type. This means you can create ``sh`` packages from input types like ``deb``, ``dir``, or ``npm`` 20 | 21 | sh-specific command line flags 22 | ------------------------------- 23 | 24 | .. include:: cli/sh.rst 25 | -------------------------------------------------------------------------------- /docs/packages/snap.rst: -------------------------------------------------------------------------------- 1 | snap - Application package files for Linux 2 | ========================================== 3 | 4 | You can learn more about ``snap`` itself by visiting the website: https://snapcraft.io/about 5 | 6 | 7 | Supported Uses in FPM 8 | --------------------- 9 | 10 | fpm supports input and output for snap packages. This means you can read a snap and convert it to a different output type (such as a `dir` or `rpm`). It also means you can create a snap package. 11 | 12 | snap-specific command line flags 13 | -------------------------------- 14 | 15 | .. include:: cli/snap.rst 16 | -------------------------------------------------------------------------------- /docs/packages/solaris.rst: -------------------------------------------------------------------------------- 1 | solaris - Solaris SRV4 package format 2 | ===================================== 3 | 4 | This package format is typically used in older Solaris versions (Solaris 7, 8, 5 | 9, and 10). You may also know them by files with a SUNW prefix and may have file names that end in ".pkg". 6 | 7 | If you're using Solaris 11, OpenSolaris, or Illumos, you might want to use `the newer package format, p5p`_. 8 | 9 | .. _newer package format, p5p: /packages/p5p.html 10 | 11 | Supported Uses in FPM 12 | --------------------- 13 | 14 | fpm supports using ``solaris`` only as an output type. This means you can create ``solaris`` packages from input types like ``deb``, ``dir``, or ``npm`` 15 | 16 | solaris-specific command line flags 17 | ----------------------------------- 18 | 19 | .. include:: cli/solaris.rst 20 | -------------------------------------------------------------------------------- /docs/packages/tar.rst: -------------------------------------------------------------------------------- 1 | tar - Tape archives aka "tar" files 2 | =================================== 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports input and output for NAME (tar). This means you can read a tar and convert it to a different output type (such as a `dir` or `rpm`). It also means you can create a tar package. 8 | 9 | tar-specific command line flags 10 | ------------------------------- 11 | 12 | .. include:: cli/tar.rst 13 | -------------------------------------------------------------------------------- /docs/packages/virtualenv.rst: -------------------------------------------------------------------------------- 1 | virtualenv - Python virtualenv 2 | ============================== 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports using ``virtualenv`` only as an input type. This means you can convert 8 | ``virtualenv`` input packages to output packages like ``deb``, ``rpm``, and more. 9 | 10 | virtualenv-specific command line flags 11 | -------------------------------------- 12 | 13 | .. include:: cli/virtualenv.rst 14 | -------------------------------------------------------------------------------- /docs/packages/zip.rst: -------------------------------------------------------------------------------- 1 | zip - Zip files 2 | =============== 3 | 4 | Supported Uses in FPM 5 | --------------------- 6 | 7 | fpm supports input and output for zip files. This means you can read a zip and convert it to a different output type (such as a `dir` or `rpm`). It also means you can create a zip package. 8 | 9 | zip-specific command line flags 10 | ------------------------------- 11 | 12 | .. include:: cli/zip.rst 13 | -------------------------------------------------------------------------------- /docs/packaging-types.rst: -------------------------------------------------------------------------------- 1 | Packaging Types 2 | =============== 3 | 4 | .. toctree:: 5 | :includehidden: 6 | :glob: 7 | 8 | packages/* 9 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # https://blog.readthedocs.com/build-errors-docutils-0-18/ 2 | docutils<0.18 3 | -------------------------------------------------------------------------------- /examples/api/dir-to-deb-rpm-with-init.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $: << File.join(File.dirname(__FILE__), "..", "..", "lib") 3 | 4 | # This example uses the API to create a package from local files 5 | # it also creates necessary init-scripts and systemd files so our executable can be used as a service 6 | 7 | require "fpm" 8 | require "tmpdir" 9 | require "fpm/package/pleaserun" 10 | 11 | # enable logging 12 | FPM::Util.send :module_function, :logger 13 | FPM::Util.logger.level = :info 14 | FPM::Util.logger.subscribe STDERR 15 | 16 | package = FPM::Package::Dir.new 17 | 18 | # Set some attributes 19 | package.name = "my-service" 20 | package.version = "1.0" 21 | 22 | # Add a script to run after install (should be in the current directory): 23 | package.scripts[:after_install] = 'my_after_install_script.sh' 24 | 25 | # Example for adding special attributes 26 | package.attributes[:deb_group] = "super-useful" 27 | package.attributes[:rpm_group] = "super-useful" 28 | 29 | # Add our files (should be in the current directory): 30 | package.input("my-executable=/usr/bin/") 31 | package.input("my-library.so=/usr/lib/") 32 | 33 | # Now, add our init-scripts, systemd services, and so on: 34 | pleaserun = package.convert(FPM::Package::PleaseRun) 35 | pleaserun.input ["/usr/bin/my-executable", "--foo-from", "bar"] 36 | 37 | # Create two output packages! 38 | output_packages = [] 39 | output_packages << pleaserun.convert(FPM::Package::RPM) 40 | output_packages << pleaserun.convert(FPM::Package::Deb) 41 | 42 | # and write them both. 43 | begin 44 | output_packages.each do |output_package| 45 | output = output_package.to_s 46 | output_package.output(output) 47 | 48 | puts "successfully created #{output}" 49 | end 50 | ensure 51 | # defer cleanup until the end 52 | output_packages.each {|p| p.cleanup} 53 | end 54 | -------------------------------------------------------------------------------- /examples/api/gem-to-rpm.rb: -------------------------------------------------------------------------------- 1 | $: << File.join(File.dirname(__FILE__), "..", "..", "lib") 2 | require "fpm" 3 | 4 | package = FPM::Package::Gem.new 5 | 6 | # the Gem package takes a string name of the package to download/install. 7 | # Example, run this script with 'rails' as an argument and it will convert 8 | # the latest 'rails' gem into rpm. 9 | package.input(ARGV[0]) 10 | rpm = package.convert(FPM::Package::RPM) 11 | begin 12 | output = "NAME-VERSION.ARCH.rpm" 13 | rpm.output(rpm.to_s(output)) 14 | ensure 15 | rpm.cleanup 16 | end 17 | -------------------------------------------------------------------------------- /examples/api/multiple-to-rpm.rb: -------------------------------------------------------------------------------- 1 | $: << File.join(File.dirname(__FILE__), "..", "..", "lib") 2 | require "fpm" 3 | 4 | package = FPM::Package::Gem.new 5 | ARGV.each do |gem| 6 | name, version = gem.split(/[=]/, 2) 7 | package.version = version # Allow specifying a specific version 8 | package.input(gem) 9 | end 10 | rpm = package.convert(FPM::Package::RPM) 11 | rpm.name = "rubygem-manythings" 12 | rpm.version = "1.0" 13 | begin 14 | output = "NAME-VERSION.ARCH.rpm" 15 | rpm.output(rpm.to_s(output)) 16 | ensure 17 | rpm.cleanup 18 | end 19 | -------------------------------------------------------------------------------- /examples/fpm-with-system-ruby/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # feel free to change these to whatever makes sense 3 | # 4 | # debian package we rely on 5 | RUBY_PACKAGE=ruby 6 | # and the executable that comes from it 7 | RUBY_BIN=/usr/bin/ruby 8 | # the version we name the deb 9 | VERSION=1.1.0 10 | # where to get the sauce 11 | GIT_URL=https://github.com/jordansissel/fpm.git 12 | # the tag we checkout to build from 13 | TAG_SPEC=refs/tags/v$(VERSION) 14 | 15 | CHECKOUT_DIR=fpm-checkout 16 | BUILD_DIR=build 17 | LIB_DIR=$(BUILD_DIR)/usr/lib/fpm 18 | BIN_DIR=$(BUILD_DIR)/usr/bin 19 | GEM_PATH:=$(shell readlink -f .)/build/gem 20 | FPM_BIN=$(BIN_DIR)/fpm 21 | BUNDLE_BIN=$(GEM_PATH)/bin/bundle 22 | BUNDLE_CMD=$(RUBY_CMD) $(BUNDLE_BIN) 23 | FPM_CMD=$(FPM_BIN) 24 | GEM_CMD=$(RUBY_BIN) -S gem 25 | 26 | .PHONY: clean 27 | clean: 28 | rm -rf $(CHECKOUT_DIR) 29 | rm -rf $(BUILD_DIR) 30 | rm -f fpm*.deb 31 | 32 | $(CHECKOUT_DIR): 33 | rm -rf $(CHECKOUT_DIR) 34 | git clone $(GIT_URL) $(CHECKOUT_DIR) --depth 1 35 | cd $(CHECKOUT_DIR) && git fetch && git checkout $(TAG_SPEC) 36 | 37 | $(BUNDLE_BIN): 38 | $(GEM_CMD) install bundler --install-dir=$(GEM_PATH) --no-ri --no-rdoc 39 | 40 | $(FPM_BIN): 41 | mkdir --parents $(BIN_DIR) 42 | # Couldn't think of a nice way to do this, so here is this code turd 43 | echo "#! $(RUBY_BIN)" > $(FPM_BIN) 44 | echo 'load File.dirname($$0) + "/../lib/fpm/bundle/bundler/setup.rb"' >> $(FPM_BIN) 45 | echo 'require "fpm"' >> $(FPM_BIN) 46 | echo 'require "fpm/command"' >> $(FPM_BIN) 47 | echo 'exit(FPM::Command.run || 0)' >> $(FPM_BIN) 48 | chmod +x $(FPM_BIN) 49 | 50 | .PHONY: install 51 | install: $(CHECKOUT_DIR) $(BUNDLE_BIN) $(FPM_BIN) 52 | mkdir --parents $(LIB_DIR) 53 | cd $(CHECKOUT_DIR) && GEM_PATH=$(GEM_PATH) $(BUNDLE_CMD) install --without=development --standalone 54 | cd $(CHECKOUT_DIR) && gem build fpm.gemspec 55 | tar -xf $(CHECKOUT_DIR)/fpm*gem -C $(BUILD_DIR) 56 | tar --touch -xf $(BUILD_DIR)/data.tar.gz -C $(LIB_DIR) 57 | cp -r $(CHECKOUT_DIR)/bundle $(LIB_DIR)/bundle 58 | 59 | .PHONY: package 60 | package: install 61 | $(FPM_BIN) -s dir -t deb -n fpm -d $(RUBY_PACKAGE) -v $(VERSION) -C $(BUILD_DIR) usr 62 | -------------------------------------------------------------------------------- /examples/fpm-with-system-ruby/README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | - build and install dependency on a ruby1.9 of some kind 4 | - does not need root to package 5 | - has its own GEM_DIR to keep its dependencies isolated 6 | - installation does not install any gems in to your ruby environment 7 | - installs in to standard locations /usr/{bin,lib}/fpm 8 | - doesn't depend on having fpm installed for packaging to work 9 | 10 | # Dependencies 11 | 12 | - build-essential (perhaps more, but basically the standard packages you need 13 | for deb packaging) 14 | - ruby and ruby-dev 15 | 16 | # Usage 17 | 18 | - $ cd examples/fpm-with-system-ruby && make package 19 | -------------------------------------------------------------------------------- /examples/fpm/Makefile: -------------------------------------------------------------------------------- 1 | NAME=ruby 2 | VERSION=1.9.2-p180 3 | MAJOR_VERSION=1.9 4 | ARCHITECTURE=x86 5 | TARDIR=$(NAME)-$(VERSION) 6 | TARBALL=$(TARDIR).tar.gz 7 | DOWNLOAD=http://ftp.ruby-lang.org/pub/ruby/$(MAJOR_VERSION)/$(TARBALL) 8 | 9 | PREFIX=/opt/fpm 10 | 11 | PACKAGE_NAME=fpm 12 | PACKAGE_VERSION=0.2.30 13 | 14 | .PHONY: default 15 | default: deb 16 | package: deb 17 | 18 | .PHONY: clean 19 | clean: 20 | rm -f $(NAME)-* $(NAME)_* |NAME| true 21 | rm -fr $(TARDIR) || true 22 | rm -fr $(PREFIX) || true 23 | rm -f *.deb 24 | 25 | $(TARBALL): 26 | wget "$(DOWNLOAD)" 27 | 28 | $(TARDIR): $(TARBALL) 29 | tar -zxf $(TARBALL) 30 | cd $(TARDIR); ./configure --enable-shared=false --prefix=$(PREFIX); make; make install 31 | $(PREFIX)/bin/gem install fpm 32 | 33 | .PHONY: deb 34 | deb: $(TARDIR) 35 | $(PREFIX)/bin/fpm -s dir -t deb -v $(PACKAGE_VERSION) -n $(PACKAGE_NAME) -a $(ARCHITECTURE) -C $(PREFIX) . 36 | -------------------------------------------------------------------------------- /examples/fpm/README.md: -------------------------------------------------------------------------------- 1 | Notes: 2 | 3 | You should have write permission on /opt directory 4 | 5 | Dependencies: 6 | 7 | $ sudo apt-get install build-essential bison openssl libreadline6 libreadline6-dev zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libxml2-dev libxslt-dev autoconf libc6-dev 8 | 9 | Usage: 10 | 11 | $ make package 12 | 13 | Should make the package. Try installing: 14 | 15 | $ sudo dpkg -i fpm-0.2.30.x86.deb 16 | 17 | Now try it: 18 | 19 | $ /opt/fpm/bin/fpm --help 20 | -------------------------------------------------------------------------------- /examples/jruby/Makefile: -------------------------------------------------------------------------------- 1 | NAME=jruby 2 | VERSION=1.6.1 3 | DOWNLOAD=http://jruby.org.s3.amazonaws.com/downloads/$(VERSION)/$(TARBALL) 4 | TARBALL=$(NAME)-bin-$(VERSION).tar.gz 5 | TARDIR=$(NAME)-$(VERSION) 6 | 7 | PREFIX=/opt/jruby 8 | 9 | .PHONY: default 10 | default: deb 11 | package: deb 12 | 13 | .PHONY: clean 14 | clean: 15 | rm -f $(NAME)-* $(NAME)_* || true 16 | rm -fr $(TARDIR) || true 17 | rm -f *.deb 18 | rm -f *.rpm 19 | 20 | $(TARBALL): 21 | wget "$(DOWNLOAD)" 22 | 23 | $(TARDIR): $(TARBALL) 24 | tar -zxf $(TARBALL) 25 | 26 | .PHONY: deb 27 | deb: $(TARDIR) 28 | fpm -s dir -t deb -v $(VERSION) -n $(NAME) -d "sun-java6-bin (>> 0)" \ 29 | -a all -d "sun-java6-jre (>> 0)" --prefix $(PREFIX) -C $(TARDIR) . 30 | 31 | .PHONY: rpm 32 | rpm: $(TARDIR) 33 | fpm -s dir -t rpm -v $(VERSION) -n $(NAME) -d "jdk >= 1.6.0" \ 34 | -a all --prefix $(PREFIX) -C $(TARDIR) . 35 | 36 | -------------------------------------------------------------------------------- /examples/jruby/README.md: -------------------------------------------------------------------------------- 1 | Usage: 2 | 3 | make package 4 | 5 | Should make the package. Try installing: 6 | 7 | sudo dpkg -i jruby-1.6.0.RC2-1.all.deb 8 | 9 | Now try it: 10 | 11 | % /opt/jruby/bin/jirb 12 | >> require "java" 13 | => true 14 | >> java.lang.System.out.println("Hello") 15 | Hello 16 | => nil 17 | 18 | -------------------------------------------------------------------------------- /examples/python/twisted/Makefile: -------------------------------------------------------------------------------- 1 | NAME=twisted 2 | VERSION=10.2.0 3 | 4 | twisted: 5 | easy_install --editable --build-directory . "$(NAME)==$(VERSION)" 6 | 7 | usr: twisted 8 | cd twisted; python setup.py bdist 9 | tar -zxf twisted/dist/Twisted-$(VERSION).linux-$(shell uname -m).tar.gz 10 | 11 | package: usr 12 | fpm -s dir -t deb -n $(NAME) -v $(VERSION) \ 13 | -p python-$(NAME)-VERSION_ARCH.deb -d "python" \ 14 | usr 15 | 16 | -------------------------------------------------------------------------------- /fpm.gemspec: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), "lib/fpm/version") 2 | Gem::Specification.new do |spec| 3 | files = [] 4 | dirs = %w{lib bin templates} 5 | dirs.each do |dir| 6 | files += Dir["#{dir}/**/*"] 7 | end 8 | 9 | files << "LICENSE" 10 | files << "CONTRIBUTORS" 11 | files << "CHANGELOG.rst" 12 | 13 | files = files.reject { |path| path =~ /\.pyc$/ } 14 | 15 | spec.name = "fpm" 16 | spec.version = FPM::VERSION 17 | spec.summary = "fpm - package building and mangling" 18 | spec.description = "Convert directories, rpms, python eggs, rubygems, and " \ 19 | "more to rpms, debs, solaris packages and more. Win at package " \ 20 | "management without wasting pointless hours debugging bad rpm specs!" 21 | spec.license = "MIT-like" 22 | 23 | spec.required_ruby_version = '>= 1.9.3' 24 | 25 | # For logging 26 | # https://github.com/jordansissel/ruby-cabin 27 | spec.add_dependency("cabin", ">= 0.6.0") # license: Apache 2 28 | 29 | # For backports to older rubies 30 | # https://github.com/marcandre/backports 31 | spec.add_dependency("backports", ">= 2.6.2") # license: MIT 32 | 33 | # For reading and writing rpms 34 | spec.add_dependency("arr-pm", "~> 0.0.11") # license: Apache 2 35 | 36 | # For command-line flag support 37 | # https://github.com/mdub/clamp/blob/master/README.markdown 38 | spec.add_dependency("clamp", ">= 1.0.0") # license: MIT 39 | 40 | # For sourcing from pleaserun 41 | spec.add_dependency("pleaserun", "~> 0.0.29") # license: Apache 2 42 | 43 | spec.add_dependency("stud") 44 | 45 | # In Ruby 3.0, rexml was moved to a bundled gem instead of a default one, 46 | # so I think this needs to be added explicitly? 47 | spec.add_dependency("rexml") 48 | 49 | spec.add_development_dependency("rspec", "~> 3.0.0") # license: MIT (according to wikipedia) 50 | spec.add_development_dependency("insist", "~> 1.0.0") # license: Apache 2 51 | spec.add_development_dependency("pry") 52 | 53 | spec.add_development_dependency("rake") # For FPM::RakeTask, #1877, #756 54 | 55 | spec.files = files 56 | spec.require_paths << "lib" 57 | spec.bindir = "bin" 58 | spec.executables << "fpm" 59 | 60 | spec.author = "Jordan Sissel" 61 | spec.email = "jls@semicomplete.com" 62 | spec.homepage = "https://github.com/jordansissel/fpm" 63 | end 64 | 65 | -------------------------------------------------------------------------------- /lib/fpm.rb: -------------------------------------------------------------------------------- 1 | require "fpm/namespace" 2 | 3 | require "fpm/package" 4 | require "fpm/package/dir" 5 | require "fpm/package/gem" 6 | require "fpm/package/deb" 7 | require "fpm/package/npm" 8 | require "fpm/package/rpm" 9 | require "fpm/package/tar" 10 | require "fpm/package/cpan" 11 | require "fpm/package/pear" 12 | require "fpm/package/empty" 13 | require "fpm/package/puppet" 14 | require "fpm/package/python" 15 | require "fpm/package/osxpkg" 16 | require "fpm/package/solaris" 17 | require "fpm/package/p5p" 18 | require "fpm/package/pkgin" 19 | require "fpm/package/freebsd" 20 | require "fpm/package/apk" 21 | require "fpm/package/snap" 22 | -------------------------------------------------------------------------------- /lib/fpm/errors.rb: -------------------------------------------------------------------------------- 1 | require "fpm/namespace" 2 | 3 | # Raised if a package is configured in an unsupported way 4 | class FPM::InvalidPackageConfiguration < StandardError; end 5 | -------------------------------------------------------------------------------- /lib/fpm/namespace.rb: -------------------------------------------------------------------------------- 1 | # The FPM namespace 2 | module FPM 3 | class Package; end 4 | end 5 | -------------------------------------------------------------------------------- /lib/fpm/package/empty.rb: -------------------------------------------------------------------------------- 1 | require "fpm/package" 2 | require "backports/latest" 3 | 4 | # Empty Package type. For strict/meta/virtual package creation 5 | 6 | class FPM::Package::Empty < FPM::Package 7 | def initialize(*args) 8 | super(*args) 9 | 10 | # Override FPM::Package's default "native" architecture value 11 | # This feels like the right default because an empty package has no 12 | # architecture-specific files, and in most cases an empty package should be 13 | # installable anywhere. 14 | # 15 | # https://github.com/jordansissel/fpm/issues/1846 16 | @architecture = "all" 17 | end 18 | 19 | def output(output_path) 20 | logger.warn("Your package has gone into the void.") 21 | end 22 | def to_s(fmt) 23 | return "" 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/fpm/package/npm.rb: -------------------------------------------------------------------------------- 1 | require "fpm/namespace" 2 | require "fpm/package" 3 | require "fpm/util" 4 | require "fileutils" 5 | 6 | class FPM::Package::NPM < FPM::Package 7 | class << self 8 | include FPM::Util 9 | end 10 | # Flags '--foo' will be accessable as attributes[:npm_foo] 11 | option "--bin", "NPM_EXECUTABLE", 12 | "The path to the npm executable you wish to run.", :default => "npm" 13 | 14 | option "--package-name-prefix", "PREFIX", "Name to prefix the package " \ 15 | "name with.", :default => "node" 16 | 17 | option "--registry", "NPM_REGISTRY", 18 | "The npm registry to use instead of the default." 19 | 20 | private 21 | def input(package) 22 | # Notes: 23 | # * npm respects PREFIX 24 | settings = { 25 | "cache" => build_path("npm_cache"), 26 | "loglevel" => "warn", 27 | "global" => "true" 28 | } 29 | 30 | settings["registry"] = attributes[:npm_registry] if attributes[:npm_registry_given?] 31 | set_default_prefix unless attributes[:prefix_given?] 32 | 33 | settings["prefix"] = staging_path(attributes[:prefix]) 34 | FileUtils.mkdir_p(settings["prefix"]) 35 | 36 | npm_flags = [] 37 | settings.each do |key, value| 38 | # npm lets you specify settings in a .npmrc but the same key=value settings 39 | # are valid as '--key value' command arguments to npm. Woo! 40 | logger.debug("Configuring npm", key => value) 41 | npm_flags += [ "--#{key}", value ] 42 | end 43 | 44 | install_args = [ 45 | attributes[:npm_bin], 46 | "install", 47 | # use 'package' or 'package@version' 48 | (version ? "#{package}@#{version}" : package) 49 | ] 50 | 51 | # The only way to get npm to respect the 'prefix' setting appears to 52 | # be to set the --global flag. 53 | #install_args << "--global" 54 | install_args += npm_flags 55 | 56 | safesystem(*install_args) 57 | 58 | # Query details about our now-installed package. 59 | # We do this by using 'npm ls' with json + long enabled to query details 60 | # about the installed package. 61 | npm_ls_out = safesystemout(attributes[:npm_bin], "ls", "--json", "--long", *npm_flags) 62 | npm_ls = JSON.parse(npm_ls_out) 63 | name, info = npm_ls["dependencies"].first 64 | 65 | self.name = [attributes[:npm_package_name_prefix], name].join("-") 66 | self.version = info.fetch("version", "0.0.0") 67 | 68 | if info.include?("repository") 69 | self.url = info["repository"]["url"] 70 | else 71 | self.url = "https://npmjs.org/package/#{self.name}" 72 | end 73 | 74 | self.description = info["description"] 75 | # Supposedly you can upload a package for npm with no author/author email 76 | # so I'm being safer with this. Author can also be a hash or a string 77 | self.vendor = "Unknown " 78 | if info.include?("author") 79 | author_info = info["author"] 80 | # If a hash, assemble into a string 81 | if author_info.respond_to? :fetch 82 | self.vendor = sprintf("%s <%s>", author_info.fetch("name", "unknown"), 83 | author_info.fetch("email", "unknown@unknown.unknown")) 84 | else 85 | # Probably will need a better check for validity here 86 | self.vendor = author_info unless author_info == "" 87 | end 88 | end 89 | 90 | # npm installs dependencies in the module itself, so if you do 91 | # 'npm install express' it installs dependencies (like 'connect') 92 | # to: node_modules/express/node_modules/connect/... 93 | # 94 | # To that end, I don't think we necessarily need to include 95 | # any automatic dependency information since every 'npm install' 96 | # is fully self-contained. That's why you don't see any bother, yet, 97 | # to include the package's dependencies in here. 98 | # 99 | # It's possible someone will want to decouple that in the future, 100 | # but I will wait for that feature request. 101 | end 102 | 103 | def set_default_prefix 104 | attributes[:prefix] = self.class.default_prefix 105 | attributes[:prefix_given?] = true 106 | end 107 | 108 | def self.default_prefix 109 | npm_prefix = safesystemout("npm", "prefix", "-g").chomp 110 | if npm_prefix.count("\n") > 0 111 | raise FPM::InvalidPackageConfiguration, "`npm prefix -g` returned unexpected output." 112 | elsif !File.directory?(npm_prefix) 113 | raise FPM::InvalidPackageConfiguration, "`npm prefix -g` returned a non-existent directory" 114 | end 115 | logger.info("Setting default npm install prefix", :prefix => npm_prefix) 116 | npm_prefix 117 | end 118 | 119 | public(:input) 120 | end # class FPM::Package::NPM 121 | -------------------------------------------------------------------------------- /lib/fpm/package/p5p.rb: -------------------------------------------------------------------------------- 1 | require "erb" 2 | require "fpm/namespace" 3 | require "fpm/package" 4 | require "fpm/errors" 5 | require "fpm/util" 6 | 7 | class FPM::Package::P5P < FPM::Package 8 | 9 | option "--user", "USER", 10 | "Set the user to USER in the prototype files.", 11 | :default => 'root' 12 | 13 | option "--group", "GROUP", 14 | "Set the group to GROUP in the prototype file.", 15 | :default => 'root' 16 | 17 | option "--zonetype", "ZONETYPE", 18 | "Set the allowed zone types (global, nonglobal, both)", 19 | :default => 'value=global value=nonglobal' do |value| 20 | case @value 21 | when "both" 22 | value = "value=global value=nonglobal" 23 | else 24 | value = "value=#{value}" 25 | end 26 | end # value 27 | 28 | option "--publisher", "PUBLISHER", 29 | "Set the publisher name for the repository", 30 | :default => 'FPM' 31 | 32 | option "--lint" , :flag, "Check manifest with pkglint", 33 | :default => true 34 | 35 | option "--validate", :flag, "Validate with pkg install", 36 | :default => true 37 | 38 | def architecture 39 | case @architecture 40 | when nil, "native" 41 | @architecture = %x{uname -p}.chomp 42 | when "all" 43 | @architecture = 'i386 value=sparc' 44 | end 45 | 46 | return @architecture 47 | end # def architecture 48 | 49 | def output(output_path) 50 | 51 | # Fixup the category to an acceptable solaris category 52 | case @category 53 | when nil, "default" 54 | @category = 'Applications/System Utilities' 55 | end 56 | 57 | # Manifest Filename 58 | manifest_fn = build_path("#{name}.p5m") 59 | 60 | # Generate a package manifest. 61 | pkg_generate = safesystemout("pkgsend", "generate", "#{staging_path}") 62 | File.write(build_path("#{name}.p5m.1"), pkg_generate) 63 | 64 | # Add necessary metadata to the generated manifest. 65 | metadata_template = template("p5p_metadata.erb").result(binding) 66 | File.write(build_path("#{name}.mog"), metadata_template) 67 | 68 | # Combine template and filelist; allow user to edit before proceeding 69 | File.open(manifest_fn, "w") do |manifest| 70 | manifest.write metadata_template 71 | manifest.write pkg_generate 72 | end 73 | edit_file(manifest_fn) if attributes[:edit?] 74 | 75 | # Execute the transmogrification on the manifest 76 | pkg_mogrify = safesystemout("pkgmogrify", manifest_fn) 77 | File.write(build_path("#{name}.p5m.2"), pkg_mogrify) 78 | safesystem("cp", build_path("#{name}.p5m.2"), manifest_fn) 79 | 80 | # Evaluate dependencies. 81 | if !attributes[:no_auto_depends?] 82 | pkgdepend_gen = safesystemout("pkgdepend", "generate", "-md", "#{staging_path}", manifest_fn) 83 | File.write(build_path("#{name}.p5m.3"), pkgdepend_gen) 84 | 85 | # Allow user to review added dependencies 86 | edit_file(build_path("#{name}.p5m.3")) if attributes[:edit?] 87 | 88 | safesystem("pkgdepend", "resolve", "-m", build_path("#{name}.p5m.3")) 89 | safesystem("cp", build_path("#{name}.p5m.3.res"), manifest_fn) 90 | end 91 | 92 | # Final format of manifest 93 | safesystem("pkgfmt", manifest_fn) 94 | 95 | # Final edit for lint check and packaging 96 | edit_file(manifest_fn) if attributes[:edit?] 97 | 98 | # Add any facets or actuators that are needed. 99 | # TODO(jcraig): add manpage actuator to enable install wo/ man pages 100 | 101 | # Verify the package. 102 | if attributes[:p5p_lint] then 103 | safesystem("pkglint", manifest_fn) 104 | end 105 | 106 | # Publish the package. 107 | repo_path = build_path("#{name}_repo") 108 | safesystem("pkgrepo", "create", repo_path) 109 | safesystem("pkgrepo", "set", "-s", repo_path, "publisher/prefix=#{attributes[:p5p_publisher]}") 110 | safesystem("pkgsend", "-s", repo_path, 111 | "publish", "-d", "#{staging_path}", "#{build_path}/#{name}.p5m") 112 | safesystem("pkgrecv", "-s", repo_path, "-a", 113 | "-d", build_path("#{name}.p5p"), name) 114 | 115 | # Test the package 116 | if attributes[:p5p_validate] then 117 | safesystem("pkg", "install", "-nvg", build_path("#{name}.p5p"), name) 118 | end 119 | 120 | # Cleanup 121 | safesystem("mv", build_path("#{name}.p5p"), output_path) 122 | 123 | end # def output 124 | end # class FPM::Package::IPS 125 | -------------------------------------------------------------------------------- /lib/fpm/package/pear.rb: -------------------------------------------------------------------------------- 1 | require "fpm/namespace" 2 | require "fpm/package" 3 | require "fileutils" 4 | require "fpm/util" 5 | 6 | # This provides PHP PEAR package support. 7 | # 8 | # This provides input support, but not output support. 9 | class FPM::Package::PEAR < FPM::Package 10 | option "--package-name-prefix", "PREFIX", 11 | "Name prefix for pear package", :default => "php-pear" 12 | 13 | option "--channel", "CHANNEL_URL", 14 | "The pear channel url to use instead of the default." 15 | 16 | option "--channel-update", :flag, 17 | "call 'pear channel-update' prior to installation" 18 | 19 | option "--bin-dir", "BIN_DIR", 20 | "Directory to put binaries in" 21 | 22 | option "--php-bin", "PHP_BIN", 23 | "Specify php executable path if differs from the os used for packaging" 24 | 25 | option "--php-dir", "PHP_DIR", 26 | "Specify php dir relative to prefix if differs from pear default (pear/php)" 27 | 28 | option "--data-dir", "DATA_DIR", 29 | "Specify php dir relative to prefix if differs from pear default (pear/data)" 30 | 31 | # Input a PEAR package. 32 | # 33 | # The parameter is a PHP PEAR package name. 34 | # 35 | # Attributes that affect behavior here: 36 | # * :prefix - changes the install root, default is /usr/share 37 | # * :pear_package_name_prefix - changes the 38 | def input(input_package) 39 | if !program_in_path?("pear") 40 | raise ExecutableNotFound.new("pear") 41 | end 42 | 43 | # Create a temporary config file 44 | logger.debug("Creating pear config file") 45 | config = File.expand_path(build_path("pear.config")) 46 | installroot = attributes[:prefix] || "/usr/share" 47 | safesystem("pear", "config-create", staging_path(installroot), config) 48 | 49 | if attributes[:pear_php_dir] 50 | logger.info("Setting php_dir", :php_dir => attributes[:pear_php_dir]) 51 | safesystem("pear", "-c", config, "config-set", "php_dir", "#{staging_path(installroot)}/#{attributes[:pear_php_dir]}") 52 | end 53 | 54 | if attributes[:pear_data_dir] 55 | logger.info("Setting data_dir", :data_dir => attributes[:pear_data_dir]) 56 | safesystem("pear", "-c", config, "config-set", "data_dir", "#{staging_path(installroot)}/#{attributes[:pear_data_dir]}") 57 | end 58 | 59 | bin_dir = attributes[:pear_bin_dir] || "usr/bin" 60 | logger.info("Setting bin_dir", :bin_dir => bin_dir) 61 | safesystem("pear", "-c", config, "config-set", "bin_dir", bin_dir) 62 | 63 | php_bin = attributes[:pear_php_bin] || "/usr/bin/php" 64 | logger.info("Setting php_bin", :php_bin => php_bin) 65 | safesystem("pear", "-c", config, "config-set", "php_bin", php_bin) 66 | 67 | # do channel-discover if required 68 | if !attributes[:pear_channel].nil? 69 | logger.info("Custom channel specified", :channel => attributes[:pear_channel]) 70 | channel_list = safesystemout("pear", "-c", config, "list-channels") 71 | if channel_list !~ /#{Regexp.quote(attributes[:pear_channel])}/ 72 | logger.info("Discovering new channel", :channel => attributes[:pear_channel]) 73 | safesystem("pear", "-c", config, "channel-discover", attributes[:pear_channel]) 74 | end 75 | input_package = "#{attributes[:pear_channel]}/#{input_package}" 76 | logger.info("Prefixing package name with channel", :package => input_package) 77 | end 78 | 79 | # do channel-update if requested 80 | if attributes[:pear_channel_update?] 81 | channel = attributes[:pear_channel] || "pear" 82 | logger.info("Updating the channel", :channel => channel) 83 | safesystem("pear", "-c", config, "channel-update", channel) 84 | end 85 | 86 | logger.info("Installing pear package", :package => input_package, 87 | :directory => staging_path) 88 | ::Dir.chdir(staging_path) do 89 | safesystem("pear", "-c", config, "install", "-n", "-f", input_package) 90 | end 91 | 92 | pear_cmd = "pear -c #{config} remote-info #{input_package}" 93 | logger.info("Fetching package information", :package => input_package, :command => pear_cmd) 94 | name = %x{#{pear_cmd} | sed -ne '/^Package\s*/s/^Package\s*//p'}.chomp 95 | self.name = "#{attributes[:pear_package_name_prefix]}-#{name}" 96 | self.version = %x{#{pear_cmd} | sed -ne '/^Installed\s*/s/^Installed\s*//p'}.chomp 97 | self.description = %x{#{pear_cmd} | sed -ne '/^Summary\s*/s/^Summary\s*//p'}.chomp 98 | logger.debug("Package info", :name => self.name, :version => self.version, 99 | :description => self.description) 100 | 101 | # Remove the stuff we don't want 102 | delete_these = [".depdb", ".depdblock", ".filemap", ".lock", ".channel", "cache", "temp", "download", ".channels", ".registry"] 103 | Find.find(staging_path) do |path| 104 | if File.file?(path) 105 | logger.info("replacing staging_path in file", :replace_in => path, :staging_path => staging_path) 106 | begin 107 | content = File.read(path).gsub(/#{Regexp.escape(staging_path)}/, "") 108 | File.write(path, content) 109 | rescue ArgumentError => e 110 | logger.warn("error replacing staging_path in file", :replace_in => path, :error => e) 111 | end 112 | end 113 | FileUtils.rm_r(path) if delete_these.include?(File.basename(path)) 114 | end 115 | 116 | end # def input 117 | end # class FPM::Package::PEAR 118 | -------------------------------------------------------------------------------- /lib/fpm/package/pkgin.rb: -------------------------------------------------------------------------------- 1 | class FPM::Package::Pkgin < FPM::Package 2 | 3 | def output(output_path) 4 | output_check(output_path) 5 | 6 | File.write(build_path("build-info"), `pkg_info -X pkg_install | egrep '^(MACHINE_ARCH|OPSYS|OS_VERSION|PKGTOOLS_VERSION)'`) 7 | 8 | cwd = ::Dir.pwd 9 | ::Dir.chdir(staging_path) 10 | 11 | files = [] 12 | Find.find(".") do |path| 13 | stat = File.lstat(path) 14 | next unless stat.symlink? or stat.file? 15 | files << path 16 | end 17 | ::Dir.chdir(cwd) 18 | 19 | File.write(build_path("packlist"), files.sort.join("\n")) 20 | 21 | File.write(build_path("comment"), self.description + "\n") 22 | 23 | File.write(build_path("description"), self.description + "\n") 24 | 25 | args = [ "-B", build_path("build-info"), "-c", build_path("comment"), "-d", build_path("description"), "-f", build_path("packlist"), "-I", "/opt/local", "-p", staging_path, "-U", "#{cwd}/#{name}-#{self.version}-#{iteration}.tgz" ] 26 | safesystem("pkg_create", *args) 27 | 28 | end 29 | 30 | def iteration 31 | return @iteration ? @iteration : 1 32 | end 33 | 34 | end 35 | 36 | -------------------------------------------------------------------------------- /lib/fpm/package/pleaserun.rb: -------------------------------------------------------------------------------- 1 | require "fpm/namespace" 2 | require "fpm/package" 3 | require "fpm/util" 4 | require "fileutils" 5 | 6 | require "pleaserun/cli" 7 | 8 | # A pleaserun package. 9 | # 10 | # This does not currently support 'output' 11 | class FPM::Package::PleaseRun < FPM::Package 12 | # TODO(sissel): Implement flags. 13 | 14 | require "pleaserun/platform/systemd" 15 | require "pleaserun/platform/upstart" 16 | require "pleaserun/platform/launchd" 17 | require "pleaserun/platform/sysv" 18 | 19 | option "--name", "SERVICE_NAME", "The name of the service you are creating" 20 | option "--chdir", "CHDIR", "The working directory used by the service" 21 | option "--user", "USER", "The user to use for executing this program." 22 | 23 | private 24 | def input(command) 25 | platforms = [ 26 | ::PleaseRun::Platform::Systemd.new("default"), # RHEL 7, Fedora 19+, Debian 8, Ubuntu 16.04 27 | ::PleaseRun::Platform::Upstart.new("1.5"), # Recent Ubuntus 28 | ::PleaseRun::Platform::Upstart.new("0.6.5"), # CentOS 6 29 | ::PleaseRun::Platform::Launchd.new("10.9"), # OS X 30 | ::PleaseRun::Platform::SYSV.new("lsb-3.1") # Ancient stuff 31 | ] 32 | pleaserun_attributes = [ "chdir", "user", "group", "umask", "chroot", "nice", "limit_coredump", 33 | "limit_cputime", "limit_data", "limit_file_size", "limit_locked_memory", 34 | "limit_open_files", "limit_user_processes", "limit_physical_memory", "limit_stack_size", 35 | "log_directory", "log_file_stderr", "log_file_stdout"] 36 | 37 | attributes[:pleaserun_name] ||= File.basename(command.first) 38 | attributes[:prefix] ||= "/usr/share/pleaserun/#{attributes[:pleaserun_name]}" 39 | 40 | platforms.each do |platform| 41 | logger.info("Generating service manifest.", :platform => platform.class.name) 42 | platform.program = command.first 43 | platform.name = attributes[:pleaserun_name] 44 | platform.args = command[1..-1] 45 | platform.description = if attributes[:description_given?] 46 | attributes[:description] 47 | else 48 | platform.name 49 | end 50 | pleaserun_attributes.each do |attribute_name| 51 | attribute = "pleaserun_#{attribute_name}".to_sym 52 | if attributes.has_key?(attribute) and not attributes[attribute].nil? 53 | platform.send("#{attribute_name}=", attributes[attribute]) 54 | end 55 | end 56 | 57 | base = staging_path(File.join(attributes[:prefix], "#{platform.platform}/#{platform.target_version || "default"}")) 58 | target = File.join(base, "files") 59 | actions_script = File.join(base, "install_actions.sh") 60 | ::PleaseRun::Installer.install_files(platform, target, false) 61 | ::PleaseRun::Installer.write_actions(platform, actions_script) 62 | end 63 | 64 | libs = [ "install.sh", "install-path.sh", "generate-cleanup.sh" ] 65 | libs.each do |file| 66 | base = staging_path(File.join(attributes[:prefix])) 67 | File.write(File.join(base, file), template(File.join("pleaserun", file)).result(binding)) 68 | File.chmod(0755, File.join(base, file)) 69 | end 70 | 71 | scripts[:after_install] = template(File.join("pleaserun", "scripts", "after-install.sh")).result(binding) 72 | scripts[:before_remove] = template(File.join("pleaserun", "scripts", "before-remove.sh")).result(binding) 73 | end # def input 74 | 75 | public(:input) 76 | end # class FPM::Package::PleaseRun 77 | -------------------------------------------------------------------------------- /lib/fpm/package/puppet.rb: -------------------------------------------------------------------------------- 1 | require "erb" 2 | require "fpm/namespace" 3 | require "fpm/package" 4 | require "fpm/errors" 5 | require "etc" 6 | require "fileutils" 7 | 8 | class FPM::Package::Puppet < FPM::Package 9 | def architecture 10 | case @architecture 11 | when nil, "native" 12 | @architecture = %x{uname -m}.chomp 13 | end 14 | return @architecture 15 | end # def architecture 16 | 17 | # Default specfile generator just makes one specfile, whatever that is for 18 | # this package. 19 | def generate_specfile(builddir) 20 | paths = [] 21 | logger.info("PWD: #{File.join(builddir, unpack_data_to)}") 22 | fileroot = File.join(builddir, unpack_data_to) 23 | Dir.chdir(fileroot) do 24 | Find.find(".") do |p| 25 | next if p == "." 26 | paths << p 27 | end 28 | end 29 | logger.info(paths[-1]) 30 | manifests = %w{package.pp package/remove.pp} 31 | 32 | ::Dir.mkdir(File.join(builddir, "manifests")) 33 | manifests.each do |manifest| 34 | dir = File.join(builddir, "manifests", File.dirname(manifest)) 35 | logger.info("manifests targeting: #{dir}") 36 | ::Dir.mkdir(dir) if !File.directory?(dir) 37 | 38 | File.open(File.join(builddir, "manifests", manifest), "w") do |f| 39 | logger.info("manifest: #{f.path}") 40 | template = template(File.join("puppet", "#{manifest}.erb")) 41 | ::Dir.chdir(fileroot) do 42 | f.puts template.result(binding) 43 | end 44 | end 45 | end 46 | end # def generate_specfile 47 | 48 | def unpack_data_to 49 | "files" 50 | end 51 | 52 | def build!(params) 53 | # TODO(sissel): Support these somehow, perhaps with execs and files. 54 | self.scripts.each do |name, path| 55 | case name 56 | when "pre-install" 57 | when "post-install" 58 | when "pre-uninstall" 59 | when "post-uninstall" 60 | end # case name 61 | end # self.scripts.each 62 | 63 | if File.exist?(params[:output]) 64 | # TODO(sissel): Allow folks to choose output? 65 | logger.error("Puppet module directory '#{params[:output]}' already " \ 66 | "exists. Delete it or choose another output (-p flag)") 67 | end 68 | 69 | ::Dir.mkdir(params[:output]) 70 | builddir = ::Dir.pwd 71 | 72 | # Copy 'files' from builddir to :output/files 73 | Find.find("files", "manifests") do |path| 74 | logger.info("Copying path: #{path}") 75 | if File.directory?(path) 76 | ::Dir.mkdir(File.join(params[:output], path)) 77 | else 78 | FileUtils.cp(path, File.join(params[:output], path)) 79 | end 80 | end 81 | end # def build! 82 | 83 | # The directory we create should just be the name of the package as the 84 | # module name 85 | def default_output 86 | name 87 | end # def default_output 88 | 89 | # This method is used by the puppet manifest template 90 | def puppetsort(hash) 91 | # TODO(sissel): Implement sorting that follows the puppet style guide 92 | # Such as, 'ensure' goes first, etc. 93 | return hash.to_a 94 | end # def puppetsort 95 | 96 | # Helper for user lookup 97 | def uid2user(uid) 98 | begin 99 | pwent = Etc.getpwuid(uid) 100 | return pwent.name 101 | rescue ArgumentError => e 102 | # Invalid user id? No user? Return the uid. 103 | logger.warn("Failed to find username for uid #{uid}") 104 | return uid.to_s 105 | end 106 | end # def uid2user 107 | 108 | # Helper for group lookup 109 | def gid2group(gid) 110 | begin 111 | grent = Etc.getgrgid(gid) 112 | return grent.name 113 | rescue ArgumentError => e 114 | # Invalid user id? No user? Return the uid. 115 | logger.warn("Failed to find group for gid #{gid}") 116 | return gid.to_s 117 | end 118 | end # def uid2user 119 | end # class FPM::Target::Puppet 120 | 121 | -------------------------------------------------------------------------------- /lib/fpm/package/pyfpm/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ "get_metadata" ] 2 | -------------------------------------------------------------------------------- /lib/fpm/package/pyfpm/get_metadata.py: -------------------------------------------------------------------------------- 1 | from distutils.core import Command 2 | import os 3 | import sys 4 | import pkg_resources 5 | try: 6 | import json 7 | except ImportError: 8 | import simplejson as json 9 | 10 | PY3 = sys.version_info[0] == 3 11 | 12 | if PY3: 13 | def u(s): 14 | return s 15 | else: 16 | def u(s): 17 | if isinstance(u, unicode): 18 | return u 19 | return s.decode('utf-8') 20 | 21 | 22 | # Note, the last time I coded python daily was at Google, so it's entirely 23 | # possible some of my techniques below are outdated or bad. 24 | # If you have fixes, let me know. 25 | 26 | 27 | class get_metadata(Command): 28 | description = "get package metadata" 29 | user_options = [ 30 | ('load-requirements-txt', 'l', 31 | "load dependencies from requirements.txt"), 32 | ("output=", "o", "output destination for metadata json") 33 | ] 34 | boolean_options = ['load-requirements-txt'] 35 | 36 | def initialize_options(self): 37 | self.load_requirements_txt = False 38 | self.cwd = None 39 | self.output = None 40 | 41 | def finalize_options(self): 42 | self.cwd = os.getcwd() 43 | self.requirements_txt = os.path.join(self.cwd, "requirements.txt") 44 | # make sure we have a requirements.txt 45 | if self.load_requirements_txt: 46 | self.load_requirements_txt = os.path.exists(self.requirements_txt) 47 | 48 | def process_dep(self, dep): 49 | deps = [] 50 | if hasattr(dep, 'marker') and dep.marker: 51 | # PEP0508 marker present 52 | if not dep.marker.evaluate(): 53 | return deps 54 | 55 | if dep.specs: 56 | for operator, version in dep.specs: 57 | deps.append("%s %s %s" % (dep.project_name, 58 | operator, version)) 59 | else: 60 | deps.append(dep.project_name) 61 | 62 | return deps 63 | 64 | def run(self): 65 | data = { 66 | "name": self.distribution.get_name(), 67 | "version": self.distribution.get_version(), 68 | "author": u("%s <%s>") % ( 69 | u(self.distribution.get_author()), 70 | u(self.distribution.get_author_email()), 71 | ), 72 | "description": self.distribution.get_description(), 73 | "license": self.distribution.get_license(), 74 | "url": self.distribution.get_url(), 75 | } 76 | 77 | if self.distribution.has_ext_modules(): 78 | data["architecture"] = "native" 79 | else: 80 | data["architecture"] = "all" 81 | 82 | final_deps = [] 83 | 84 | if self.load_requirements_txt: 85 | requirement = open(self.requirements_txt).readlines() 86 | for dep in pkg_resources.parse_requirements(requirement): 87 | final_deps.extend(self.process_dep(dep)) 88 | else: 89 | if getattr(self.distribution, 'install_requires', None): 90 | for dep in pkg_resources.parse_requirements( 91 | self.distribution.install_requires): 92 | final_deps.extend(self.process_dep(dep)) 93 | if getattr(self.distribution, 'extras_require', None): 94 | for dep in pkg_resources.parse_requirements( 95 | v for k, v in self.distribution.extras_require.items() 96 | if k.startswith(':') and pkg_resources.evaluate_marker(k[1:])): 97 | final_deps.extend(self.process_dep(dep)) 98 | 99 | data["dependencies"] = final_deps 100 | 101 | output = open(self.output, "w") 102 | if hasattr(json, 'dumps'): 103 | def default_to_str(obj): 104 | """ Fall back to using __str__ if possible """ 105 | # This checks if the class of obj defines __str__ itself, 106 | # so we don't fall back to an inherited __str__ method. 107 | if "__str__" in type(obj).__dict__: 108 | return str(obj) 109 | return json.JSONEncoder.default(self, obj) 110 | 111 | output.write(json.dumps(data, indent=2, default=default_to_str)) 112 | else: 113 | # For Python 2.5 and Debian's python-json 114 | output.write(json.write(data)) 115 | output.close() 116 | -------------------------------------------------------------------------------- /lib/fpm/package/sh.rb: -------------------------------------------------------------------------------- 1 | require "erb" 2 | require "fpm/namespace" 3 | require "fpm/package" 4 | require "fpm/errors" 5 | require "fpm/util" 6 | require "backports/latest" 7 | require "fileutils" 8 | require "digest" 9 | 10 | # Support for self extracting sh files (.sh files) 11 | # 12 | # This class only supports output of packages. 13 | # 14 | # The sh package is a single sh file with a tar payload concatenated to the end. 15 | # The script can unpack the tarball to install it and call optional post install scripts. 16 | class FPM::Package::Sh < FPM::Package 17 | 18 | def output(output_path) 19 | create_scripts 20 | 21 | # Make one file. The installscript can unpack itself. 22 | `cat #{install_script} #{payload} > #{output_path}` 23 | FileUtils.chmod("+x", output_path) 24 | end 25 | 26 | def create_scripts 27 | if script?(:after_install) 28 | File.write(File.join(fpm_meta_path, "after_install"), script(:after_install)) 29 | end 30 | end 31 | 32 | def install_script 33 | path = build_path("installer.sh") 34 | File.open(path, "w") do |file| 35 | file.write template("sh.erb").result(binding) 36 | end 37 | path 38 | end 39 | 40 | # Returns the path to the tar file containing the packed up staging directory 41 | def payload 42 | payload_tar = build_path("payload.tar") 43 | logger.info("Creating payload tar ", :path => payload_tar) 44 | 45 | args = [ tar_cmd, 46 | "-C", 47 | staging_path, 48 | "-cf", 49 | payload_tar, 50 | "--owner=0", 51 | "--group=0", 52 | "--numeric-owner", 53 | "." ] 54 | 55 | unless safesystem(*args) 56 | raise "Command failed while creating payload tar: #{args}" 57 | end 58 | payload_tar 59 | end 60 | 61 | # Where we keep metadata and post install scripts and such 62 | def fpm_meta_path 63 | @fpm_meta_path ||= begin 64 | path = File.join(staging_path, ".fpm") 65 | FileUtils.mkdir_p(path) 66 | path 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/fpm/package/snap.rb: -------------------------------------------------------------------------------- 1 | require "yaml" 2 | 3 | require "fpm/package" 4 | require "fpm/util" 5 | require "fileutils" 6 | require "fpm/package/dir" 7 | 8 | # Support for snaps (.snap files). 9 | # 10 | # This supports the input and output of snaps. 11 | class FPM::Package::Snap < FPM::Package 12 | 13 | option "--yaml", "FILEPATH", 14 | "Custom version of the snap.yaml file." do | snap_yaml | 15 | File.expand_path(snap_yaml) 16 | end 17 | 18 | option "--confinement", "CONFINEMENT", 19 | "Type of confinement to use for this snap.", 20 | default: "devmode" do | confinement | 21 | if ['strict', 'devmode', 'classic'].include? confinement 22 | confinement 23 | else 24 | raise "Unsupported confinement type '#{confinement}'" 25 | end 26 | end 27 | 28 | option "--grade", "GRADE", "Grade of this snap.", 29 | default: "devel" do | grade | 30 | if ['stable', 'devel'].include? grade 31 | grade 32 | else 33 | raise "Unsupported grade type '#{grade}'" 34 | end 35 | end 36 | 37 | # Input a snap 38 | def input(input_snap) 39 | extract_snap_to_staging input_snap 40 | extract_snap_metadata_from_staging 41 | end # def input 42 | 43 | # Output a snap. 44 | def output(output_snap) 45 | output_check(output_snap) 46 | 47 | write_snap_yaml 48 | 49 | # Create the snap from the staging path 50 | safesystem("mksquashfs", staging_path, output_snap, "-noappend", "-comp", 51 | "xz", "-no-xattrs", "-no-fragments", "-all-root") 52 | end # def output 53 | 54 | def to_s(format=nil) 55 | # Default format if nil 56 | # name_version_arch.snap 57 | return super(format.nil? ? "NAME_FULLVERSION_ARCH.EXTENSION" : format) 58 | end # def to_s 59 | 60 | private 61 | 62 | def extract_snap_to_staging(snap_path) 63 | safesystem("unsquashfs", "-f", "-d", staging_path, snap_path) 64 | end 65 | 66 | def extract_snap_metadata_from_staging 67 | metadata = YAML.safe_load(File.read( 68 | staging_path(File.join("meta", "snap.yaml")))) 69 | 70 | self.name = metadata["name"] 71 | self.version = metadata["version"] 72 | self.description = metadata["summary"] + "\n" + metadata["description"] 73 | self.architecture = metadata["architectures"][0] 74 | self.attributes[:snap_confinement] = metadata["confinement"] 75 | self.attributes[:snap_grade] = metadata["grade"] 76 | 77 | if metadata["apps"].nil? 78 | attributes[:snap_apps] = [] 79 | else 80 | attributes[:snap_apps] = metadata["apps"] 81 | end 82 | 83 | if metadata["hooks"].nil? 84 | attributes[:snap_hooks] = [] 85 | else 86 | attributes[:snap_hooks] = metadata["hooks"] 87 | end 88 | end 89 | 90 | def write_snap_yaml 91 | # Write the snap.yaml 92 | if attributes[:snap_yaml] 93 | logger.debug("Using '#{attributes[:snap_yaml]}' as the snap.yaml") 94 | yaml_data = File.read(attributes[:snap_yaml]) 95 | else 96 | summary, *remainder = (self.description or "no summary given").split("\n") 97 | description = "no description given" 98 | if remainder.any? 99 | description = remainder.join("\n") 100 | end 101 | 102 | yaml_data = { 103 | "name" => self.name, 104 | "version" => self.version, 105 | "summary" => summary, 106 | "description" => description, 107 | "architectures" => [self.architecture], 108 | "confinement" => self.attributes[:snap_confinement], 109 | "grade" => self.attributes[:snap_grade], 110 | } 111 | 112 | unless attributes[:snap_apps].nil? or attributes[:snap_apps].empty? 113 | yaml_data["apps"] = attributes[:snap_apps] 114 | end 115 | 116 | unless attributes[:snap_hooks].nil? or attributes[:snap_hooks].empty? 117 | yaml_data["hooks"] = attributes[:snap_hooks] 118 | end 119 | 120 | yaml_data = yaml_data.to_yaml 121 | end 122 | 123 | FileUtils.mkdir_p(staging_path("meta")) 124 | snap_yaml_path = staging_path(File.join("meta", "snap.yaml")) 125 | logger.debug("Writing snap.yaml", :path => snap_yaml_path) 126 | File.write(snap_yaml_path, yaml_data) 127 | File.chmod(0644, snap_yaml_path) 128 | edit_file(snap_yaml_path) if attributes[:edit?] 129 | end # def write_snap_yaml 130 | end # class FPM::Package::Snap 131 | -------------------------------------------------------------------------------- /lib/fpm/package/solaris.rb: -------------------------------------------------------------------------------- 1 | require "erb" 2 | require "fpm/namespace" 3 | require "fpm/package" 4 | require "fpm/errors" 5 | require "fpm/util" 6 | 7 | # TODO(sissel): Add dependency checking support. 8 | # IIRC this has to be done as a 'checkinstall' step. 9 | class FPM::Package::Solaris < FPM::Package 10 | 11 | option "--user", "USER", 12 | "Set the user to USER in the prototype files.", 13 | :default => 'root' 14 | 15 | option "--group", "GROUP", 16 | "Set the group to GROUP in the prototype file.", 17 | :default => 'root' 18 | 19 | def architecture 20 | case @architecture 21 | when nil, "native" 22 | @architecture = %x{uname -p}.chomp 23 | end 24 | # "all" is a valid arch according to 25 | # http://www.bolthole.com/solaris/makeapackage.html 26 | 27 | return @architecture 28 | end # def architecture 29 | 30 | def specfile(builddir) 31 | "#{builddir}/pkginfo" 32 | end 33 | 34 | def output(output_path) 35 | self.scripts.each do |name, path| 36 | case name 37 | when "pre-install" 38 | safesystem("cp", path, "./preinstall") 39 | File.chmod(0755, "./preinstall") 40 | when "post-install" 41 | safesystem("cp", path, "./postinstall") 42 | File.chmod(0755, "./postinstall") 43 | when "pre-uninstall" 44 | raise FPM::InvalidPackageConfiguration.new( 45 | "pre-uninstall is not supported by Solaris packages" 46 | ) 47 | when "post-uninstall" 48 | raise FPM::InvalidPackageConfiguration.new( 49 | "post-uninstall is not supported by Solaris packages" 50 | ) 51 | end # case name 52 | end # self.scripts.each 53 | 54 | template = template("solaris.erb") 55 | File.open("#{build_path}/pkginfo", "w") do |pkginfo| 56 | pkginfo.puts template.result(binding) 57 | end 58 | 59 | # Generate the package 'Prototype' file 60 | File.open("#{build_path}/Prototype", "w") do |prototype| 61 | prototype.puts("i pkginfo") 62 | prototype.puts("i preinstall") if self.scripts["pre-install"] 63 | prototype.puts("i postinstall") if self.scripts["post-install"] 64 | 65 | # TODO(sissel): preinstall/postinstall 66 | # strip @prefix, since BASEDIR will set prefix via the pkginfo file 67 | IO.popen("pkgproto #{staging_path}/#{@prefix}=").each_line do |line| 68 | type, klass, path, mode, user, group = line.split 69 | 70 | prototype.puts([type, klass, path, mode, attributes[:solaris_user], attributes[:solaris_group]].join(" ")) 71 | end # popen "pkgproto ..." 72 | end # File prototype 73 | 74 | ::Dir.chdir staging_path do 75 | # Should create a package directory named by the package name. 76 | safesystem("pkgmk", "-o", "-f", "#{build_path}/Prototype", "-d", build_path) 77 | end 78 | 79 | 80 | # Convert the 'package directory' built above to a real solaris package. 81 | safesystem("pkgtrans", "-s", build_path, output_path, name) 82 | safesystem("cp", "#{build_path}/#{output_path}", output_path) 83 | end # def output 84 | 85 | def default_output 86 | v = version 87 | v = "#{epoch}:#{v}" if epoch 88 | if iteration 89 | "#{name}_#{v}-#{iteration}_#{architecture}.#{type}" 90 | else 91 | "#{name}_#{v}_#{architecture}.#{type}" 92 | end 93 | end # def default_output 94 | end # class FPM::Deb 95 | 96 | -------------------------------------------------------------------------------- /lib/fpm/package/tar.rb: -------------------------------------------------------------------------------- 1 | require "backports/latest" # gem backports/latest 2 | require "fpm/package" 3 | require "fpm/util" 4 | require "fileutils" 5 | require "fpm/package/dir" 6 | 7 | # Use a tarball as a package. 8 | # 9 | # This provides no metadata. Both input and output are supported. 10 | class FPM::Package::Tar < FPM::Package 11 | 12 | # Input a tarball. Compressed tarballs should be OK. 13 | def input(input_path) 14 | # use part of the filename as the package name 15 | self.name = File.basename(input_path).split(".").first 16 | 17 | # Unpack the tarball to the build path before ultimately moving it to 18 | # staging. 19 | args = ["-xf", input_path, "-C", build_path] 20 | 21 | # Add the tar compression flag if necessary 22 | tar_compression_flag(input_path).tap do |flag| 23 | args << flag unless flag.nil? 24 | end 25 | 26 | safesystem("tar", *args) 27 | 28 | # use dir to set stuff up properly, mainly so I don't have to reimplement 29 | # the chdir/prefix stuff special for tar. 30 | dir = convert(FPM::Package::Dir) 31 | if attributes[:chdir] 32 | dir.attributes[:chdir] = File.join(build_path, attributes[:chdir]) 33 | else 34 | dir.attributes[:chdir] = build_path 35 | end 36 | 37 | cleanup_staging 38 | # Tell 'dir' to input "." and chdir/prefix will help it figure out the 39 | # rest. 40 | dir.input(".") 41 | @staging_path = dir.staging_path 42 | dir.cleanup_build 43 | end # def input 44 | 45 | # Output a tarball. 46 | # 47 | # If the output path ends predictably (like in .tar.gz) it will try to obey 48 | # the compression type. 49 | def output(output_path) 50 | output_check(output_path) 51 | 52 | # Write the scripts, too. 53 | write_scripts 54 | 55 | # Unpack the tarball to the staging path 56 | args = ["-cf", output_path, "-C", staging_path] 57 | tar_compression_flag(output_path).tap do |flag| 58 | args << flag unless flag.nil? 59 | end 60 | args << "." 61 | 62 | safesystem("tar", *args) 63 | end # def output 64 | 65 | # Generate the proper tar flags based on the path name. 66 | def tar_compression_flag(path) 67 | case path 68 | when /\.tar\.bz2$/ 69 | return "-j" 70 | when /\.tar\.gz$|\.tgz$/ 71 | return "-z" 72 | when /\.tar\.xz$/ 73 | return "-J" 74 | else 75 | return nil 76 | end 77 | end # def tar_compression_flag 78 | end # class FPM::Package::Tar 79 | -------------------------------------------------------------------------------- /lib/fpm/package/zip.rb: -------------------------------------------------------------------------------- 1 | require "backports/latest" # gem backports/latest 2 | require "fpm/package" 3 | require "fpm/util" 4 | require "fileutils" 5 | require "fpm/package/dir" 6 | 7 | # Use a zip as a package. 8 | # 9 | # This provides no metadata. Both input and output are supported. 10 | class FPM::Package::Zip < FPM::Package 11 | 12 | # Input a zipfile. 13 | def input(input_path) 14 | # use part of the filename as the package name 15 | self.name = File.extname(input_path)[1..-1] 16 | 17 | realpath = Pathname.new(input_path).realpath.to_s 18 | ::Dir.chdir(build_path) do 19 | safesystem("unzip", realpath) 20 | end 21 | 22 | # use dir to set stuff up properly, mainly so I don't have to reimplement 23 | # the chdir/prefix stuff special for zip. 24 | dir = convert(FPM::Package::Dir) 25 | if attributes[:chdir] 26 | dir.attributes[:chdir] = File.join(build_path, attributes[:chdir]) 27 | else 28 | dir.attributes[:chdir] = build_path 29 | end 30 | 31 | cleanup_staging 32 | # Tell 'dir' to input "." and chdir/prefix will help it figure out the 33 | # rest. 34 | dir.input(".") 35 | @staging_path = dir.staging_path 36 | dir.cleanup_build 37 | end # def input 38 | 39 | # Output a zipfile. 40 | def output(output_path) 41 | output_check(output_path) 42 | realpath = Pathname.new(output_path).realdirpath.to_s 43 | ::Dir.chdir(staging_path) do 44 | safesystem("zip", "-9r", realpath, ".") 45 | end 46 | end # def output 47 | end # class FPM::Package::Tar 48 | -------------------------------------------------------------------------------- /lib/fpm/rake_task.rb: -------------------------------------------------------------------------------- 1 | require "fpm/namespace" 2 | require "ostruct" 3 | require "rake" 4 | require "rake/tasklib" 5 | 6 | class FPM::RakeTask < Rake::TaskLib 7 | attr_reader :options 8 | 9 | def initialize(package_name, opts = {}, &block) 10 | @options = OpenStruct.new(:name => package_name.to_s) 11 | @source, @target = opts.values_at(:source, :target).map(&:to_s) 12 | @directory = File.expand_path(opts[:directory].to_s) 13 | 14 | (@source.empty? || @target.empty? || options.name.empty?) && 15 | abort("Must specify package name, source and output") 16 | 17 | desc "Package #{@name}" unless ::Rake.application.last_description 18 | 19 | task(options.name) do |_, task_args| 20 | block.call(*[options, task_args].first(block.arity)) if block_given? 21 | abort("Must specify args") unless options.respond_to?(:args) 22 | @args = options.delete_field(:args) 23 | run_cli 24 | end 25 | end 26 | 27 | private 28 | 29 | def parsed_options 30 | options.to_h.map do |option, value| 31 | opt = option.to_s.tr("_", "-") 32 | 33 | case 34 | when value.is_a?(String), value.is_a?(Symbol) 35 | %W(--#{opt} #{value}) 36 | when value.is_a?(Array) 37 | value.map { |v| %W(--#{opt} #{v}) } 38 | when value.is_a?(TrueClass) 39 | "--#{opt}" 40 | when value.is_a?(FalseClass) 41 | "--no-#{opt}" 42 | else 43 | fail TypeError, "Unexpected type: #{value.class}" 44 | end 45 | end 46 | end 47 | 48 | def run_cli 49 | require "fpm" 50 | require "fpm/command" 51 | 52 | args = %W(-t #{@target} -s #{@source} -C #{@directory}) 53 | args << parsed_options 54 | args << @args 55 | 56 | args.flatten!.compact! 57 | 58 | abort 'FPM failed!' unless FPM::Command.new("fpm").run(args) == 0 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/fpm/util/tar_writer.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems/package' 2 | 3 | require 'stringio' 4 | 5 | module FPM 6 | module Issues 7 | module TarWriter 8 | # See https://github.com/rubygems/rubygems/issues/1608 9 | def self.has_issue_1608? 10 | name, prefix = nil,nil 11 | io = StringIO.new 12 | ::Gem::Package::TarWriter.new(io) do |tw| 13 | name, prefix = tw.split_name('/123456789'*9 + '/1234567890') # abs name 101 chars long 14 | end 15 | return prefix.empty? 16 | end 17 | 18 | def self.has_issues_with_split_name? 19 | return false unless ::Gem::Package::TarWriter.method_defined?(:split_name) 20 | return has_issue_1608? 21 | end 22 | 23 | def self.has_issues_with_add_symlink? 24 | return !::Gem::Package::TarWriter.public_instance_methods.include?(:add_symlink) 25 | end 26 | end # module TarWriter 27 | end # module Issues 28 | end # module FPM 29 | 30 | module FPM; module Util; end; end 31 | 32 | # Like the ::Gem::Package::TarWriter but contains some backports/latest and bug fixes 33 | class FPM::Util::TarWriter < ::Gem::Package::TarWriter 34 | if FPM::Issues::TarWriter.has_issues_with_split_name? 35 | def split_name(name) 36 | if name.bytesize > 256 then 37 | raise ::Gem::Package::TooLongFileName.new("File \"#{name}\" has a too long path (should be 256 or less)") 38 | end 39 | 40 | prefix = '' 41 | if name.bytesize > 100 then 42 | parts = name.split('/', -1) # parts are never empty here 43 | name = parts.pop # initially empty for names with a trailing slash ("foo/.../bar/") 44 | prefix = parts.join('/') # if empty, then it's impossible to split (parts is empty too) 45 | while !parts.empty? && (prefix.bytesize > 155 || name.empty?) 46 | name = parts.pop + '/' + name 47 | prefix = parts.join('/') 48 | end 49 | 50 | if name.bytesize > 100 or prefix.empty? then 51 | raise ::Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long name (should be 100 or less)") 52 | end 53 | 54 | if prefix.bytesize > 155 then 55 | raise ::Gem::Package::TooLongFileName.new("File \"#{prefix}/#{name}\" has a too long base path (should be 155 or less)") 56 | end 57 | end 58 | 59 | return name, prefix 60 | end 61 | end # if FPM::Issues::TarWriter.spit_name_has_issues? 62 | 63 | if FPM::Issues::TarWriter.has_issues_with_add_symlink? 64 | # Backport Symlink Support to TarWriter 65 | # https://github.com/rubygems/rubygems/blob/4a778c9c2489745e37bcc2d0a8f12c601a9c517f/lib/rubygems/package/tar_writer.rb#L239-L253 66 | def add_symlink(name, target, mode) 67 | check_closed 68 | 69 | name, prefix = split_name name 70 | 71 | header = ::Gem::Package::TarHeader.new(:name => name, :mode => mode, 72 | :size => 0, :typeflag => "2", 73 | :linkname => target, 74 | :prefix => prefix, 75 | :mtime => Time.now).to_s 76 | 77 | @io.write header 78 | 79 | self 80 | end # def add_symlink 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /lib/fpm/version.rb: -------------------------------------------------------------------------------- 1 | module FPM 2 | VERSION = "1.16.0" 3 | end 4 | -------------------------------------------------------------------------------- /misc/pkgsrc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ ! -f "mk/bsd.pkg.mk" ] ; then 4 | # TODO(sissel): Maybe download pkgsrc ourselves. 5 | echo "Current directory doesn't appear to be a pkgsrc tree. ($PWD)" 6 | echo "I was expecting to find file: ./mk/bsd.pkg.mk" 7 | exit 1 8 | fi 9 | 10 | if [ ! -f "build/usr/local/bin/bmake" ] ; then 11 | # TODO(sissel): Maybe bootstrap ourselves. 12 | echo "This script requires pkgsrc to be bootstrapped in a specific way." 13 | echo "I expected to find file: build/usr/local/bin/bmake and did not" 14 | echo 15 | echo "Bootstrap with:" 16 | echo "SH=/bin/bash ./bootstrap/bootstrap --unprivileged --prefix $PWD/build/usr/local --pkgdbdir $PWD/pkgdb" 17 | exit 1 18 | fi 19 | 20 | # TODO(sissel): put some flags. 21 | 22 | LOCALBASE="/usr/local" 23 | DESTDIR=$PWD/build 24 | 25 | mkdir -p "$DESTDIR" 26 | 27 | export PATH=$DESTDIR/$LOCALBASE/bin:$DESTDIR/$LOCALBASE/sbin:$PATH 28 | 29 | for i in "$@" ; do 30 | # process dependencies first before the final target. 31 | set -- $(bmake -C "$@" show-depends-pkgpaths) "$@" 32 | done 33 | 34 | TARGETS="$*" 35 | 36 | for target in $TARGETS; do 37 | set -- 38 | 39 | eval "$(bmake -C $target show-vars-eval VARS="PKGNAME PKGVERSION")" 40 | name="$(echo "$PKGNAME" | sed -e "s/-$PKGVERSION\$//")" 41 | orig_version=${PKGVERSION} 42 | version=${PKGVERSION}-pkgsrc 43 | 44 | # Purge old package 45 | rm packages/All/$PKGNAME.tgz 46 | 47 | pkg_delete $name > /dev/null 2>&1 48 | 49 | bmake -C $target clean || exit 1 50 | bmake -C $target USE_DESTDIR=yes LOCALBASE=$LOCALBASE PREFIX=$LOCALBASE \ 51 | DESTDIR=$DESTDIR SKIP_DEPENDS=yes \ 52 | clean package || exit 1 53 | 54 | # Start building fpm args 55 | set -- -n "$name" -v "$version" --prefix $LOCALBASE 56 | 57 | # Skip the pkgsrc package metadata files 58 | set -- "$@" --exclude '+*' 59 | 60 | # Handle deps 61 | for dep in $(bmake -C $target show-depends-pkgpaths) ; do 62 | eval "$(bmake -C $dep show-vars-eval VARS="PKGNAME PKGVERSION")" 63 | PKGNAME="$(echo "$PKGNAME" | sed -e "s/-$PKGVERSION\$//")" 64 | set -- "$@" -d "$PKGNAME (= $PKGVERSION-pkgsrc)" 65 | done 66 | 67 | set -- -s tar -t deb "$@" 68 | set -- "$@" packages/All/$name-$orig_version.tgz 69 | fpm "$@" 70 | done 71 | 72 | 73 | -------------------------------------------------------------------------------- /notify-failure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | "$@" 4 | status=$? 5 | 6 | if [ ! -z "$TMUX" ] ; then 7 | if [ "$status" -ne 0 ] ; then 8 | tmux display-message "Tests Fail" 9 | else 10 | tmux display-message "Tests OK" 11 | fi 12 | fi 13 | 14 | exit $status 15 | 16 | -------------------------------------------------------------------------------- /singularity.def: -------------------------------------------------------------------------------- 1 | # Build command: 2 | # 3 | # singularity build fpm.simg singularity.def 4 | # 5 | # Execute instructions: 6 | # 7 | # singularity exec fpm.simg fpm --version 8 | # 9 | Bootstrap: docker 10 | From: alpine:3.7 11 | 12 | %help 13 | 14 | Singularity container with Effing Package Management - FPM 15 | 16 | %post 17 | 18 | apk add --no-cache \ 19 | ruby \ 20 | ruby-dev \ 21 | gcc \ 22 | libffi-dev \ 23 | make \ 24 | libc-dev \ 25 | rpm 26 | gem install --no-ri --no-rdoc fpm -------------------------------------------------------------------------------- /spec/acceptance/puppet/manifests/install.pp: -------------------------------------------------------------------------------- 1 | node default { 2 | $package_provider = "$operatingsystem-$operatingsystemrelease" ? { 3 | /^(Fedora|RedHat|CentOS|OpenSuSE)/ => "rpm", 4 | /^(Debian|Ubuntu)/ => "dpkg", 5 | default => undef, 6 | } 7 | 8 | $package_source = "$operatingsystem-$operatingsystemrelease" ? { 9 | /^(Fedora|RedHat|CentOS|OpenSuSE)/ => "example-service-1.0-1.noarch.rpm", 10 | /^(Debian|Ubuntu)/ => "example-service_1.0_all.deb", 11 | default => undef, 12 | } 13 | 14 | $service_provider = "$operatingsystem-$operatingsystemrelease" ? { 15 | /^CentOS-6/ => "upstart", 16 | default => undef, 17 | } 18 | 19 | 20 | 21 | package { 22 | "example-service": 23 | provider => $package_provider, 24 | source => $package_source, 25 | ensure => present; 26 | } 27 | 28 | service { 29 | "example": 30 | provider => $service_provider, 31 | require => Package["example-service"], 32 | enable => true, 33 | ensure => running; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /spec/acceptance/puppet/manifests/remove.pp: -------------------------------------------------------------------------------- 1 | node default { 2 | $package_provider = "$operatingsystem-$operatingsystemrelease" ? { 3 | /^(Fedora|RedHat|CentOS)/ => "rpm", 4 | /^(Debian|Ubuntu)/ => "dpkg", 5 | default => undef, 6 | } 7 | 8 | $service_provider = "$operatingsystem-$operatingsystemrelease" ? { 9 | /^CentOS-6/ => "upstart", 10 | default => undef, 11 | } 12 | 13 | package { 14 | "example-service": 15 | require => Service["example"], 16 | provider => $package_provider, 17 | source => "example-service-1.0-1.noarch.rpm", 18 | ensure => absent; 19 | } 20 | 21 | service { 22 | "example": 23 | provider => $service_provider, 24 | enable => false, 25 | ensure => stopped; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spec/conversion/gem_to_deb.rb: -------------------------------------------------------------------------------- 1 | require "spec_setup" 2 | require "fpm/command" # local 3 | require "fpm/package/deb" # local 4 | require "fpm/package/gem" # local 5 | require "stud/temporary" 6 | 7 | describe "-s gem -t deb" do 8 | # dpkg-deb lets us query deb package files. 9 | # Comes with debian and ubuntu systems. 10 | have_dpkg_deb = program_exists?("dpkg-deb") 11 | if !have_dpkg_deb 12 | Cabin::Channel.get("rspec") \ 13 | .warn("Skipping some deb tests because 'dpkg-deb' isn't in your PATH") 14 | end 15 | 16 | let(:fpm) { FPM::Command.new("fpm") } 17 | 18 | let(:target) { Stud::Temporary.pathname + ".deb" } 19 | 20 | after do 21 | File.unlink(target) if File.exist?(target) 22 | end 23 | 24 | before do 25 | insist { fpm.run(["-s", "gem", "-t", "deb", "-p", target, "rails"]) } == 0 26 | end 27 | 28 | it "should have a correctly formatted Provides field" do 29 | deb = FPM::Package::Deb.new 30 | deb.input(target) 31 | 32 | # Converting gem->deb should format the deb Provides field as "rubygem-rails (= version)" 33 | insist { deb.provides.first } =~ /^rubygem-rails \(= \d+\.\d+\.\d+\)$/ 34 | end 35 | 36 | end # describe "-s gem -t deb" 37 | -------------------------------------------------------------------------------- /spec/fixtures/deb/meta_test: -------------------------------------------------------------------------------- 1 | asdf 2 | -------------------------------------------------------------------------------- /spec/fixtures/deb/staging/etc/init.d/test: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # Start/stop the test daemon. 3 | # 4 | ### BEGIN INIT INFO 5 | # Provides: test 6 | # Required-Start: $syslog 7 | # Required-Stop: $syslog 8 | # Default-Start: 2 3 4 5 9 | # Default-Stop: 10 | # Short-Description: Test 11 | # Description: Test 12 | ### END INIT INFO 13 | 14 | . /lib/lsb/init-functions 15 | 16 | do_start() { 17 | : 18 | } 19 | 20 | do_stop() { 21 | : 22 | } 23 | 24 | do_restart() { 25 | : 26 | } 27 | 28 | do_reload() { 29 | : 30 | } 31 | 32 | case $1 in 33 | start) do_start ;; 34 | stop) do_stop ;; 35 | force-reload) do_reload ;; 36 | esac 37 | -------------------------------------------------------------------------------- /spec/fixtures/deb/triggers: -------------------------------------------------------------------------------- 1 | interest from-meta-file 2 | -------------------------------------------------------------------------------- /spec/fixtures/gem/example/bin/example: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | -------------------------------------------------------------------------------- /spec/fixtures/gem/example/example-1.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordansissel/fpm/7ef007ef5fd75a0201b3ac5d2a565e6a2d2d4f8d/spec/fixtures/gem/example/example-1.0.gem -------------------------------------------------------------------------------- /spec/fixtures/gem/example/example.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |spec| 2 | spec.name = "example" 3 | spec.version = "1.0" 4 | spec.summary = "sample summary" 5 | spec.description = "sample description" 6 | 7 | spec.add_dependency("dependency1") # license: Ruby License 8 | spec.add_dependency("dependency2") 9 | 10 | #spec.files = ["hello.txt"] 11 | spec.files = [ "bin/example" ] 12 | spec.executables = "example" 13 | spec.bindir = "bin" 14 | #spec.require_paths << "lib" 15 | 16 | spec.author = "sample author" 17 | spec.email = "sample email" 18 | spec.homepage = "http://sample-url/" 19 | end 20 | 21 | -------------------------------------------------------------------------------- /spec/fixtures/mockpackage.rb: -------------------------------------------------------------------------------- 1 | require "fpm/namespace" 2 | require "fpm/package" 3 | 4 | class FPM::Package::Mock < FPM::Package 5 | def input(*args); end 6 | def output(*args); end 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/python/easy_install_default.py: -------------------------------------------------------------------------------- 1 | # The following python code helps predicting easy_install's default behavior. 2 | # See: http://stackoverflow.com/a/9155056 3 | from setuptools.command.easy_install import easy_install 4 | class _easy_install_default(easy_install): 5 | """ class easy_install had problems with the fist parameter not being 6 | an instance of Distribution, even though it was. This is due to 7 | some import-related mess. 8 | """ 9 | 10 | def __init__(self): 11 | from distutils.dist import Distribution 12 | dist = Distribution() 13 | self.distribution = dist 14 | self.initialize_options() 15 | self._dry_run = None 16 | self.verbose = dist.verbose 17 | self.force = None 18 | self.help = 0 19 | self.finalized = 0 20 | 21 | default_options = _easy_install_default() 22 | import distutils.errors 23 | try: 24 | default_options.finalize_options() 25 | except distutils.errors.DistutilsError: 26 | pass 27 | 28 | __all__=[default_options] 29 | -------------------------------------------------------------------------------- /spec/fixtures/python/requirements.txt: -------------------------------------------------------------------------------- 1 | rtxt-dep1 > 0.1 2 | rtxt-dep2 == 0.1 3 | rtxt-dep3; python_version == "2.0" 4 | rtxt-dep4; python_version > "2.0" 5 | -------------------------------------------------------------------------------- /spec/fixtures/python/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name="Example", 4 | version="1.0", 5 | description="sample description", 6 | author="sample author", 7 | author_email="sample email", 8 | url="sample url", 9 | packages=[], 10 | package_dir={}, 11 | install_requires=[ 12 | "Dependency1", "dependency2", 13 | # XXX: I don't know what these python_version-dependent deps mean 14 | # needs investigation 15 | # Reference: PEP-0508 16 | 'rtxt-dep3; python_version == "2.0"', 17 | 'rtxt-dep4; python_version > "2.0"', 18 | ], 19 | ) 20 | 21 | -------------------------------------------------------------------------------- /spec/fixtures/virtualenv/requirements.txt: -------------------------------------------------------------------------------- 1 | pip==8.1.2 2 | -------------------------------------------------------------------------------- /spec/fpm/package/cpan_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_setup" 2 | require "tmpdir" # for Dir.mktmpdir 3 | require "fpm" # local 4 | require "fpm/package/cpan" # local 5 | 6 | have_cpanm = program_exists?("cpanm") 7 | if !have_cpanm 8 | Cabin::Channel.get("rspec") \ 9 | .warn("Skipping CPAN#input tests because 'cpanm' isn't in your PATH") 10 | end 11 | 12 | is_travis = ENV["TRAVIS_OS_NAME"] && !ENV["TRAVIS_OS_NAME"].empty? 13 | 14 | describe FPM::Package::CPAN do 15 | before do 16 | skip("Missing cpanm program") unless have_cpanm 17 | end 18 | 19 | subject { FPM::Package::CPAN.new } 20 | 21 | after :each do 22 | subject.cleanup 23 | end 24 | 25 | it "should package Digest::MD5" do 26 | pending("Disabled on travis-ci because it always fails, and there is no way to debug it?") if is_travis 27 | 28 | # Disable testing because we don't really need to run the cpan tests. The 29 | # goal is to see the parsed result (name, module description, etc) 30 | # Additionally, it fails on my workstation when cpan_test? is enabled due 31 | # to not finding `Test.pm`, and it seems like a flakey test if we keep this 32 | # enabled. 33 | subject.attributes[:cpan_test?] = false 34 | subject.input("Digest::MD5") 35 | insist { subject.name } == "perl-Digest-MD5" 36 | insist { subject.description } == "Perl interface to the MD-5 algorithm" 37 | insist { subject.vendor } == "Gisle Aas " 38 | # TODO(sissel): Check dependencies 39 | end 40 | 41 | it "should unpack tarball containing ./ leading paths" do 42 | pending("Disabled on travis-ci because it always fails, and there is no way to debug it?") if is_travis 43 | 44 | Dir.mktmpdir do |tmpdir| 45 | # Create tarball containing a file './foo/bar.txt' 46 | system("mkdir -p #{tmpdir}/z/foo") 47 | system("touch #{tmpdir}/z/foo/bar.txt") 48 | system("tar -C #{tmpdir} -cvzf #{tmpdir}/z.tar.gz .") 49 | 50 | # Invoke the unpack method 51 | directory = subject.instance_eval { unpack("#{tmpdir}/z.tar.gz") } 52 | 53 | insist { File.file?("#{directory}/foo/bar.txt") } == true 54 | end 55 | end 56 | 57 | it "should package File::Spec" do 58 | pending("Disabled on travis-ci because it always fails, and there is no way to debug it?") if is_travis 59 | subject.input("File::Spec") 60 | 61 | # the File::Spec module comes from the PathTools CPAN distribution 62 | insist { subject.name } == "perl-PathTools" 63 | end 64 | 65 | it "should package Class::Data::Inheritable" do 66 | pending("Disabled on travis-ci because it always fails, and there is no way to debug it?") if is_travis 67 | 68 | # Class::Data::Inheritable version 0.08 has a blank author field in its 69 | # META.yml file. 70 | subject.instance_variable_set(:@version, "0.08"); 71 | subject.input("Class::Data::Inheritable") 72 | insist { subject.vendor } == "No Vendor Or Author Provided" 73 | end 74 | 75 | context "given a distribution without a META.* file" do 76 | it "should package IPC::Session" do 77 | pending("Disabled on travis-ci because it always fails, and there is no way to debug it?") if is_travis 78 | 79 | # IPC::Session fails 'make test' 80 | subject.attributes[:cpan_test?] = false 81 | subject.input("IPC::Session") 82 | end 83 | end 84 | end # describe FPM::Package::CPAN 85 | -------------------------------------------------------------------------------- /spec/fpm/package/empty_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_setup" 2 | require "fpm" # local 3 | 4 | describe FPM::Package::Empty do 5 | describe "#to_s" do 6 | before do 7 | subject.name = "name" 8 | subject.version = "123" 9 | subject.architecture = "all" 10 | subject.iteration = "100" 11 | subject.epoch = "5" 12 | end 13 | it "should always return the empty string" do 14 | expect(subject.to_s "NAME-VERSION-ITERATION.ARCH.TYPE").to(be == "") 15 | expect(subject.to_s "gobbledegook").to(be == "") 16 | expect(subject.to_s "").to(be == "") 17 | expect(subject.to_s nil).to(be == "") 18 | end 19 | end # describe to_s 20 | 21 | describe "#architecture" do 22 | it "should default to 'all'" do 23 | insist { subject.architecture } == "all" 24 | end 25 | 26 | it "should accept changing the architecture" do 27 | subject.architecture = "native" 28 | insist { subject.architecture } == "native" 29 | end 30 | end 31 | end # describe FPM::Package::Empty 32 | -------------------------------------------------------------------------------- /spec/fpm/package/freebsd_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_setup" 2 | require "fpm" # local 3 | require "fpm/package/freebsd" # local 4 | require "stud/temporary" 5 | 6 | describe FPM::Package::FreeBSD do 7 | context "#output" do 8 | subject { FPM::Package::FreeBSD.new } 9 | let(:package) { Stud::Temporary.pathname } 10 | let(:allfiles) { `tar -Jtf #{package} 2> /dev/null`.split("\n") } 11 | let(:files) { allfiles - [ "+COMPACT_MANIFEST", "+MANIFEST" ] } 12 | 13 | before do 14 | Dir.mkdir(subject.staging_path("/usr")) 15 | Dir.mkdir(subject.staging_path("/usr/bin")) 16 | File.write(subject.staging_path("/usr/bin/example"), "testing") 17 | File.write(subject.staging_path("/usr/bin/hello"), "world") 18 | subject.output(package) 19 | end 20 | 21 | after do 22 | subject.cleanup 23 | File.unlink(package) 24 | end 25 | 26 | context "tarball" do 27 | it "should have a +COMPACT_MANIFEST file" do 28 | insist { allfiles }.include?("+COMPACT_MANIFEST") 29 | end 30 | 31 | it "should have a +MANIFEST file" do 32 | insist { allfiles }.include?("+MANIFEST") 33 | end 34 | 35 | # Ensure files have a leading / - Issue #1811, #1844 36 | it "should have files with a leading slash" do 37 | files.each do |path| 38 | insist { path }.start_with?("/") 39 | end 40 | end 41 | 42 | it "should contain expected files" do 43 | insist { files }.include?("/usr/bin/example") 44 | insist { files }.include?("/usr/bin/hello") 45 | end 46 | end 47 | 48 | context "+MANIFEST" do 49 | let(:manifest) { JSON.parse(`tar -Jxf #{package} -O +MANIFEST`) } 50 | it "should have a files list identical to the tar contents" do 51 | insist { files.sort } == manifest["files"].keys.sort 52 | end 53 | 54 | [ "arch", "name", "version", "comment", "desc", "origin", 55 | "maintainer", "www", "prefix", "files", "scripts" ].each do |field| 56 | it "should have a top-level '#{field}'" do 57 | insist { manifest.keys }.include?(field) 58 | end 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/fpm/package/gem_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_setup" 2 | require "fpm" # local 3 | require "fpm/package/gem" # local 4 | 5 | have_gem = program_exists?("gem") 6 | if !have_gem 7 | Cabin::Channel.get("rspec") \ 8 | .warn("Skipping Gem#input tests because 'gem' isn't in your PATH") 9 | end 10 | 11 | describe FPM::Package::Gem do 12 | before do 13 | skip("Missing program 'gem'") unless program_exists?("gem") 14 | end 15 | 16 | let (:example_gem) do 17 | File.expand_path("../../fixtures/gem/example/example-1.0.gem", File.dirname(__FILE__)) 18 | end 19 | 20 | after :each do 21 | subject.cleanup 22 | end 23 | 24 | context "when :gem_version_bins? is true" do 25 | before :each do 26 | subject.attributes[:gem_version_bins?] = true 27 | subject.attributes[:gem_bin_path] = '/usr/bin' 28 | end 29 | 30 | it "it should append the version to binaries" do 31 | subject.input(example_gem) 32 | insist { ::Dir.entries(File.join(subject.staging_path, "/usr/bin")) }.include?("example-1.0.0") 33 | end 34 | end 35 | 36 | context "when :gem_version_bins? is false" do 37 | before :each do 38 | subject.attributes[:gem_version_bins?] = false 39 | subject.attributes[:gem_bin_path] = '/usr/bin' 40 | end 41 | 42 | it "it should not append the version to binaries" do 43 | subject.input(example_gem) 44 | insist { ::Dir.entries(File.join(subject.staging_path, "/usr/bin")) }.include?("example") 45 | end 46 | 47 | end 48 | 49 | context "when :gem_fix_name? is true" do 50 | before :each do 51 | subject.attributes[:gem_fix_name?] = true 52 | end 53 | 54 | context "and :gem_package_name_prefix is nil/default" do 55 | it "should prefix the package with 'gem-'" do 56 | subject.input(example_gem) 57 | insist { subject.name } == "rubygem-example" 58 | end 59 | end 60 | 61 | context "and :gem_package_name_prefix is set" do 62 | it "should prefix the package name appropriately" do 63 | prefix = "whoa" 64 | subject.attributes[:gem_package_name_prefix] = prefix 65 | subject.input(example_gem) 66 | insist { subject.name } == "#{prefix}-example" 67 | end 68 | end 69 | end 70 | 71 | context "when :gem_fix_name? is false" do 72 | before :each do 73 | subject.attributes[:gem_fix_name?] = false 74 | end 75 | 76 | it "it should not prefix the name at all" do 77 | subject.input(example_gem) 78 | insist { subject.name } == "example" 79 | end 80 | end 81 | 82 | context "when :gem_shebang is nil/default" do 83 | before :each do 84 | subject.attributes[:gem_bin_path] = '/usr/bin' 85 | end 86 | 87 | it 'should not change the shebang' do 88 | subject.input(example_gem) 89 | file_path = File.join(subject.staging_path, '/usr/bin/example') 90 | insist { File.readlines(file_path).grep(/^#!\/usr\/bin\/env /).any? } == true 91 | end 92 | end 93 | 94 | context "when :gem_shebang is set" do 95 | before :each do 96 | subject.attributes[:gem_shebang] = '/opt/special/bin/ruby' 97 | subject.attributes[:gem_bin_path] = '/usr/bin' 98 | end 99 | 100 | it 'should change the shebang' do 101 | subject.input(example_gem) 102 | file_path = File.join(subject.staging_path, '/usr/bin/example') 103 | insist { File.readlines(file_path).grep("#!/opt/special/bin/ruby\n").any? } == true 104 | end 105 | end 106 | 107 | context "when confronted with a multiplicity of changelog formats" do 108 | # FIXME: don't expose RE's, provide a more stable interface 109 | 110 | it 'should recognize these formats' do 111 | r1 = Regexp.new(FPM::Package::Gem::P_RE_VERSION_DATE) 112 | r2 = Regexp.new(FPM::Package::Gem::P_RE_DATE_VERSION) 113 | [ 114 | [ "cabin", "v0.1.7 (2011-11-07)", "0.1.7", "2011-11-07", "1320624000" ], 115 | [ "chandler", "## [0.7.0][] (2016-12-23)", "0.7.0", "2016-12-23", "1482451200" ], 116 | [ "domain_name", "## [v0.5.20170404](https://github.com/knu/ruby-domain_name/tree/v0.5.20170404) (2017-04-04)", "0.5.20170404", "2017-04-04", "1491264000" ], 117 | [ "parseconfig", "Mon Jan 25, 2016 - v1.0.8", "1.0.8", "Mon Jan 25, 2016", "1453680000" ], 118 | [ "rack_csrf", "# v2.6.0 (2016-12-31)", "2.6.0", "2016-12-31", "1483142400" ], 119 | [ "sinatra", "= 1.4.7 / 2016-01-24", "1.4.7", "2016-01-24", "1453593600" ], 120 | ].each do |gem, line, version, date, unixdate| 121 | v = "" 122 | d = "" 123 | [r1, r2].each do |r| 124 | if r.match(line) 125 | d = $~[:date] 126 | v = $~[:version] 127 | break 128 | end 129 | end 130 | if (d == "") 131 | puts("RE failed to match for gem #{gem}, #{line}") 132 | end 133 | e = Date.parse(d) 134 | u = e.strftime("%s") 135 | insist { v } == version 136 | insist { d } == date 137 | insist { u } == unixdate 138 | end 139 | end 140 | end 141 | 142 | end # describe FPM::Package::Gem 143 | -------------------------------------------------------------------------------- /spec/fpm/package/npm_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_setup" 2 | require "fpm" # local 3 | require "fpm/package/npm" # local 4 | 5 | have_npm = program_exists?("npm") 6 | if !have_npm 7 | Cabin::Channel.get("rspec") \ 8 | .warn("Skipping NPM tests because 'npm' isn't in your PATH") 9 | end 10 | 11 | describe FPM::Package::NPM do 12 | after do 13 | subject.cleanup 14 | end 15 | 16 | describe "::default_prefix" do 17 | before do 18 | skip("Missing npm program") unless have_npm 19 | end 20 | 21 | it "should provide a valid default_prefix" do 22 | stat = File.stat(FPM::Package::NPM.default_prefix) 23 | insist { stat }.directory? 24 | end 25 | end 26 | end # describe FPM::Package::NPM 27 | -------------------------------------------------------------------------------- /spec/fpm/package/osxpkg_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_setup" 2 | require "fpm" # local 3 | require "fpm/package/osxpkg" # local 4 | 5 | platform_is_darwin = (%x{uname -s}.chomp == "Darwin") 6 | if !platform_is_darwin 7 | Cabin::Channel.get("rspec").warn("Skipping OS X pkg tests requiring 'pkgbuild', " \ 8 | "which requires a Darwin platform.") 9 | end 10 | 11 | describe FPM::Package::OSXpkg, :if => platform_is_darwin do 12 | describe "#identifier" do 13 | it "should be of the form reverse.domain.pkgname" do 14 | subject.name = "name" 15 | subject.attributes[:osxpkg_identifier_prefix] = "org.great" 16 | insist { subject.identifier } == \ 17 | "#{subject.attributes[:osxpkg_identifier_prefix]}.#{subject.name}" 18 | end 19 | 20 | it "should be the name only if a prefix was not given" do 21 | subject.name = "name" 22 | subject.attributes[:osxpkg_identifier_prefix] = nil 23 | insist { subject.identifier } == subject.name 24 | end 25 | end 26 | 27 | describe "#to_s" do 28 | it "should have a default output usable as a filename" do 29 | subject.name = "name" 30 | subject.version = "123" 31 | 32 | # We like the format 'name-version.pkg' 33 | insist { subject.to_s } == "name-123.pkg" 34 | end 35 | end 36 | 37 | describe "#output" do 38 | before do 39 | skip("Current platform is not darwin/osx") unless platform_is_darwin 40 | end 41 | 42 | before :all do 43 | skip("Current platform is not darwin/osx") unless platform_is_darwin 44 | # output a package, use it as the input, set the subject to that input 45 | # package. This helps ensure that we can write and read packages 46 | # properly. 47 | tmpfile = Tempfile.new("fpm-test-osxpkg") 48 | @target = tmpfile.path 49 | # The target file must not exist. 50 | tmpfile.unlink 51 | 52 | @original = FPM::Package::OSXpkg.new 53 | @original.name = "name" 54 | @original.version = "123" 55 | @original.attributes[:osxpkg_identifier_prefix] = "org.my" 56 | @original.output(@target) 57 | 58 | @input = FPM::Package::OSXpkg.new 59 | @input.input(@target) 60 | end 61 | 62 | after :all do 63 | @original.cleanup if @original 64 | @input.cleanup if @input 65 | end # after 66 | 67 | context "package attributes" do 68 | it "should have the correct name" do 69 | insist { @input.name } == @original.name 70 | end 71 | 72 | it "should have the correct version" do 73 | insist { @input.version } == @original.version 74 | end 75 | end # package attributes 76 | end # #output 77 | end # describe FPM::Package:OSXpkg 78 | -------------------------------------------------------------------------------- /spec/fpm/package/sh_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_setup" 2 | require "fpm" # local 3 | require "fpm/package/sh" # local 4 | require "tmpdir" 5 | 6 | shell_is_bash = (%r{/bash} =~ ENV['SHELL']) 7 | if !shell_is_bash 8 | Cabin::Channel.get("rspec").warn("Skipping SH pkg tests which require a BASH shell.") 9 | end 10 | 11 | describe FPM::Package::Sh do 12 | describe "#output" do 13 | before do 14 | skip("Shell (SHELL env) is not bash") unless shell_is_bash 15 | end 16 | 17 | def make_sh_package 18 | # output a package, use it as the input, set the subject to that input 19 | # package. This helps ensure that we can write and read packages 20 | # properly. 21 | tmpfile = Tempfile.new("fpm-test-sh") 22 | target = tmpfile.path 23 | # The target file must not exist. 24 | tmpfile.unlink 25 | 26 | original = FPM::Package::Sh.new 27 | yield original if block_given? 28 | original.output(target) 29 | 30 | return target, original 31 | end 32 | context "pre_install script" do 33 | before :all do 34 | @temptarget = Dir.mktmpdir() 35 | @target, @original = make_sh_package do |pkg| 36 | pkg.scripts[:before_install] = "#!/bin/sh\n\necho before_install" 37 | end 38 | end 39 | it "should execute a pre_install script" do 40 | output = `#{@target} -i #{@temptarget}`.split($/) 41 | insist { output.any? {|l| l.chomp == "before_install" }} == true 42 | insist { $?.success? } == true 43 | end 44 | end 45 | context "empty pre_install script" do 46 | before :all do 47 | @temptarget = Dir.mktmpdir() 48 | @target, @original = make_sh_package do |pkg| 49 | pkg.scripts[:before_install] = "" 50 | end 51 | end 52 | it "shouldn't choke even if the pre-install script is empty" do 53 | output = %x(#{@target} -i #{@temptarget}) 54 | status = $?.success? 55 | insist { status } == true 56 | end 57 | end 58 | 59 | context "Contain segments" do 60 | before :all do 61 | @target, @original = make_sh_package 62 | end 63 | 64 | after :all do 65 | @original.cleanup 66 | end # after 67 | 68 | context "package contents" do 69 | it "should contain a ARCHIVE segment" do 70 | insist { File.readlines(@target).any? {|l| l.chomp == '__ARCHIVE__' } } == true 71 | end 72 | 73 | it "should contain a METADATA segment" do 74 | insist { File.readlines(@target).any? {|l| l.chomp == '__METADATA__' } } == true 75 | end 76 | end # package attributes 77 | end 78 | end # #output 79 | end # describe FPM::Package::Sh 80 | 81 | -------------------------------------------------------------------------------- /spec/fpm/package/tar_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_setup" 2 | require "fpm" # local 3 | require "English" # for $CHILD_STATUS 4 | 5 | describe FPM::Package::Tar do 6 | before do 7 | subject.name = "name" 8 | subject.version = "123" 9 | subject.architecture = "all" 10 | subject.iteration = "100" 11 | end 12 | 13 | describe "#to_s" do 14 | it "should have a default output filename" do 15 | insist { subject.to_s "NAME-VERSION-ITERATION.ARCH.TYPE"} == "name-123-100.all.tar" 16 | end 17 | end # describe to_s 18 | 19 | context 'when extracted' do 20 | let(:target) { Stud::Temporary.pathname + ".tar" } 21 | let(:output_dir) { Stud::Temporary.directory } 22 | before do 23 | subject.output( target) 24 | system("tar x -C '#{output_dir}' -f #{target}") 25 | raise "couldn't extract test tar" unless $CHILD_STATUS.success? 26 | end 27 | 28 | it "doesn't include a .scripts folder" do 29 | insist { Dir.exist?(File.join(output_dir, '.scripts')) } == false 30 | end 31 | 32 | after do 33 | FileUtils.rm_rf(output_dir) 34 | FileUtils.rm_rf(target) 35 | end 36 | end 37 | end # describe FPM::Package::Tar 38 | -------------------------------------------------------------------------------- /spec/fpm/package/virtualenv_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_setup" 2 | require "fpm" # local 3 | require "fpm/package/virtualenv" # local 4 | require "find" # stdlib 5 | 6 | def virtualenv_usable? 7 | return program_exists?("virtualenv") && program_exists?("virtualenv-tools") 8 | end 9 | 10 | if !virtualenv_usable? 11 | Cabin::Channel.get("rspec").warn("Skipping python virtualenv tests because " \ 12 | "no virtualenv/tools bin on your path") 13 | end 14 | 15 | describe FPM::Package::Virtualenv, :if => virtualenv_usable? do 16 | before do 17 | skip("virtualenv and/or virtualenv-tools programs not found") unless virtualenv_usable? 18 | end 19 | 20 | after :each do 21 | subject.cleanup 22 | end 23 | 24 | context "without a version on the input" do 25 | it "requires that the version be passed separately" do 26 | # this failed before I got here 27 | subject.version = "8.1.2" 28 | subject.input("pip") 29 | end 30 | end 31 | 32 | context "with a version on the input" do 33 | it "requires that the version be passed separately" do 34 | subject.input("pip==8.1.2") 35 | 36 | insist { subject.version } == "8.1.2" 37 | insist { subject.name } == "virtualenv-pip" 38 | 39 | activate_path = File.join(subject.build_path, '/usr/share/python/pip/bin/activate') 40 | 41 | expect(File.exist?(activate_path)).to(be_truthy) 42 | end 43 | 44 | it "can override the version specified on the input" do 45 | subject.version = "1.2.3" 46 | subject.input("pip==8.1.2") 47 | 48 | insist { subject.version } == "1.2.3" 49 | insist { subject.name } == "virtualenv-pip" 50 | end 51 | 52 | context "with a package name supplied" do 53 | 54 | before do 55 | subject.name = "foo" 56 | end 57 | 58 | it "will prepend the default prefix" do 59 | subject.input("pip==8.1.2") 60 | 61 | insist { subject.name } == "virtualenv-foo" 62 | end 63 | 64 | it "will prepend a non default prefix" do 65 | subject.attributes[:virtualenv_package_name_prefix] = 'bar' 66 | subject.input("pip==8.1.2") 67 | 68 | insist { subject.name } == "bar-foo" 69 | end 70 | 71 | it "will not prepend a prefix if --fix-name is false" do 72 | subject.attributes[:virtualenv_fix_name?] = false 73 | subject.input("pip==8.1.2") 74 | 75 | insist { subject.name } == "foo" 76 | end 77 | end 78 | end 79 | 80 | context "with an alternate install path" do 81 | it "installs in to the alternate directory" do 82 | # it seems odd that you can't control the name of the directory under here... 83 | subject.attributes[:virtualenv_install_location] = '/opt/foo' 84 | 85 | subject.input("pip==8.1.2") 86 | 87 | activate_path = File.join(subject.build_path, '/opt/foo/pip/bin/activate') 88 | expect(File.exist?(activate_path)).to(be_truthy) 89 | end 90 | end 91 | 92 | context "with other files dir" do 93 | it "includes the other files in the package" do 94 | subject.attributes[:virtualenv_other_files_dir] = File.expand_path("../../fixtures/python/", File.dirname(__FILE__)) 95 | subject.input("pip==8.1.2") 96 | 97 | activate_path = File.join(subject.build_path, '/usr/share/python/pip/bin/activate') 98 | expect(File.exist?(activate_path)).to(be_truthy) 99 | 100 | egg_path = File.join(subject.build_path, '/setup.py') 101 | expect(File.exist?(egg_path)).to(be_truthy) 102 | end 103 | end 104 | 105 | context "input is a requirements.txt file" do 106 | 107 | before :each do 108 | subject.attributes[:virtualenv_requirements?] = true 109 | end 110 | 111 | let :fixtures_dir do 112 | File.expand_path("../../fixtures/virtualenv/", File.dirname(__FILE__)) 113 | end 114 | 115 | context "default use" do 116 | 117 | it "creates the virtualenv, using the parent dir as the package name" do 118 | subject.input(File.join(fixtures_dir, 'requirements.txt')) 119 | 120 | activate_path = File.join(subject.build_path, '/usr/share/python/virtualenv/bin/activate') 121 | expect(File.exist?(activate_path)).to(be_truthy) 122 | expect(subject.name).to eq("virtualenv-virtualenv") 123 | end 124 | end 125 | 126 | context "with --name" do 127 | it "uses the supplied argument over the parent dir " do 128 | subject.name = 'foo' 129 | subject.input(File.join(fixtures_dir, 'requirements.txt')) 130 | activate_path = File.join(subject.build_path, '/usr/share/python/virtualenv/bin/activate') 131 | 132 | expect(File.exist?(activate_path)).to(be_truthy) 133 | 134 | expect(subject.name).to eq("virtualenv-foo") 135 | end 136 | end 137 | end 138 | 139 | context "new --prefix behaviour" do 140 | it "--prefix puts virtualenv under the prefix" do 141 | subject.attributes[:prefix] = '/opt/foo' 142 | subject.input('absolute') 143 | 144 | activate_path = File.join(subject.staging_path, '/opt/foo/bin/activate') 145 | 146 | expect(File.exist?(activate_path)).to(be_truthy) 147 | end 148 | 149 | it "takes precedence over other folder options" do 150 | subject.attributes[:prefix] = '/opt/foo' 151 | subject.attributes[:install_location] = '/usr/local/foo' 152 | subject.input('absolute') 153 | 154 | activate_path = File.join(subject.staging_path, '/opt/foo/bin/activate') 155 | 156 | expect(File.exist?(activate_path)).to(be_truthy) 157 | end 158 | end 159 | end 160 | -------------------------------------------------------------------------------- /spec/fpm/package_convert_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_setup" 2 | require "fpm" # local 3 | 4 | describe "FPM::Package#convert" do 5 | 6 | let(:gem_package_name_prefix) { 'rubygem19' } 7 | let(:default_rpm_compression) { 'gzip' } 8 | 9 | subject do 10 | source = FPM::Package::Gem.new 11 | prefix = source.attributes[:gem_package_name_prefix ] = 'rubygem19' 12 | name = source.name = "whatever" 13 | version = source.version = "1.0" 14 | source.provides << "#{prefix}-#{name} = #{version}" 15 | source.convert(FPM::Package::RPM) 16 | end 17 | 18 | it "applies the default attributes for target format" do 19 | insist { subject.attributes[:rpm_compression] } == default_rpm_compression 20 | end 21 | 22 | it "remembers attributes applied to source" do 23 | insist { subject.attributes[:gem_package_name_prefix] } == gem_package_name_prefix 24 | end 25 | 26 | it "should list provides matching the gem_package_name_prefix (#585)" do 27 | insist { subject.provides }.include?("rubygem19(whatever) = 1.0") 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/fpm/rake_task_spec.rb: -------------------------------------------------------------------------------- 1 | require "rspec" # Avoid setup_spec due to overrides 2 | require "fpm/command" 3 | require "fpm/rake_task" 4 | require "tmpdir" 5 | 6 | describe FPM::RakeTask do 7 | around do |example| 8 | old_stderr = $stderr.dup 9 | $stderr.reopen("/dev/null") 10 | 11 | example.run 12 | 13 | $stderr = old_stderr 14 | Rake::Task.clear 15 | end 16 | 17 | describe "#new" do 18 | it "requires a package name" do 19 | expect { described_class.new(nil, :source => :dir, :target => :tar) }. 20 | to raise_error(SystemExit, "Must specify package name, source and output") 21 | end 22 | 23 | it "requires a source" do 24 | expect { described_class.new(:awesome, :source => nil, :target => :tar) }. 25 | to raise_error(SystemExit, "Must specify package name, source and output") 26 | end 27 | 28 | it "requires a target" do 29 | expect { described_class.new(:awesome, :source => :dir, :target => nil) }. 30 | to raise_error(SystemExit, "Must specify package name, source and output") 31 | end 32 | 33 | it "requires package args" do 34 | described_class.new(:awesome, :source => :dir, :target => :tar) 35 | expect { Rake::Task["awesome"].execute }. 36 | to raise_error(SystemExit, "Must specify args") 37 | end 38 | 39 | it "executes FPM::Command with the appropriate arguments" do 40 | command = instance_double(FPM::Command) 41 | expected = %W(-t tar -s dir -C #{Dir.tmpdir} --cpan-mirror-only 42 | --no-cpan-test --config-files foo --config-files bar 43 | --name awesome --cpan-mirror-only --url http://example.com 44 | bin/) 45 | 46 | allow(FPM::Command).to receive(:new).and_return(command) 47 | expect(command).to receive(:run).with(array_including(*expected)) 48 | 49 | args = [:awesome, 50 | { :source => :dir, :target => :tar, :directory => Dir.tmpdir }] 51 | 52 | described_class.new(*args) do |pkg| 53 | pkg.args = %w(bin/) 54 | pkg.cpan_mirror_only = true 55 | pkg.cpan_test = false 56 | pkg.url = "http://example.com" 57 | pkg.config_files = %w(foo bar) 58 | end 59 | 60 | expect { Rake::Task["awesome"].execute }.to raise_error(SystemExit, nil) 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/spec_setup.rb: -------------------------------------------------------------------------------- 1 | require "rubygems" # for ruby 1.8 2 | require "insist" # gem "insist" 3 | require "cabin" # gem "cabin" 4 | require "tmpdir" # stdlib 5 | require "tempfile" # stdlib 6 | require "fileutils" # stdlib 7 | require "date" # stdlib 8 | 9 | # put "lib" in RUBYLIB 10 | $: << File.join(File.dirname(File.dirname(__FILE__)), "lib") 11 | 12 | # for method "program_exists?" etc 13 | require "fpm/util" 14 | include FPM::Util 15 | 16 | Cabin::Channel.get.level = :error 17 | spec_logger = Cabin::Channel.get("rspec") 18 | spec_logger.subscribe(STDOUT) 19 | spec_logger.level = :error 20 | 21 | # Enable debug logs if requested. 22 | if $DEBUG or ENV["DEBUG"] 23 | Cabin::Channel.get.level = :debug 24 | Cabin::Channel.get.subscribe(STDOUT) 25 | else 26 | class << Cabin::Channel.get 27 | alias_method :subscribe_, :subscribe 28 | def subscribe(io) 29 | return if io == STDOUT 30 | subscribe_(io) 31 | #puts caller.join("\n") 32 | end 33 | end 34 | end 35 | 36 | 37 | # Quiet the output of all system() calls 38 | module Kernel 39 | alias_method :orig_system, :system 40 | def system(*args) 41 | old_stdout = $stdout.clone 42 | old_stderr = $stderr.clone 43 | null = File.new("/dev/null", "w") 44 | $stdout.reopen(null) 45 | $stderr.reopen(null) 46 | value = orig_system(*args) 47 | $stdout.reopen(old_stdout) 48 | $stderr.reopen(old_stderr) 49 | null.close 50 | return value 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /templates/deb.erb: -------------------------------------------------------------------------------- 1 | Package: <%= name %> 2 | Version: <%= "#{epoch}:" if epoch %><%= version %><%= "-" + iteration.to_s if iteration %> 3 | License: <%= license %> 4 | <% if !vendor.nil? and !vendor.empty? -%> 5 | Vendor: <%= vendor %> 6 | <% end -%> 7 | Architecture: <%= architecture %> 8 | Maintainer: <%= maintainer %> 9 | Installed-Size: <%= attributes[:deb_installed_size] %> 10 | <% if !dependencies.empty? and !attributes[:no_depends?] -%> 11 | Depends: <%= dependencies.collect { |d| fix_dependency(d) }.flatten.join(", ") %> 12 | <% end -%> 13 | <% if !conflicts.empty? -%> 14 | Conflicts: <%= conflicts.collect { |d| fix_dependency(d) }.flatten.join(", ") %> 15 | <% end -%> 16 | <% if attributes[:deb_breaks] and !attributes[:deb_breaks].empty? -%> 17 | Breaks: <%= attributes[:deb_breaks].collect { |d| fix_dependency(d) }.flatten.join(", ") %> 18 | <% end -%> 19 | <% if attributes[:deb_pre_depends] and !attributes[:deb_pre_depends].empty? -%> 20 | Pre-Depends: <%= attributes[:deb_pre_depends].collect { |d| fix_dependency(d) }.flatten.join(", ") %> 21 | <% end -%> 22 | <% if attributes[:deb_build_depends] and !attributes[:deb_build_depends].empty? -%> 23 | Build-Depends: <%= attributes[:deb_build_depends].collect { |d| fix_dependency(d) }.flatten.join(", ") %> 24 | <% end -%> 25 | <% if !provides.empty? -%> 26 | <%# Turn each provides from 'foo = 123' to simply 'foo' because Debian :\ -%> 27 | <%# http://www.debian.org/doc/debian-policy/ch-relationships.html -%> 28 | Provides: <%= provides.join ", " %> 29 | <% end -%> 30 | <% if !replaces.empty? -%> 31 | Replaces: <%= replaces.collect { |d| fix_dependency(d) }.flatten.join(", ") %> 32 | <% end -%> 33 | <% if attributes[:deb_recommends] and !attributes[:deb_recommends].empty? -%> 34 | Recommends: <%= attributes[:deb_recommends].collect { |d| fix_dependency(d) }.flatten.join(", ") %> 35 | <% end -%> 36 | <% if attributes[:deb_suggests] and !attributes[:deb_suggests].empty? -%> 37 | Suggests: <%= attributes[:deb_suggests].collect { |d| fix_dependency(d) }.flatten.join(", ") %> 38 | <% end -%> 39 | Section: <%= category %> 40 | Priority: <%= attributes[:deb_priority] %> 41 | Homepage: <%= url or "http://nourlgiven.example.com/" %> 42 | <% lines = (description or "no description given").split("\n") -%> 43 | <% firstline, *remainder = lines -%> 44 | Description: <%= firstline %> 45 | <% if remainder.any? -%> 46 | <%= remainder.collect { |l| l =~ /^ *$/ ? " ." : " #{l}" }.join("\n") %> 47 | <% end -%> 48 | <% if attributes[:deb_field_given?] -%> 49 | <% attributes[:deb_field].each do |field, value| -%> 50 | <%= field %>: <%= value %> 51 | <% end -%> 52 | <% end -%> 53 | -------------------------------------------------------------------------------- /templates/deb/changelog.erb: -------------------------------------------------------------------------------- 1 | <%= name %> (<%= "#{epoch}:" if epoch %><%= version %><%= "-" + iteration.to_s if iteration %>) <%= distribution %>; urgency=medium 2 | 3 | * Package created with FPM. 4 | 5 | -- <%= maintainer %> <%= (if attributes[:source_date_epoch].nil? then Time.now() else Time.at(attributes[:source_date_epoch].to_i) end).strftime("%a, %d %b %Y %T %z") %> 6 | -------------------------------------------------------------------------------- /templates/deb/deb.changes.erb: -------------------------------------------------------------------------------- 1 | Format: 1.8 2 | Date: <%= (if attributes[:source_date_epoch].nil? then Time.now() else Time.at(attributes[:source_date_epoch].to_i) end).strftime("%a, %d %b %Y %T %z") %> 3 | Source: <%= name %> 4 | Binary: <%= name %> 5 | Architecture: <%= architecture %> 6 | Version: <%= "#{epoch}:" if epoch %><%= version %><%= "-" + iteration.to_s if iteration %> 7 | Distribution: <%= distribution %> 8 | Urgency: medium 9 | Maintainer: <%= maintainer %> 10 | <% lines = (description or "no description given").split("\n") -%> 11 | <% firstline, *remainder = lines -%> 12 | Description: <%= firstline %> 13 | <% if remainder.any? -%> 14 | <%= remainder.collect { |l| l =~ /^ *$/ ? " ." : " #{l}" }.join("\n") %> 15 | <% end -%> 16 | Changes: 17 | <%= name %> (<%= "#{epoch}:" if epoch %><%= version %><%= "-" + iteration.to_s if iteration %>) <%= distribution %>; urgency=medium 18 | * Package created with FPM. 19 | Checksums-Sha1: 20 | <% changes_files.each do |file| -%> 21 | <%= file[:sha1sum] %> <%= file[:size] %> <%= file[:name] %> 22 | <% end -%> 23 | Checksums-Sha256: 24 | <% changes_files.each do |file| -%> 25 | <%= file[:sha256sum] %> <%= file[:size] %> <%= file[:name] %> 26 | <% end -%> 27 | Files: 28 | <% changes_files.each do |file| -%> 29 | <%= file[:md5sum] %> <%= file[:size] %> default <%= attributes[:deb_priority] %> <%= file[:name] %> 30 | <% end -%> 31 | -------------------------------------------------------------------------------- /templates/deb/ldconfig.sh.erb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This script is automatically added by fpm when you specify 3 | # conditions that usually require running ldconfig upon 4 | # package installation and removal. 5 | # 6 | # For example, if you set '--deb-shlibs' in creating your package, 7 | # fpm will use this script if you don't provide your own --after-install or 8 | # --after-remove 9 | set -e 10 | 11 | case $1 in 12 | configure|remove) ldconfig ;; 13 | esac 14 | -------------------------------------------------------------------------------- /templates/deb/postinst_upgrade.sh.erb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | <% if attributes[:deb_maintainerscripts_force_errorchecks?] -%> 4 | set -e 5 | <% end -%> 6 | 7 | after_upgrade() { 8 | <%# Making sure that at least one command is in the function -%> 9 | <%# avoids a lot of potential errors, including the case that -%> 10 | <%# the script is non-empty, but just whitespace and/or comments -%> 11 | : 12 | <% if script?(:after_upgrade) -%> 13 | <%= script(:after_upgrade) %> 14 | <% end -%> 15 | 16 | <%# if any systemd services specified, loop through and start them -%> 17 | <% if attributes[:deb_systemd].any? -%> 18 | systemctl --system daemon-reload >/dev/null || true 19 | debsystemctl=$(command -v deb-systemd-invoke || echo systemctl) 20 | <% attributes[:deb_systemd].each do |service| -%> 21 | if ! systemctl is-enabled <%= service %> >/dev/null 22 | then 23 | : # Ensure this if-clause is not empty. If it were empty, and we had an 'else', then it is an error in shell syntax 24 | <% if attributes[:deb_systemd_enable?]-%> 25 | systemctl enable <%= service %> >/dev/null || true 26 | <% end -%> 27 | <% if attributes[:deb_systemd_auto_start?]-%> 28 | $debsystemctl start <%= service %> >/dev/null || true 29 | <% end -%> 30 | <% if attributes[:deb_systemd_restart_after_upgrade?] -%> 31 | else 32 | $debsystemctl restart <%= service %> >/dev/null || true 33 | <% end -%> 34 | fi 35 | <% end -%> 36 | <% end -%> 37 | } 38 | 39 | after_install() { 40 | <%# Making sure that at least one command is in the function -%> 41 | <%# avoids a lot of potential errors, including the case that -%> 42 | <%# the script is non-empty, but just whitespace and/or comments -%> 43 | : 44 | <% if script?(:after_install) -%> 45 | <%= script(:after_install) %> 46 | <% end -%> 47 | 48 | <%# if any systemd services specified, loop through and start them -%> 49 | <% if attributes[:deb_systemd].any? -%> 50 | systemctl --system daemon-reload >/dev/null || true 51 | debsystemctl=$(command -v deb-systemd-invoke || echo systemctl) 52 | <% attributes[:deb_systemd].each do |service| -%> 53 | <% if attributes[:deb_systemd_enable?]-%> 54 | systemctl enable <%= service %> >/dev/null || true 55 | <% end -%> 56 | <% if attributes[:deb_systemd_auto_start?]-%> 57 | $debsystemctl start <%= service %> >/dev/null || true 58 | <% end -%> 59 | <% end -%> 60 | <% end -%> 61 | } 62 | 63 | if [ "${1}" = "configure" -a -z "${2}" ] || \ 64 | [ "${1}" = "abort-remove" ] 65 | then 66 | # "after install" here 67 | # "abort-remove" happens when the pre-removal script failed. 68 | # In that case, this script, which should be idemptoent, is run 69 | # to ensure a clean roll-back of the removal. 70 | after_install 71 | elif [ "${1}" = "configure" -a -n "${2}" ] 72 | then 73 | upgradeFromVersion="${2}" 74 | # "after upgrade" here 75 | # NOTE: This slot is also used when deb packages are removed, 76 | # but their config files aren't, but a newer version of the 77 | # package is installed later, called "Config-Files" state. 78 | # basically, that still looks a _lot_ like an upgrade to me. 79 | after_upgrade "${2}" 80 | elif echo "${1}" | grep -E -q "(abort|fail)" 81 | then 82 | echo "Failed to install before the post-installation script was run." >&2 83 | exit 1 84 | fi 85 | -------------------------------------------------------------------------------- /templates/deb/postrm_upgrade.sh.erb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | <% if attributes[:deb_maintainerscripts_force_errorchecks?] -%> 4 | set -e 5 | <% end -%> 6 | 7 | after_remove() { 8 | <%# Making sure that at least one command is in the function -%> 9 | <%# avoids a lot of potential errors, including the case that -%> 10 | <%# the script is non-empty, but just whitespace and/or comments -%> 11 | : 12 | <% if script?(:after_remove) -%> 13 | <%= script(:after_remove) %> 14 | <% end -%> 15 | } 16 | 17 | after_purge() { 18 | <%# Making sure that at least one command is in the function -%> 19 | <%# avoids a lot of potential errors, including the case that -%> 20 | <%# the script is non-empty, but just whitespace and/or comments -%> 21 | : 22 | <% if script?(:after_purge) -%> 23 | <%= script(:after_purge) %> 24 | <% end -%> 25 | } 26 | 27 | dummy() { 28 | : 29 | } 30 | 31 | 32 | if [ "${1}" = "remove" -o "${1}" = "abort-install" ] 33 | then 34 | # "after remove" goes here 35 | # "abort-install" happens when the pre-installation script failed. 36 | # In that case, this script, which should be idemptoent, is run 37 | # to ensure a clean roll-back of the installation. 38 | after_remove 39 | elif [ "${1}" = "purge" -a -z "${2}" ] 40 | then 41 | # like "on remove", but executes after dpkg deletes config files 42 | # 'apt-get purge' runs 'on remove' section, then this section. 43 | # There is no equivalent in RPM or ARCH. 44 | after_purge 45 | elif [ "${1}" = "upgrade" ] 46 | then 47 | # This represents the case where the old package's postrm is called after 48 | # the 'preinst' script is called. 49 | # We should ignore this and just use 'preinst upgrade' and 50 | # 'postinst configure'. The newly installed package should do the 51 | # upgrade, not the uninstalled one, since it can't anticipate what new 52 | # things it will have to do to upgrade for the new version. 53 | dummy 54 | elif echo "${1}" | grep -E -q '(fail|abort)' 55 | then 56 | echo "Failed to install before the post-removal script was run." >&2 57 | exit 1 58 | fi 59 | -------------------------------------------------------------------------------- /templates/deb/preinst_upgrade.sh.erb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | <% if attributes[:deb_maintainerscripts_force_errorchecks?] -%> 4 | set -e 5 | <% end -%> 6 | 7 | before_upgrade() { 8 | <%# Making sure that at least one command is in the function -%> 9 | <%# avoids a lot of potential errors, including the case that -%> 10 | <%# the script is non-empty, but just whitespace and/or comments -%> 11 | : 12 | <% if script?(:before_upgrade) -%> 13 | <%= script(:before_upgrade) %> 14 | <% end -%> 15 | } 16 | 17 | before_install() { 18 | <%# Making sure that at least one command is in the function -%> 19 | <%# avoids a lot of potential errors, including the case that -%> 20 | <%# the script is non-empty, but just whitespace and/or comments -%> 21 | : 22 | <% if script?(:before_install) -%> 23 | <%= script(:before_install) %> 24 | <% end -%> 25 | } 26 | 27 | if [ "${1}" = "install" -a -z "${2}" ] 28 | then 29 | before_install 30 | elif [ "${1}" = "upgrade" -a -n "${2}" ] 31 | then 32 | upgradeFromVersion="${2}" 33 | before_upgrade "${upgradeFromVersion}" 34 | elif [ "${1}" = "install" -a -n "${2}" ] 35 | then 36 | upgradeFromVersion="${2}" 37 | # Executed when a package is removed but its config files aren't, 38 | # and a new version is installed. 39 | # Looks a _lot_ like an upgrade case, I say we just execute the 40 | # same "before upgrade" script as in the previous case 41 | before_upgrade "${upgradeFromVersion}" 42 | elif echo "${1}" | grep -E -q '(fail|abort)' 43 | then 44 | echo "Failed to install before the pre-installation script was run." >&2 45 | exit 1 46 | fi 47 | -------------------------------------------------------------------------------- /templates/deb/prerm_upgrade.sh.erb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | <% if attributes[:deb_maintainerscripts_force_errorchecks?] -%> 4 | set -e 5 | <% end -%> 6 | 7 | before_remove() { 8 | <%# Making sure that at least one command is in the function -%> 9 | <%# avoids a lot of potential errors, including the case that -%> 10 | <%# the script is non-empty, but just whitespace and/or comments -%> 11 | : 12 | <% if script?(:before_remove) -%> 13 | <%= script(:before_remove) %> 14 | <% end -%> 15 | 16 | <%# Stop and remove any systemd services that were installed-%> 17 | <% if attributes[:deb_systemd].any? -%> 18 | debsystemctl=$(command -v deb-systemd-invoke || echo systemctl) 19 | <% attributes[:deb_systemd].each do |service| -%> 20 | $debsystemctl stop <%= service %> >/dev/null || true 21 | systemctl disable <%= service %> >/dev/null || true 22 | <% end -%> 23 | systemctl --system daemon-reload >/dev/null || true 24 | <% end -%> 25 | } 26 | 27 | dummy() { 28 | : 29 | } 30 | 31 | if [ "${1}" = "remove" -a -z "${2}" ] 32 | then 33 | # "before remove" goes here 34 | before_remove 35 | elif [ "${1}" = "upgrade" ] 36 | then 37 | # Executed before the old version is removed 38 | # upon upgrade. 39 | # We should generally not do anything here. The newly installed package 40 | # should do the upgrade, not the uninstalled one, since it can't anticipate 41 | # what new things it will have to do to upgrade for the new version. 42 | dummy 43 | elif echo "${1}" | grep -E -q "(fail|abort)" 44 | then 45 | echo "Failed to install before the pre-removal script was run." >&2 46 | exit 1 47 | fi 48 | -------------------------------------------------------------------------------- /templates/osxpkg.erb: -------------------------------------------------------------------------------- 1 | postinstall-action="<%= attributes[:osxpkg_postinstall_action] %>"<% end -%> 3 | > 4 | <% if !attributes[:osxpkg_dont_obsolete].nil? -%> 5 | 6 | <% attributes[:osxpkg_dont_obsolete].each do |filepath| -%> 7 | 8 | <% end -%> 9 | 10 | <% end -%> 11 | 12 | -------------------------------------------------------------------------------- /templates/p5p_metadata.erb: -------------------------------------------------------------------------------- 1 | set name=pkg.fmri value="<%=@name%>@<%=@version%>" 2 | set name=pkg.summary value="<%=@description%>" 3 | set name=pkg.human-version value="<%=@version%>" 4 | set name=pkg.description value="<%=@description%>" 5 | set name=info.classification value="org.opensolaris.category.2008:<%=@category%>" 6 | set name=variant.opensolaris.zone <%= attributes[:p5p_zonetype] %> 7 | set name=variant.arch value=<%=@architecture%> 8 | set owner <%= attributes[:p5p_user] %>> 9 | set group <%= attributes[:p5p_group] %>> 10 | drop> 11 | drop> 12 | drop> 13 | -------------------------------------------------------------------------------- /templates/pacman.erb: -------------------------------------------------------------------------------- 1 | # Generated by fpm 2 | # Hello packaging friend! 3 | # 4 | # If you find yourself using this 'fpm --edit' feature frequently, it is 5 | # a sign that fpm is missing a feature! I welcome your feature requests! 6 | # Please visit the following URL and ask for a feature that helps you never 7 | # need to edit this file again! :) 8 | # https://github.com/jordansissel/fpm/issues 9 | # ------------------------------------------------------------------------ 10 | # 11 | pkgname = <%= name %> 12 | <% if !epoch.nil? -%> 13 | pkgver = <%= epoch %>:<%= version %>-<%= iteration %> 14 | <% else -%> 15 | pkgver = <%= version %>-<%= iteration %> 16 | <% end -%> 17 | pkgdesc = <%= description %> 18 | <% if !url.nil? and !url.empty? -%> 19 | url = <%= url %> 20 | <% else -%> 21 | url = http://nourlgiven.example.com/ 22 | <% end -%> 23 | builddate = <%= builddate %> 24 | packager = <%= maintainer %> 25 | size = <%= size %> 26 | arch = <%= architecture %> 27 | license = <%= license %> 28 | <% replaces.each do |repl| -%> 29 | replaces = <%= repl %> 30 | <% end -%> 31 | group = <%= category %> 32 | <% conflicts.each do |confl| -%> 33 | conflict = <%= confl %> 34 | <% end -%> 35 | <% provides.each do |prov| -%> 36 | provides = <%= prov %> 37 | <% end -%> 38 | <% config_files.each do |conf| -%> 39 | <% conf.gsub(/^\/+/, "") -%> 40 | backup = <%= conf %> 41 | <% end -%> 42 | <% dependencies.each do |req| -%> 43 | depend = <%= req %> 44 | <% end -%> 45 | <% attributes.fetch(:pacman_opt_depends, []).each do |opt| -%> 46 | optdepend = <%= opt %> 47 | <% end -%> 48 | -------------------------------------------------------------------------------- /templates/pacman/INSTALL.erb: -------------------------------------------------------------------------------- 1 | <%# In each function, a `:` is the first command. -%> 2 | <%# This makes sure that at least one command is in the function -%> 3 | <%# This avoids a lot of potential errors, including the case that -%> 4 | <%# the script is non-empty, but just whitespace and/or comments -%> 5 | <%# see issue #875 for more details -%> 6 | <% if script?(:before_install) -%> 7 | pre_install() { 8 | : 9 | <%= script(:before_install) %> 10 | } 11 | <% end -%> 12 | <% if script?(:after_install) -%> 13 | post_install() { 14 | : 15 | <%= script(:after_install) %> 16 | } 17 | <% end -%> 18 | <% if script?(:before_upgrade) -%> 19 | pre_upgrade() { 20 | : 21 | <%= script(:before_upgrade) %> 22 | } 23 | <% end -%> 24 | <% if script?(:after_upgrade) -%> 25 | post_upgrade() { 26 | : 27 | <%= script(:after_upgrade) %> 28 | } 29 | <% end -%> 30 | <% if script?(:before_remove) -%> 31 | pre_remove() { 32 | : 33 | <%= script(:before_remove) %> 34 | } 35 | <% end -%> 36 | <% if script?(:after_remove) -%> 37 | post_remove() { 38 | : 39 | <%= script(:after_remove) %> 40 | } 41 | <% end -%> 42 | -------------------------------------------------------------------------------- /templates/pleaserun/generate-cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | show_cleanup_step() { 4 | d="${1##.}" 5 | if [ ! -z "$d" ] ; then 6 | if [ -d "$1" -a ! -d "$d" ] ; then 7 | echo "rmdir \"$d\"" 8 | fi 9 | if [ -f "$1" ] ; then 10 | echo "rm \"$d\"" 11 | fi 12 | fi 13 | } 14 | 15 | for i in "$@" ; do 16 | show_cleanup_step "$i" 17 | done 18 | -------------------------------------------------------------------------------- /templates/pleaserun/install-path.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | install_path() { 4 | d="${1##.}" 5 | if [ ! -z "$d" ] ; then 6 | if [ -d "$1" -a ! -d "$d" ] ; then 7 | mkdir "$d" 8 | fi 9 | if [ -f "$1" ] ; then 10 | cp -p "$1" "$d" 11 | fi 12 | fi 13 | } 14 | 15 | for i in "$@" ; do 16 | install_path "$i" 17 | done 18 | -------------------------------------------------------------------------------- /templates/pleaserun/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | source="<%= attributes[:prefix] %>" 4 | 5 | cleanup_script="$source/cleanup.sh" 6 | 7 | silent() { 8 | "$@" > /dev/null 2>&1 9 | } 10 | 11 | install_files() { 12 | # TODO(sissel): Need to know what prefix the files exist at 13 | platform="$1" 14 | version="$(version_${platform})" 15 | 16 | ( 17 | # TODO(sissel): Should I just rely on rsync for this stuff? 18 | cd "${source}/${platform}/${version}/files/" || exit 1 19 | 20 | # Write a cleanup script 21 | find . -print0 | xargs -r0 -n1 "$source/generate-cleanup.sh" > "$cleanup_script" 22 | 23 | # Actually do the installation 24 | find . -print0 | xargs -r0 -n1 "$source/install-path.sh" 25 | ) 26 | } 27 | 28 | install_actions() { 29 | # TODO(sissel): Need to know what prefix the files exist at 30 | platform="$1" 31 | version="$(version_${platform})" 32 | 33 | 34 | actions="${source}/${platform}/${version}/install_actions.sh" 35 | if [ -f "$actions" ] ; then 36 | . "$actions" 37 | fi 38 | } 39 | 40 | version_systemd() { 41 | # Treat all systemd versions the same 42 | echo default 43 | } 44 | 45 | version_launchd() { 46 | # Treat all launchd versions the same 47 | echo 10.9 48 | } 49 | 50 | version_upstart() { 51 | # Treat all upstart versions the same 52 | # TODO(sissel): Upstart 0.6.5 needs to be handled specially. 53 | version="$(initctl --version | head -1 | tr -d '()' | awk '{print $NF}')" 54 | 55 | case $version in 56 | 0.6.5) echo $version ;; 57 | *) echo "1.5" ;; # default modern assumption 58 | esac 59 | } 60 | 61 | version_sysv() { 62 | # TODO(sissel): Once pleaserun supports multiple sysv implementations, maybe 63 | # we inspect the OS to find out what we should target. 64 | echo lsb-3.1 65 | } 66 | 67 | has_systemd() { 68 | # Some OS vendors put systemd in ... different places ... 69 | [ -d "/lib/systemd/system/" -o -d "/usr/lib/systemd/system" ] && silent which systemctl 70 | } 71 | 72 | has_upstart() { 73 | [ -d "/etc/init" ] && silent which initctl 74 | } 75 | 76 | has_sysv() { 77 | [ -d "/etc/init.d" ] 78 | } 79 | 80 | #has_freebsd_rcng() { 81 | #[ -d "/etc/rc.d" ] && silent which rcorder 82 | #} 83 | 84 | has_daemontools() { 85 | [ -d "/service" ] && silent which sv 86 | } 87 | 88 | has_launchd() { 89 | [ -d "/Library/LaunchDaemons" ] && silent which launchtl 90 | } 91 | 92 | install_help() { 93 | case $platform in 94 | systemd) echo "To start this service, use: systemctl start <%= attributes[:pleaserun_name] %>" ;; 95 | upstart) echo "To start this service, use: initctl start <%= attributes[:pleaserun_name] %>" ;; 96 | launchd) echo "To start this service, use: launchctl start <%= attributes[:pleaserun_name] %>" ;; 97 | sysv) echo "To start this service, use: /etc/init.d/<%= attributes[:pleaserun_name] %> start" ;; 98 | esac 99 | } 100 | 101 | platforms="systemd upstart launchd sysv" 102 | installed=0 103 | for platform in $platforms ; do 104 | if has_$platform ; then 105 | version="$(version_$platform)" 106 | echo "Platform $platform ($version) detected. Installing service." 107 | install_files $platform 108 | install_actions $platform 109 | install_help $platform 110 | installed=1 111 | break 112 | fi 113 | done 114 | 115 | if [ "$installed" -eq 0 ] ; then 116 | echo "Failed to detect any service platform, so no service was installed. Files are available in ${source} if you need them." 117 | fi 118 | -------------------------------------------------------------------------------- /templates/pleaserun/scripts/after-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | source="<%= attributes[:prefix] %>" 4 | exec sh "$source/install.sh" 5 | -------------------------------------------------------------------------------- /templates/pleaserun/scripts/before-remove.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | source="<%= attributes[:prefix] %>" 4 | 5 | if [ -f "$source/cleanup.sh" ] ; then 6 | echo "Running cleanup to remove service for package <%= attributes[:name] %>" 7 | set -e 8 | sh "$source/cleanup.sh" 9 | 10 | # Remove the script also since the package installation generated it. 11 | rm "$source/cleanup.sh" 12 | fi 13 | -------------------------------------------------------------------------------- /templates/puppet/package.pp.erb: -------------------------------------------------------------------------------- 1 | # TODO(sissel): implement 'anti' class for this package. 2 | class <%= name %>::package { 3 | $version = "<%= version %>" 4 | $iteration = "<%= iteration %>" 5 | 6 | file { 7 | <% paths.each do |path| 8 | stat = File.lstat(path) 9 | params = {} 10 | if stat.directory? 11 | params[:ensure] = "directory" 12 | elsif stat.symlink? 13 | params[:ensure] = "link" 14 | params[:target] = File.readlink(path) 15 | stat = File.stat(path) 16 | else 17 | params[:ensure] = "file" 18 | params[:source] = "puppet:///modules/#{name}/#{path}" 19 | end 20 | 21 | if params[:ensure] != "link" 22 | params[:owner] = uid2user(stat.uid) 23 | params[:group] = gid2group(stat.gid) 24 | params[:mode] = sprintf("%04o", stat.mode & 0777) 25 | end 26 | 27 | settings = puppetsort(params).collect { |k,v| "#{k} => \"#{v}\"" }.join(",\n ") 28 | -%> 29 | # <%= stat.inspect %> 30 | "<%= fixpath(path) %>": 31 | <%= settings %>; 32 | <% end # paths.each -%> 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /templates/puppet/package/remove.pp.erb: -------------------------------------------------------------------------------- 1 | class <%= name %>::package::remove { 2 | $version = "<%= version %>" 3 | $iteration = "<%= iteration %>" 4 | 5 | fail("DO NOT USE THIS. IT IS NOT WELL DEFINED YET.") 6 | 7 | file { 8 | <% paths.each do |path| -%> 9 | "<%= fixpath(path) %>": 10 | ensure => absent; 11 | <% end # paths.each -%> 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /templates/solaris.erb: -------------------------------------------------------------------------------- 1 | CLASSES=none 2 | TZ=PST 3 | PATH=/sbin:/usr/sbin:/usr/bin:/usr/sadm/install/bin 4 | BASEDIR=/ 5 | PKG=<%= name %> 6 | NAME=<%= name %> 7 | ARCH=<%= architecture %> 8 | VERSION=<%= version %><%= iteration && "-" + iteration.to_s %> 9 | CATEGORY=application 10 | <%# pkginfo(4) says DESC is max 256 characters -%> 11 | DESC=<%= description.split("\n").first[0..255] or "no description given" %> 12 | VENDOR=<%= maintainer %> 13 | <%# Take maintainer of "Foo " and use "bar@baz.com" -%> 14 | EMAIL=<%= (maintainer[/<.+>/] or maintainer).gsub(/[<>]/, "") %> 15 | 16 | -------------------------------------------------------------------------------- /test/vagrant.pp: -------------------------------------------------------------------------------- 1 | case $operatingsystem { 2 | centos, redhat, fedora: { 3 | $pkgupdate = "yum clean all" 4 | $devsuffix = "-devel" 5 | } 6 | debian, ubuntu: { 7 | $pkgupdate = "apt-get update" 8 | $devsuffix = "-dev" 9 | package { 10 | "lintian": ensure => latest 11 | } 12 | } 13 | Archlinux: { 14 | $pkgupdate = "pacman -Syu --noconfirm --needed" 15 | $devsuffix = "dev" 16 | } 17 | } 18 | 19 | exec { 20 | "update-packages": 21 | command => $pkgupdate, 22 | path => [ "/bin", "/usr/bin", "/sbin", "/usr/sbin" ], 23 | timeout => 14400 24 | } 25 | 26 | package { 27 | "git": ensure => latest; 28 | "bundler": provider => "gem", ensure => latest; 29 | "ruby$devsuffix": ensure => latest; 30 | } 31 | 32 | Exec["update-packages"] -> Package <| |> 33 | --------------------------------------------------------------------------------