├── .gitignore ├── CHANGES ├── COPYING ├── HACKING ├── INSTALLING ├── KNOWN-ISSUES ├── Makefile ├── README.md ├── TODO ├── configure ├── doc ├── Makefile ├── depsolver.md └── rpg-solve.1.ronn ├── misc └── rpg-completion.bash ├── munge.rb ├── rpg-build.sh ├── rpg-complete.sh ├── rpg-config.sh ├── rpg-dependencies.sh ├── rpg-diff.sh ├── rpg-env.sh ├── rpg-fetch.sh ├── rpg-fsck.sh ├── rpg-help.sh ├── rpg-install.sh ├── rpg-leaves.sh ├── rpg-list.sh ├── rpg-manifest.sh ├── rpg-outdated.sh ├── rpg-package-index.sh ├── rpg-package-install.sh ├── rpg-package-list.sh ├── rpg-package-register.sh ├── rpg-package-spec.rb ├── rpg-parse-gemfile.rb ├── rpg-parse-index.rb ├── rpg-prepare.sh ├── rpg-resolve.sh ├── rpg-sh-setup.sh ├── rpg-shit-list.sh ├── rpg-solve.c ├── rpg-steal.sh ├── rpg-sync.sh ├── rpg-uninstall.sh ├── rpg-unpack.sh ├── rpg-upgrade.sh ├── rpg.sh ├── strnatcmp.c ├── strnatcmp.h └── test ├── .gitignore ├── specs.4.8.gz ├── test-lib.sh └── test-rpg.sh /.gitignore: -------------------------------------------------------------------------------- 1 | config.mk 2 | config.sh 3 | rpg 4 | rpg-build 5 | rpg-config 6 | rpg-deps 7 | rpg-env 8 | rpg-fetch 9 | rpg-install 10 | rpg-list 11 | rpg-resolve 12 | rpg-sh-setup 13 | rpg-uninstall 14 | rpg-update 15 | rpg-version-test 16 | rpg-build.html 17 | rpg-config.html 18 | rpg-deps.html 19 | rpg-env.html 20 | rpg-fetch.html 21 | rpg-install.html 22 | rpg-list.html 23 | rpg-sa 24 | rpg-sh-setup.html 25 | rpg-uninstall.html 26 | rpg-update.html 27 | rpg-version-test.html 28 | rpg.html 29 | rpg-steal 30 | rpg-steal.html 31 | rpg-upgrade 32 | rpg-upgrade.html 33 | rpg-fsck 34 | rpg-outdated 35 | rpg-status 36 | rpg-fsck.html 37 | rpg-outdated.html 38 | rpg-resolve.html 39 | rpg-status.html 40 | rpg-package-install 41 | rpg-package-register 42 | rpg-parse-gemfile 43 | rpg-parse-package-list 44 | rpg-solve 45 | rpg-unpack 46 | rpg-package-spec 47 | rpg-parse-index 48 | rpg-solve.html 49 | rpg-package-install.html 50 | rpg-package-register.html 51 | rpg-package-spec.html 52 | rpg-parse-index.html 53 | rpg-shit-list 54 | rpg-prepare 55 | rpg-complete 56 | rpg-sync 57 | rpg-help 58 | rpg-package-list 59 | rpg-package-index 60 | rpg-dependencies 61 | strnatcmp.o 62 | rpg-solve-fast.o 63 | rpg-solve-fast 64 | rpg-leaves 65 | rpg-diff 66 | rpg-manifest 67 | *.o 68 | work 69 | test/trash 70 | setup 71 | tags 72 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | RPG CHANGES 2 | =========== 3 | 4 | Version 0.3 (2010 Jun 24) 5 | ------------------------- 6 | 7 | This is a smallish bug fix and package compatibility release: 8 | 9 | * Package compatibility: thin, RedCloth, taps, json, url_escape. 10 | [Ryan Tomayko, Josh Peek] 11 | 12 | * The default RPGSTALETIME is now 2 weeks instead of 1 day. Use rpg-sync to 13 | update the gem index more frequently. [Ryan Tomayko] 14 | 15 | * Fixed a bug where environment variables set in ~/.rpgrc and /etc/rpgrc would 16 | override values set in the current environment. [Josh Peek] 17 | 18 | * Improved default mac framework install. /Library/Ruby/RPG/1.8 is used as 19 | the root for the RPGDB, RPGINDEX, RPGPACKS, and RPGCACHE paths. Executables 20 | go to /usr/bin; library files to /usr/lib/ruby/vendor_ruby/1.8. Unfortunately, 21 | this still requires root privileges. [Ryan Tomayko] 22 | 23 | Version 0.2 (2010 Apr 27) 24 | ------------------------- 25 | 26 | * The ~/.rpgrc and /etc/rpgrc files are now sourced properly and can be 27 | used to control installation and internal database locations. These files 28 | did not work properly in the 0.1 release, so installing to non-default 29 | locations was impossible. [Ryan Tomayko] 30 | 31 | * Added the RPGGEMURL config variable for controlling the base gem server 32 | location. The default value is "http://rubygems.org/downloads" but it can 33 | be set to any base URL (http: or file:) with gem files available at 34 | "$RPGGEMURL/-.gem" [Josh Peek] 35 | 36 | * rpg-fetch now fails with an error message and non-zero exit status when 37 | the gem server returns 404 for a package. [Josh Peek] 38 | 39 | * Dependencies are now resolved and installed properly for gems that use 40 | the older gemspec format without dependency type information (runtime vs. 41 | development) [Ryan Tomayko] 42 | 43 | * rpg-list now supports globs when listing all/remote packages. e.g., 44 | `rpg list -a rack\*'. [Josh Peek, Ryan Tomayko] 45 | 46 | * rpg-package-install now uses the gemspec defined "require_paths" value 47 | instead of assuming the "lib" directory is the only source of ruby 48 | library files. Multiple lib directories are also supported. This fixes a 49 | variety of packages, including ruby-debug and facets. [Ryan Tomayko] 50 | 51 | * rpg-manifest now abbreviates installed file paths using a "lib/", "bin/", 52 | or "man/" prefix instead of showing absolute paths. The -a option can be 53 | used to get the old behavior. [Ryan Tomayko] 54 | 55 | * rpg-env now properly adds RPGBIN to PATH, and RPGLIB to RUBYLIB. The 56 | previous version did not, so executing commands like `rpg env rake --help' 57 | or `rpg env bash' did not setup the environment properly. [Ryan Tomayko] 58 | 59 | * Fixed a bug with `--help' not working properly when passed anywhere other 60 | than the last argument. [Ryan Tomayko] 61 | 62 | * Various development mode `configure --development' fixes. [Ryan Tomayko] 63 | 64 | Package specific fixes: 65 | 66 | * facets: fixed to install non-standard library directories. [Josh Peek] 67 | 68 | * json: fixed installation location of native extension library [Josh Peek] 69 | 70 | * mongrel: shit listed to fix missing shebang in mongrel_rails executable, 71 | and also to make mongrel_rails available on the load path to work around 72 | issues with Rails 2.x loading it in `script/server'. [Ryan Tomayko] 73 | 74 | * ruby-debug: fixed to install non-standard library directories and 75 | dependencies. [Ryan Tomayko] 76 | 77 | Version 0.1 (2010 Apr 19) 78 | ------------------------- 79 | 80 | * Initial public release. Experimental. 81 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Ryan Tomayko 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /HACKING: -------------------------------------------------------------------------------- 1 | HACKING 2 | 3 | This is a quick guide to setting up a temporary working environment for hacking 4 | on the rpg source code. 5 | 6 | rpg is really a bunch of separate composeable programs, each in the unix style 7 | of doing one specific thing. Most of these programs are written in POSIX shell 8 | (rpg-*.sh files), a small few are written in Ruby (rpg-*.rb files), and one or 9 | two are written in ANSI C (rpg-*.c files). In any case, the programs are built 10 | as "rpg-" (i.e., without file extension) so that any program may be 11 | rewritten in a different source language and not effect the programs that rely 12 | on it. 13 | 14 | Grabbing the code 15 | ----------------- 16 | 17 | If you haven't already: 18 | 19 | git clone git://github.com/rtomayko/rpg.git 20 | cd rpg 21 | 22 | Working out of a source directory 23 | --------------------------------- 24 | 25 | There's a special configure option for working out of a source directory: 26 | 27 | ./configure --development 28 | 29 | This generates default config.sh and config.mk files that cause the rpg programs 30 | to use a temporary work area (/work) for keeping the various package 31 | databases and installing files. Once `configure --development' is complete, add 32 | the source dir at the beginning of your PATH and you should be all set: 33 | 34 | PATH="$(pwd):$PATH" 35 | 36 | With the source directory on your PATH, the general workflow is to edit the 37 | rpg-.sh and rpg-.rb files, then run `make' to build rpg-. 38 | 39 | Then just execute them: 40 | 41 | make 42 | rpg --help 43 | 44 | make 45 | [SH] rpg-install OK 46 | [SH] rpg-fetch OK 47 | rpg-fetch sinatra 0.9.6 48 | rpg-install sinatra 0.9.6 49 | 50 | There's also a `make auto' target that sits in a loop and constantly looks for 51 | stuff to rebuild so you don't have to do so manually after every edit: 52 | 53 | make auto 54 | 55 | The way I work is with a screen(1) session horizontally split such that I have a 56 | normal shell prompt up top and a `make auto' session down below at ~15% height. 57 | Then I have vim going to the left of the terminal so that I can see the output 58 | from `make auto' while I'm editing. The make targets run syntax checks on the sh 59 | and ruby source so this is great for catching syntax errors early on. 60 | 61 | New programs 62 | ------------ 63 | 64 | To add a new program, create a rpg-.sh, rpg-.rb, or rpg-.c 65 | file and then open the Makefile and add the filename to the SOURCES and PROGRAMS 66 | variables. The program will be built like the others the next time you run `make'. 67 | 68 | POSIX shell 69 | ----------- 70 | 71 | The rpg shell sources should conform to "The Standard" where possible. Any 72 | shell language features, utility programs, or arguments to utility programs not 73 | documented here should be avoided: 74 | 75 | http://www.opengroup.org/onlinepubs/009695399/utilities/contents.html 76 | 77 | This isn't really about portability (although that's a nice feature). The main 78 | reason for conforming to the SUS defined portions of the shell and tool usage is 79 | that it's a well-defined, functional, and fairly simple subset of the unix/shell 80 | universe that's known to work predictably in a wide-enough range of 81 | environments. It just keeps things simple so they can fit in your head. 82 | 83 | POSIX shell and shell utility man pages can be found at man.cx. It's a bit 84 | easier to read than the opengroup.org documentation for my eyes. HOWEVER, make 85 | sure you're looking at a 1posix section manpage. You can tell because there'll 86 | be "(1posix)" in the URL. Some examples: 87 | 88 | http://man.cx/sh(1posix) 89 | http://man.cx/sort(1posix) 90 | http://man.cx/join(1posix) 91 | http://man.cx/sed(1posix) 92 | 93 | Lastly, shell programs should be tested under dash if possible. This is the 94 | default /bin/sh on recent Debian versions and can be easily installed on MacOS X 95 | using homebrew: `brew install dash'. 96 | 97 | POSIX make 98 | ---------- 99 | 100 | The makefile shall remain POSIX so that it works with GNU and BSD makes. 101 | 102 | http://man.cx/make(1posix) 103 | 104 | There's no reason to use the advanced features of non-POSIX makes. 105 | 106 | Docco-Mentation 107 | --------------- 108 | 109 | The shell and Ruby sources are commented with Docco literate-programming-style 110 | documentation. If you have Rocco and shocco installed locally, you can build 111 | docs for all source files with: 112 | 113 | make doc 114 | 115 | Or for a specific set of files with: 116 | 117 | make rpg-sh-setup.html 118 | 119 | These docs are also available on the web at: 120 | 121 | http://rtomayko.github.com/rpg/ 122 | 123 | For more information on Docco, Rocco, and shocco: 124 | 125 | http://jashkenas.github.com/docco/ 126 | http://rtomayko.github.com/rocco/ 127 | http://rtomayko.github.com/shocco/ 128 | 129 | Manpages 130 | -------- 131 | 132 | Each program will eventually have a unix manual page. I plan to write a tool to 133 | generate the initial set from comments at some point in the not too distant 134 | future. For now, try to include any information useful in a manpage in source 135 | file comments and make sure USAGE messages document all options. 136 | 137 | And that should do it. 138 | 139 | Have fun! 140 | 141 | Ryan Tomayko 142 | -------------------------------------------------------------------------------- /INSTALLING: -------------------------------------------------------------------------------- 1 | INSTALLING 2 | 3 | rpg is installed with a conventional `./configure; make; make install' process. 4 | This document describes where to obtain the latest release and how to customize 5 | the installation for different configurations. 6 | 7 | See the HACKING file for information on setting up a temporary working 8 | environment for development or test-driving rpg without modifying your system 9 | directories. 10 | 11 | OBTAINING 12 | --------- 13 | 14 | If you have git, the easiest way to obtain the latest sources is to clone the 15 | repository: 16 | 17 | git clone git://github.com/rtomayko/rpg.git 18 | cd rpg 19 | 20 | Release tarballs are also available from the rpg downloads page: 21 | 22 | http://github.com/rtomayko/rpg/downloads 23 | 24 | Start by fetching and extracting the latest rpg release into a temporary 25 | directory, and then switch into it: 26 | 27 | curl -L http://github.com/downloads/rtomayko/rpg/rpg-0.2.0.tar.gz | tar xvzf - 28 | cd rpg-0.2.0 29 | 30 | INSTALLING 31 | ---------- 32 | 33 | rpg works with two trees: one for rpg binaries and supporting code, and one 34 | for installed packages and the package database. It is possible to have either 35 | of these trees be standalone, or located under an existing ruby installation. 36 | 37 | To install rpg under the default location (`/usr/local`): 38 | 39 | ./configure 40 | make 41 | sudo make install 42 | 43 | Ruby packages will be installed under your ruby installation 44 | (e.g. /usr/local/lib/ruby or /usr/lib/ruby). Assuming that /usr/local/bin 45 | is in your PATH environment variable no further configuration is necessary. 46 | 47 | To install rpg into a self-contained directory off your home: 48 | 49 | ./configure --prefix=~/.rpg --rpgdir ~/.packages 50 | make 51 | make install 52 | 53 | Ruby packages will be installed in ~/.packages/lib, and binaries 54 | for ruby packages in ~/.packages/bin, requiring these directories be added 55 | to RUBYOPT and PATH as follows: 56 | 57 | RUBYOPT="$RUBYOPT -I~/.packages/lib" 58 | PATH="$PATH:~/.packages/bin" 59 | 60 | You might need to use $HOME instead of ~ depending on your shell/environment. 61 | 62 | rpg itself requires that its `bin` directory (`/bin`) be on your PATH 63 | as well (this is needed for rpg operations on packages, not for using packages 64 | installed by rpg). If it is not there already add the following to your 65 | ~/.bashrc or ~/.profile to ensure rpg is available to all new shell sessions: 66 | 67 | PATH="$PATH:/bin" 68 | 69 | To install rpg into an existing system-wide ruby installation: 70 | 71 | ./configure --with-ruby 72 | make 73 | sudo make install 74 | 75 | To install rpg into a specific ruby installation: 76 | 77 | ./configure --with-ruby=~/.rvm/rubies/ruby187 78 | make 79 | make install 80 | 81 | In both of these cases packages will be installed under the ruby installation 82 | tree so that no further configuration of environment variables is needed. 83 | 84 | The installation puts files under the bin, libexec, and share/man directories of 85 | the configured . See `configure --help' for information on tuning 86 | these default locations. 87 | 88 | On FreeBSD, if installing as an unprivileged user, you might receive an error 89 | during `make install` complaining about insufficient permissions for `chown`. 90 | To fix it, configure with `RB_USER_INSTALL=yes` as follows: 91 | 92 | RB_USER_INSTALL=yes ./configure --prefix=... [other options...] 93 | -------------------------------------------------------------------------------- /KNOWN-ISSUES: -------------------------------------------------------------------------------- 1 | These are some known issues with rpg, their causes, and notes on plans for 2 | addressing them. This list intentionally does not include missing features but 3 | rather things that you are likely to run into during the course of using rpg. 4 | 5 | - Packages with libraries or executables that assume they are installed under a 6 | directory-per-package structure and attempt to read files outside of their 7 | containing lib or bin directory will fail to locate those files. This 8 | typically manifests with library files that attempt to read a VERSION file 9 | from the package's root directory. 10 | 11 | The rpg-shit-list program is a crappy hack that attempts to patch certain 12 | popular libraries up during installation. 13 | 14 | There is as of yet no compelling proposed solution to this issue beyond 15 | requesting (preferably via patch) that package maintainers avoid making 16 | assumptions about the locations of library and executable files where 17 | possible. 18 | 19 | - Packages with libraries or executables that call the Rubygems 20 | 'gem(, )' method to declare and load dependent packages at 21 | runtime will fail to locate the package (unless the gem is installed within 22 | rubygems environment). It is very rarely appropriate or beneficial for 23 | libraries to make these sort of calls explicitly since rubygems handles this 24 | by installing wrapper executables and also when loading packages via its 25 | Kernel#require hooks. 26 | 27 | There is no planned solution to this issue beyond asking project maintainers 28 | to avoid the gem method where not truly warranted. 29 | 30 | UPDATE: Josh Peek's gem_stub program can be 31 | used to install gemspec files for rpg installed packages. This functionality 32 | may make its way into the rpg codebase. 33 | 34 | - There is currently no (straightforward) way to manually resolve exclusive 35 | version conflicts during install. The install fails with a message stating 36 | that some "packages cannot be resolved". The most typical cause of exclusive 37 | version conflicts is when two or more packages being installed (or already 38 | installed) specify incompatible versions of package dependencies. 39 | 40 | An interactive version resolution system is planned. 41 | 42 | - The rpg-uninstall program performs no dependency checking or recursive 43 | uninstallation of packages. Uninstalling packages that other packages depend 44 | on will result in an inconsistent environment. 45 | 46 | Support for dependency aware uninstallation is planned. 47 | 48 | - Using rpg with a root-owned / system ruby requires root privileges unless 49 | a great many RPGXXX variables are tuned via rpg-config. Using rpg with a 50 | system ruby is not recommended at this time. Use with rvm or custom / 51 | non-system ruby installations for now. 52 | 53 | Full support for all of the following is planned: 1.) installing libraries 54 | into a system ruby environment, 2.) automatic privilege-deescalation on 55 | operations not requiring superuser privileges, and 3.) installing under 56 | multiple custom/configurable root-owned locations. 57 | 58 | - Extension libraries that rely on the `make install' target to perform custom 59 | tasks may not be installed or function properly. It is assumed that extensions 60 | produce one or more shared object files; rpg installs these manually -- without 61 | invoking the `make install` target -- due to issues with tracking files 62 | installed. 63 | 64 | - Some really awesome features of Rubygems are not yet supported. These include: 65 | installing packages with their development dependencies, installing 66 | pre-release packages, and installing packages from multiple source 67 | repositories. All of these features are planned and fairly high priority. 68 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # rpg makefile 2 | .POSIX: 3 | 4 | # Default make target 5 | all:: 6 | 7 | include config.mk 8 | 9 | NAME = rpg 10 | TARNAME = $(NAME) 11 | SHELL = /bin/sh 12 | 13 | CFLAGS = -Wall -pedantic 14 | 15 | # ---- END OF CONFIGURATION ---- 16 | 17 | all:: build 18 | 19 | DOCHTML = \ 20 | rpg-sh-setup.html rpg.html rpg-fetch.html \ 21 | rpg-sync.html rpg-upgrade.html rpg-outdated.html \ 22 | rpg-package-install.html rpg-package-spec.html rpg-parse-index.html \ 23 | rpg-list.html 24 | 25 | PROGRAMPROGRAMS = \ 26 | rpg-config rpg-fetch rpg-install rpg-uninstall rpg-build \ 27 | rpg-env rpg-sync rpg-resolve rpg-upgrade rpg-steal rpg-fsck rpg-list \ 28 | rpg-outdated rpg-package-list rpg-package-register rpg-package-install \ 29 | rpg-unpack rpg-package-spec rpg-parse-index rpg-shit-list \ 30 | rpg-prepare rpg-complete rpg-help rpg-package-index rpg-dependencies \ 31 | rpg-leaves rpg-manifest rpg-solve rpg-diff rpg-parse-gemfile 32 | 33 | DEADPROGRAMS = \ 34 | rpg-update rpg-status rpg-parse-package-list rpg-version-test 35 | 36 | OBJECTS = \ 37 | strnatcmp.o rpg-solve.o 38 | 39 | USERPROGRAMS = rpg rpg-sh-setup 40 | PROGRAMS = $(USERPROGRAMS) $(PROGRAMPROGRAMS) 41 | 42 | .SUFFIXES: .sh .rb .html .c .o 43 | 44 | .sh: 45 | printf "%13s %-30s" "[SH]" "$@" 46 | $(SHELL) -n $< 47 | rm -f $@ 48 | $(RUBY) ./munge.rb __RPGCONFIG__ config.sh <$< >$@+ 49 | chmod a-w+x $@+ 50 | mv $@+ $@ 51 | printf " OK\n" 52 | 53 | .sh.html: 54 | printf "%13s %-30s" "[SHOCCO]" "$@" 55 | shocco $< > $@ 56 | printf " OK\n" 57 | 58 | .rb: 59 | printf "%13s %-30s" "[RUBY]" "$@" 60 | ruby -c $< >/dev/null 61 | rm -f $@ 62 | cp $< $@ 63 | chmod a-w+x $@ 64 | printf " OK\n" 65 | 66 | .rb.html: 67 | printf "%13s %-30s" "[ROCCO]" "$@" 68 | rocco $< >/dev/null 69 | printf " OK\n" 70 | 71 | .c.o: 72 | printf "%13s %-30s" "[CC]" "$@" 73 | $(CC) -c $(CFLAGS) $< 74 | printf " OK\n" 75 | 76 | rpg-sh-setup: config.sh munge.rb rpg-sh-setup.sh 77 | rpg: config.sh munge.rb rpg.sh 78 | 79 | rpg-solve: rpg-solve.o strnatcmp.o 80 | printf "%13s %-30s" "[LINK]" "$@" 81 | $(CC) $(CFLAGS) $(LDFLAGS) rpg-solve.o strnatcmp.o -o $@ 82 | printf " OK\n" 83 | 84 | rpg-solve-fast.o: rpg-solve.c strnatcmp.h 85 | strnatcmp.o: strnatcmp.c strnatcmp.h 86 | 87 | build: $(PROGRAMS) 88 | 89 | auto: 90 | while true; do $(MAKE) ; sleep 1; done 91 | 92 | man: 93 | $(MAKE) -C doc man 94 | 95 | doc: $(DOCHTML) 96 | 97 | test: build 98 | cd test && $(SHELL) test-rpg.sh 99 | 100 | install: 101 | mkdir -p "$(bindir)" || true 102 | for f in $(USERPROGRAMS); do \ 103 | echo "$(INSTALL_PROGRAM) $$f $(bindir)"; \ 104 | $(INSTALL_PROGRAM) $$f "$(bindir)"; \ 105 | done 106 | mkdir -p "$(libexecdir)" || true 107 | for f in $(PROGRAMPROGRAMS); do \ 108 | echo "$(INSTALL_PROGRAM) $$f $(libexecdir)"; \ 109 | $(INSTALL_PROGRAM) $$f "$(libexecdir)"; \ 110 | done 111 | 112 | uninstall: 113 | for f in $(USERPROGRAMS); do \ 114 | test -e "$(bindir)/$$f" || continue; \ 115 | echo "rm -f $(bindir)/$$f"; \ 116 | rm "$(bindir)/$$f"; \ 117 | done 118 | for f in $(PROGRAMPROGRAMS) $(DEADPROGRAMS); do \ 119 | test -e "$(libexecdir)/$$f" || continue; \ 120 | echo "rm -f $(libexecdir)/$$f"; \ 121 | rm "$(libexecdir)/$$f"; \ 122 | done 123 | 124 | install-local: 125 | ./configure --prefix=/usr/local 126 | sleep 1 127 | make 128 | make install 129 | ./configure --development 130 | 131 | clean: 132 | rm -vf $(PROGRAMS) $(DOCHTML) $(OBJECTS) 133 | $(MAKE) -C doc clean 134 | 135 | tags: 136 | ctags --extra=+f \ 137 | --totals=yes \ 138 | --fields=+iaS \ 139 | --exclude=@.gitignore \ 140 | --exclude=/usr/X11 \ 141 | -R -f tags . /usr/include 142 | 143 | dist: 144 | tarname=$(TARNAME)-$$(git describe --tags); \ 145 | mkdir -p dist; \ 146 | if test -f dist/$$tarname.tar.gz; \ 147 | then echo "$$tarname.tar.gz already exists. not overwriting." 1>&2; \ 148 | false; \ 149 | else git archive --format=tar --prefix=$${tarname}/ HEAD | \ 150 | gzip >dist/$$tarname.tar.gz && \ 151 | echo "dist/$$tarname.tar.gz"; \ 152 | fi 153 | 154 | .SILENT: 155 | 156 | .PHONY: install uninstall clean tags dist 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rpg - manages gem packages. quickly. 2 | ==================================== 3 | 4 | This is rpg, an experimental Ruby package management utility for unix based on 5 | the Rubygems packaging format and repository protocol. rpg installs Ruby 6 | packages distributed from rubygems.org to a shared library directory with full 7 | support for dependency resolution, native extension compilation, and package 8 | upgrades. It's quite fast. 9 | 10 | `rpg` can be thought of as a non-compatible alternative to the `gem` command 11 | shipped with Rubygems. Most commonly used gem operations are available in `rpg`, 12 | but in ways that are a bit different from the `gem` command -- both in interface 13 | and implementation. See the *VERSUS RUBYGEMS* section below for details on these 14 | differences. 15 | 16 | rpg and Rubygems can co-exist on a system, though Rubygems is not required for 17 | rpg to operate. Packages installed with `rpg` override packages installed with 18 | the `gem` command. 19 | 20 | Please direct rpg related discussion to the rpg mailing list: 21 | [ruby-rpg@googlegroups.com](http://groups.google.com/group/ruby-rpg). 22 | 23 | Status 24 | ------ 25 | 26 | *Update: This repository is no longer actively maintained by @rtomayko. Issues and PRs documenting current issues have been intentionally left open for informational purposes.* 27 | 28 | Experimental. Using rpg with system rubys is not yet recommended. Suggested use 29 | is with rvm or custom, non-system ruby builds. See the `KNOWN-ISSUES` file for a 30 | list of potential gotchas and general annoyances. 31 | 32 | IMPORTANT: In its default configuration, rpg installs library files under the 33 | active Ruby interpreter's `vendor_ruby` or `site_ruby` directory. The `rpg 34 | config` command outputs the current destination installation paths -- use it to 35 | verify the active configuration before performing destructive operations. 36 | 37 | Installing 38 | ---------- 39 | 40 | rpg is installed with the conventional `./configure && make && make install` 41 | process. See the `INSTALLING` file for information on obtaining the latest 42 | release and variations on the basic installation. 43 | 44 | See the `HACKING` file for information on setting up a temporary working 45 | environment for development, or if you just want to try out rpg in a sandbox 46 | before installing. 47 | 48 | Basic Usage 49 | ----------- 50 | 51 | For a list of commands and basic program usage: 52 | 53 | $ rpg --help 54 | Usage: rpg [-vx] [-c ] [...] 55 | Manage gem packages, quickly. 56 | 57 | The most commonly used rpg commands are: 58 | config Show or edit rpg configuration 59 | dependencies Show dependency information for a package or all packages 60 | install Install a package from file or remote repository 61 | list Show status of local packages vs. respository 62 | steal Transplant packages from Rubygems into rpg environment 63 | sync Sync the package index with repository 64 | outdated List packages with a newer version 65 | uninstall Uninstall packages from local system 66 | upgrade Upgrade installed packages to latest version 67 | 68 | Options 69 | -c Read rcfile at instead of standard rpgrc locations 70 | -v Enable verbose logging to stderr 71 | -q Disable verbose logging to stderr 72 | -x Enable shell tracing to stderr (extremely verbose) 73 | 74 | See `rpg help ' for more information on a specific command. 75 | 76 | Installing one or more packages and all package dependencies: 77 | 78 | $ rpg install rails 79 | sync: package index not found. retrieving now. 80 | sync: complete. 11894 packages available. 81 | prepare: calculating dependencies for rails ... 82 | fetch: rails 2.3.5 83 | fetch: activeresource 2.3.5 84 | fetch: actionmailer 2.3.5 85 | fetch: actionpack 2.3.5 86 | fetch: activesupport 2.3.5 87 | fetch: rake 0.8.7 88 | fetch: activerecord 2.3.5 89 | fetch: rack 1.0.1 90 | prepare: 0 of 8 packages already installed and up to date 91 | install: installing 8 packages 92 | package-install: actionmailer 2.3.5 93 | package-install: actionpack 2.3.5 94 | package-install: activerecord 2.3.5 95 | package-install: activeresource 2.3.5 96 | package-install: activesupport 2.3.5 97 | package-install: rack 1.0.1 98 | package-install: rails 2.3.5 99 | package-install: rake 0.8.7 100 | install: installation complete 101 | 102 | Listing currently installed packages and their versions: 103 | 104 | $ rpg list 105 | actionmailer 2.3.5 106 | actionpack 2.3.5 107 | activerecord 2.3.5 108 | activeresource 2.3.5 109 | activesupport 2.3.5 110 | rack 1.0.1 111 | rails 2.3.5 112 | rake 0.8.7 113 | 114 | Listing currently installed packages with information about available package 115 | versions: 116 | 117 | $ rpg list -l 118 | actionmailer 2.3.5 2.3.5 119 | actionpack 2.3.5 2.3.5 120 | activerecord 2.3.5 2.3.5 121 | activeresource 2.3.5 2.3.5 122 | activesupport 2.3.5 2.3.5 123 | * rack 1.0.1 1.1.0 124 | rails 2.3.5 2.3.5 125 | rake 0.8.7 0.8.7 126 | 127 | Listing only outdated packages: 128 | 129 | $ rpg outdated 130 | rack 1.0.1 1.1.0 131 | 132 | Uninstalling one or more packages: 133 | 134 | $ rpg uninstall rails actionmailer 135 | 136 | Listing package dependencies recursively: 137 | 138 | $ rpg dependencies -r rails 139 | actionmailer = 2.3.5 140 | actionpack = 2.3.5 141 | activerecord = 2.3.5 142 | activeresource = 2.3.5 143 | activesupport = 2.3.5 144 | rack ~> 1.0.0 145 | rake >= 0.8.3 146 | 147 | Or, in a tree: 148 | 149 | $ rpg dependencies -t rails 150 | rake >= 0.8.3 151 | activesupport = 2.3.5 152 | activerecord = 2.3.5 153 | |-- activesupport = 2.3.5 154 | actionpack = 2.3.5 155 | |-- activesupport = 2.3.5 156 | |-- rack ~> 1.0.0 157 | actionmailer = 2.3.5 158 | |-- actionpack = 2.3.5 159 | | |-- activesupport = 2.3.5 160 | | |-- rack ~> 1.0.0 161 | activeresource = 2.3.5 162 | |-- activesupport = 2.3.5 163 | 164 | To get a feel for rpg performance vs. the gem command when install packages with 165 | complex dependency graphs: 166 | 167 | $ time rpg install merb 168 | $ time gem install merb 169 | 170 | Versus Rubygems 171 | --------------- 172 | 173 | Similarities with the `gem` command: 174 | 175 | * `rpg` uses rubygems.org as its package repository and gem 176 | files as its package format. Installing from other sources is not yet 177 | supported, but is likely to be added in the near future. 178 | 179 | * `rpg` supports dependency resolution using the information 180 | included in a gem's specification metadata. 181 | 182 | * `rpg` supports building and installing native / dynamic library 183 | extensions. 184 | 185 | * `rpg` has a rich set of commands for installing, upgrading, 186 | and uninstalling packages; listing installed, available, and outdated 187 | packages; and utilities for unpacking gem files and inspecting gem 188 | specifications. 189 | 190 | * "rpg" is made of exactly three characters. 191 | 192 | Differences from the `gem` command: 193 | 194 | * `rpg` organizes the files it installs by file type, not by package. For 195 | instance, Ruby library files are placed directly under a single 196 | `lib` directory (the currently active `site_ruby` directory by default), 197 | executables under `/usr/local/bin` (configurable), manpages under 198 | `/usr/local/share/man`, etc. 199 | 200 | * `rpg` is not capable of installing multiple versions of the same package 201 | into a single rpg environment -- the package's files would overwrite each 202 | other. All version conflicts must be resolved at install time. 203 | 204 | * `rpg` is similarly unable to install more than one package owning the 205 | same file under Ruby libdir. (Currently `rpg` will install such packages 206 | anyway, with later installed packages overwriting files installed by 207 | earlier installed packages.) 208 | 209 | * `rpg` has no runtime component (e.g., `require 'rubygems'`). Because all 210 | library files are placed under a common `lib` directory, and because package 211 | versions are sussed at install time, there's no need for a component to 212 | select which packages are active at runtime. 213 | 214 | * `rpg` installs packages in two stages: 1.) fetch package files 215 | and resolve dependencies, and 2.) install package contents. This allows 216 | for staged/later installs and conflict detection before install. 217 | 218 | * `rpg`'s installed package database is filesystem based, (will be) documented, 219 | and is built for extension. 220 | 221 | * `rpg` is written primarily in POSIX shell and requires a unix environment. 222 | 223 | * `rpg` does not provide commands for building gems or running gem servers. 224 | 225 | * `rpg` outperforms the gem command in many ways. Most comparable operations 226 | complete in at least one order of magnitude less time. 227 | 228 | About 229 | ----- 230 | 231 | rpg's design is inspired by a variety of existing tools. The `gem` command's 232 | basic UI, package format, and repository structure are heavily borrowed from 233 | or used verbatim. 234 | 235 | Many of the ideas -- and maybe even some code -- were taken from 236 | [Rip](http://defunkt.github.com/rip/). That's understating it, really. rpg 237 | started out just a couple of loose shell scripts to experiment with ideas for 238 | integrating gem package and gem dependency support into rip. The plan was to 239 | port them over to Ruby and into Rip if they panned out. Within a few days, I had 240 | a more or less entire implementation of Rubygems's gem command in POSIX shell 241 | staring back at me and it was *fast*. Some of rpg's features may make their way 242 | into Rip (the Ruby portions that read release indexes and gemspecs should be 243 | useful at least). 244 | 245 | Debian's apt and dpkg, FreeBSD's ports system, and Redhat/Fedora's yum all 246 | influenced rpg's design in various ways. 247 | 248 | Git's overall design influenced rpg significantly. Git's internal project 249 | organization is a template for writing moderate sized systems using many small 250 | specialized programs. Its granular use of the filesystem as a database (the 251 | `.git/refs` and `.git/objects` hierarchies, especially) informed much of rpg's 252 | package database design. 253 | 254 | Copying 255 | ------- 256 | 257 | Copyright (c) 2010 by [Ryan Tomayko](http://tomayko.com/about) 258 | 259 | This is Free Software distributed under the terms of the MIT license. 260 | See the `COPYING` file for license rights and limitations. 261 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | NEXT 2 | 3 | [x] rpg-steal - steal current rubygems environment 4 | [x] rpg-status cache 5 | [x] rpg-status 6 | [x] rpg-outdated 7 | [x] usage message helpers in rpg-sh-setup. 8 | [x] fetch everything then upgrade 9 | [x] dep solving 10 | [x] RPGDB///deps = dep list 11 | [x] all gemspec information in database 12 | [x] use tar to extract gems instead of unpack 13 | [x] README 14 | [x] HACKING 15 | [x] need default output 16 | [x] remove reliance on `gem list' for building index 17 | [x] install to libexec. rpg.sh knows path. 18 | [x] config.sh and config.mk / controlling `make install' 19 | [x] configure, make, make install 20 | [x] automatic RPGXXX configuration based on active ruby 21 | [x] rpg-shit-list.sh 22 | [x] make clean can fail on extension build 23 | [x] depsolver uses previous pass to resolve deps 24 | [x] rpg prepare - prepare an install for later 25 | [x] bash completion 26 | [x] rpg-update -> rpg-sync 27 | [x] rpg-help 28 | [x] rpg-list -> rpg-package-list 29 | [x] rpg-status -> rpg-list 30 | [x] rpg-status -s writes output in short format (package name) 31 | [x] default system install on mac 32 | [x] rpg-config groups variables with comments 33 | [x] rpg-config -e for editing user, ruby, system configs 34 | [x] rpg dep solver 35 | [x] rpg dep solver detects incompatible packages 36 | [x] rpg-deps shows gem dependency (tree) 37 | [x] rpg-prepare bails out with bad packages for now (until interactive) 38 | [x] dependencies file includes source package name (used grep instead) 39 | [x] rpg-solve in C - single pass over index file per run 40 | [x] rpg-manifest shows package manifest 41 | [x] uninstall before install 42 | [x] rpg-unpack with fetches 43 | [x] rpg-diff shows diff between two package versions 44 | [x] basic TAP test framework 45 | [x] rpg-sync fails with no network connection with -s. 46 | [x] tweak rpg-prepare/rpg-install/rpg-upgrade output in different cases 47 | [x] exclude non-unix platforms when building release index 48 | [x] update status section in README 49 | [x] update README with note on rvm and non-system installs 50 | [x] shitlist: mongrel_rails bad shebang 51 | 52 | LATER 53 | 54 | [ ] rpg-fetch should fetch into the current directory by default 55 | [ ] rpg-package-spec with fetches 56 | [ ] install from gem file 57 | [ ] install extensions with make install and put in sitearchdir 58 | [ ] implement rpg -q for silencing heed 59 | [ ] rpg list multiple available versions 60 | [ ] man pages (write program to extract usage message) 61 | [ ] prelease packages 62 | [ ] development deps 63 | [ ] rpg-whatsnew shows what's new since last sync using diff 64 | [ ] warn when overwriting files on install 65 | [ ] rpg-which shows which package a file belongs to 66 | [ ] use gemspec defined files, executables, and extension when installing 67 | [ ] rpg-uninstall and dependencies 68 | [ ] build package extensions in a step separate from / prior to install 69 | [ ] xargs -P is non-POSIX. check with: `xargs -P 1 ::.. 75 | [ ] rpg-list assumes -a when name given 76 | [ ] rpg-list -e assumes pattern is egrep re 77 | [ ] rpg-deps -g uses graphviz to graph dependencies 78 | [ ] rpg support command abbreviation (glob libexec/rpg-* if one match run it) 79 | [ ] logging to file of all commands w/ verbose output 80 | [ ] RPGDB///manifest = files installed ( ) 81 | [ ] colors! 82 | [ ] rpg-check verifies an installed package 83 | [ ] search from current directory up for .rpgrc 84 | [ ] ruby-specific config file based on md5 of ruby interpreter path 85 | [ ] detect file conflicts (same file installed) between packages 86 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #/ Usage: ./configure [OPTION]... [VAR=VALUE]... 3 | #/ configure rpg for installation on this system. 4 | #/ 5 | #/ Options 6 | #/ --with-ruby[=RUBY] tie to specific ruby installation. RUBY is 7 | #/ path to ruby executable. 8 | #/ --development configure for local working copy development 9 | #/ 10 | #/ Installation locations 11 | #/ --prefix=PREFIX install files in PREFIX [/usr/local] 12 | #/ --bindir=DIR user executables [PREFIX/bin] 13 | #/ --libexecdir=DIR program executables [PREFIX/libexec] 14 | #/ --sysconfdir=DIR system configuration files [PREFIX/etc] 15 | #/ --datarootdir=DIR data file root [PREFIX/share] 16 | #/ --datadir=DIR data files [DATAROOTDIR] 17 | #/ --mandir=DIR man documentation [DATAROOTDIR/man] 18 | #/ --rpgdir=DIR rpg package and db root [RUBYLIBDIR/rpg] 19 | 20 | warn () { echo "$(basename $0):" "$@" 1>&2; } 21 | longarg () { echo "$1" | sed "s/^$2=//"; } 22 | 23 | # Argument Parsing 24 | # ---------------- 25 | 26 | sourcedir="$(cd "$(dirname $0)" && pwd)" 27 | develmode=false 28 | tiedown=false 29 | 30 | while test $# -gt 0 31 | do 32 | case "$1" in 33 | # Options 34 | --with-ruby) RUBY="ruby" 35 | tiedown=true 36 | specified_ruby= 37 | shift;; 38 | --with-ruby=*) RUBY=$(longarg "$1" --with-ruby) 39 | tiedown=true 40 | specified_ruby="$RUBY" 41 | shift;; 42 | --develop|--development) 43 | prefix="$sourcedir/work" 44 | bindir="$sourcedir" 45 | libexecdir="$sourcedir" 46 | localstatedir="$prefix" 47 | sysconfdir="$sourcedir" 48 | datarootdir="$sourcedir" 49 | datadir="$sourcedir" 50 | mandir="$sourcedir/man" 51 | develmode=true 52 | shift 53 | ;; 54 | 55 | # Installation locations: 56 | --prefix) prefix="$2";shift 2;; 57 | --prefix=*) prefix="$(longarg "$1" --prefix)";shift;; 58 | --bindir) bindir="$2";shift 2;; 59 | --bindir=*) bindir="$(longarg "$1" --bindir)";shift;; 60 | --libexecdir) libexecdir="$2";shift 2;; 61 | --libexecdir=*) libexecdir="$(longarg "$1" --libexecdir)";shift;; 62 | --localstatedir) localstatedir="$2";shift 2;; 63 | --localstatedir=*) localstatedir="$(longarg "$1" --localstatedir)";shift;; 64 | --sysconfdir) sysconfdir="$2";shift 2;; 65 | --sysconfdir=*) sysconfdir="$(longarg "$1" --sysconfdir)";shift;; 66 | --datarootdir) datarootdir="$2";shift 2;; 67 | --datarootdir=*) datarootdir="$(longarg "$1" --datarootdir)";shift;; 68 | --datadir) datadir="$2";shift 2;; 69 | --datadir=*) datadir="$(longarg "$1" --datadir)";shift;; 70 | --mandir) mandir="$2";shift 2;; 71 | --mandir=*) mandir="$(longarg "$1" --mandir)";shift;; 72 | --rpgdir) rpgdir="$2";shift 2;; 73 | --rpgdir=*) rpgdir="$(longarg "$1" --rpgdir)";shift;; 74 | 75 | # Environment variables passed on command line: 76 | [A-Z]*=*) name="${1%%=*}" 77 | value="${1#*=}" 78 | shift 79 | eval "${name}='${value}'";; 80 | 81 | # Bail out with usage otherwise. 82 | *) grep '^#/' < "$0" | cut -c4- 83 | exit 2;; 84 | esac 85 | done 86 | 87 | # Finding Stuff Functions 88 | # ----------------------- 89 | 90 | looking () { printf "looking %s" "$*"; } 91 | ok () { printf " OK\n"; } 92 | missing () { printf " (missing)\n"; } 93 | 94 | found () { 95 | printf " (${1:-found})" 96 | test -n "$2" && printf " $2" 97 | printf "\n" 98 | } 99 | 100 | have () { 101 | for f in "$@" 102 | do 103 | # if a whole command line was given, assume it exists 104 | expr "$f" : '..* ' >/dev/null && { 105 | echo "$f" 106 | return 0 107 | } 108 | 109 | # look for the path with command(1) 110 | _havepath=$(command -v "$f") || { continue; } 111 | echo "$_havepath" 112 | return 0 113 | done 114 | return 1 115 | } 116 | 117 | # Usage: stdutil ... 118 | stdutil () { 119 | looking "for $1"; shift 120 | _varname="$1"; shift 121 | if _path=$(have "$@") 122 | then found "$_path" 123 | recordconfig $_varname "$_path" 124 | else missing 125 | recordconfig $_varname "" 126 | fi 127 | return 0 128 | } 129 | 130 | # Build up lists of config stuff to write to config.sh and config.mk 131 | SHCONFIG= 132 | SHALIAS= 133 | MAKECONFIG= 134 | recordconfig () { 135 | SHCONFIG="$SHCONFIG 136 | $1='$2'" 137 | eval "$SHCONFIG" 138 | SHALIAS="$SHALIAS 139 | alias $1='$2'" 140 | MAKECONFIG="$MAKECONFIG 141 | $1 = $2" 142 | } 143 | 144 | # Usage: rbconfig [] 145 | # Retrieve a rbconfig value for the currently active ruby interpreter. If 146 | # an error occurs obtaining the value, return . 147 | rbconfig () { 148 | ${RUBY:-ruby} -r rbconfig -e "puts RbConfig::CONFIG['$1']" 2>/dev/null || 149 | echo "$2" 150 | return 0 151 | } 152 | 153 | UNAME=$(uname 2>/dev/null) 154 | HOST=$(hostname 2>/dev/null) 155 | TIME=$(date 2>/dev/null) 156 | echo "building for ${UNAME:-unknown} on ${HOST:-localhost} at ${TIME:-a time unknown}" 157 | 158 | looking "for /bin/sh" 159 | SH=$(have /bin/sh) && { 160 | if expr "$("$SH" --version 2>/dev/null)" : '.*bash' >/dev/null 161 | then 162 | found '' "oh ick, it looks like bash" 163 | else 164 | found 165 | fi 166 | } || missing 167 | recordconfig SHELL "$SH" 168 | 169 | stdutil bash BASH /bin/bash /usr/bin/bash bash 170 | stdutil dash DASH /bin/dash /usr/bin/dash dash 171 | stdutil sed SED /bin/sed /usr/bin/sed sed gsed 172 | stdutil perl PERL perl 173 | if $tiedown && test -n "$specified_ruby" 174 | then 175 | stdutil ruby RUBY "$RUBY" 176 | else 177 | stdutil ruby RUBY "$RUBY" ruby ruby18 178 | fi 179 | stdutil install INSTALL "$(rbconfig INSTALL)" /usr/bin/install install ginstall 180 | stdutil egrep EGREP "$(rbconfig EGREP)" egrep 181 | 182 | stdutil ln LN /bin/ln ln gln 183 | stdutil sort SORT /bin/sort /usr/bin/sort sort gsort 184 | stdutil tr TR /usr/bin/tr tr gtr 185 | stdutil cut CUT /usr/bin/cut cut gcut 186 | stdutil mktemp MKTEMP /bin/mktemp mktemp gmktemp 187 | stdutil readlink READLINK /bin/readlink readlink greadlink 188 | stdutil diff DIFF diff 189 | stdutil patch PATCH patch 190 | stdutil tar TAR tar gtar 191 | stdutil curl CURL curl 192 | stdutil gem GEM "$(rbconfig bindir)/gem" gem 193 | stdutil schocco SHOCCO shocco 194 | stdutil rocco ROCCO rocco 195 | stdutil ronn RONN ronn 196 | 197 | recordconfig INSTALL_PROGRAM "$INSTALL" 198 | 199 | set -e 200 | 201 | # Load configuration 202 | eval "$SHCONFIG" 203 | 204 | test -z "$RUBY" && { 205 | if $tiedown && test -n "$specified_ruby" 206 | then 207 | warn "fatal: specified ruby ($specified_ruby) not found." 208 | warn "please fix: --with-ruby=/path/to/ruby." 209 | else 210 | warn "fatal: no ruby found on PATH." 211 | warn "try: $0 --with-ruby=/path/to/ruby" 212 | fi 213 | exit 3 214 | } 215 | 216 | test -z "$CURL" && { 217 | warn "fatal: no curl(1) found on PATH. install it and try again." 218 | exit 3 219 | } 220 | 221 | test -z "$TAR" && { 222 | warn "fatal: no tar(1) found on PATH. install it and try again." 223 | exit 3 224 | } 225 | 226 | test -z "$DIFF" && 227 | warn "warn: no diff(1) found on PATH. some features will be disabled." 228 | 229 | RUBYFRAMEWORK=false 230 | if test "$UNAME" = "Darwin" 231 | then 232 | RUBYFRAMEWORKPATH="/System/Library/Frameworks/Ruby.framework" 233 | printf "checking if ruby is system framework" 234 | if expr -- "$(rbconfig prefix)" : "$RUBYFRAMEWORKPATH" >/dev/null 235 | then printf " (yep)\n" 236 | RUBYFRAMEWORK=true 237 | else 238 | printf " (nope)\n" 239 | fi 240 | fi 241 | 242 | echo "okay, looks like you have everything we need. generating config files." 243 | 244 | # Try to pick a default configuration 245 | 246 | if $tiedown && ! $RUBYFRAMEWORK 247 | then 248 | : ${prefix:=$(rbconfig prefix "/usr/local")} 249 | : ${exec_prefix:=$(rbconfig exec_prefix "$prefix")} 250 | : ${bindir:=$(rbconfig bindir "$exec_prefix/bin")} 251 | : ${libexecdir:=$(rbconfig libexecdir "$exec_prefix/libexec")} 252 | : ${localstatedir:=$(rbconfig localstatedir "$prefix/var")} 253 | : ${sysconfdir:=$(rbconfig sysconfdir "$prefix/etc")} 254 | : ${datarootdir:=$(rbconfig datarootdir "$prefix/share")} 255 | : ${datadir:=$(rbconfig datadir "$datarootdir")} 256 | : ${mandir:=$(rbconfig mandir "$datadir/man")} 257 | : ${docdir:=$(rbconfig docdir "$datadir/doc")} 258 | : ${rpgdir:=$(rbconfig rubylibdir "/var/lib")/rpg} 259 | else 260 | : ${prefix:="/usr/local"} 261 | : ${exec_prefix:="$prefix"} 262 | : ${bindir:="$exec_prefix/bin"} 263 | : ${libexecdir:="$exec_prefix/libexec"} 264 | : ${localstatedir:="$prefix/var"} 265 | : ${sysconfdir:="$prefix/etc"} 266 | : ${datarootdir:="$prefix/share"} 267 | : ${datadir:="$datarootdir"} 268 | : ${mandir:="$datadir/man"} 269 | : ${docdir:="$datadir/doc"} 270 | fi 271 | 272 | recordconfig prefix "$prefix" 273 | recordconfig exec_prefix "$exec_prefix" 274 | recordconfig bindir "$bindir" 275 | recordconfig libexecdir "$libexecdir" 276 | recordconfig localstatedir "$localstatedir" 277 | recordconfig sysconfdir "$sysconfdir" 278 | recordconfig datarootdir "$datarootdir" 279 | recordconfig datadir "$datadir" 280 | recordconfig mandir "$mandir" 281 | recordconfig rpgdir "$rpgdir" 282 | recordconfig develmode "$develmode" 283 | 284 | echo writing config.mk... 285 | cat < config.mk 286 | $MAKECONFIG 287 | EOF 288 | 289 | echo writing config.sh... 290 | cat < config.sh 291 | # utility locations 292 | $SHCONFIG 293 | EOF 294 | 295 | $develmode && { 296 | echo "RPGSYSCONF=/dev/nothing" >> config.sh 297 | echo "RPGUSERCONF=/dev/nothing" >> config.sh 298 | echo "ready for development. run \`make' or \`make auto' to build." 299 | echo "you may also want to put . on PATH; in your current shell:" 300 | echo " PATH=$(pwd):\$PATH" 301 | exit 0 302 | } 303 | 304 | echo "rpg configured to install at the following locations:" 305 | printf " bindir: %-20s (%s)\n" "$bindir" "main rpg user executable" 306 | printf " libexecdir: %-20s (%s)\n" "$libexecdir" "rpg program executables" 307 | printf " sysconfdir: %-20s (%s)\n" "$sysconfdir" "rpgrc config file" 308 | printf " localstatedir: %-20s (%s)\n" "$localstatedir" "package db, index, and gem cache" 309 | printf " mandir: %-20s (%s)\n" "$mandir" "unix manpages" 310 | printf " rpgdir: %-20s (%s)\n" "$rpgdir" "rpg files" 311 | echo "run \`make' to build and then \`make install' to install." 312 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | .POSIX: 2 | 3 | include ../config.mk 4 | 5 | MANPAGES = rpg-solve.1 6 | MANPAGESHTML = rpg-solve.1.html 7 | RONN = ronn --manual="RPG MANUAL" --organization="Ryan Tomayko" 8 | 9 | all:: man 10 | 11 | man: $(MANPAGES) $(MANPAGESHTML) 12 | 13 | .SUFFIXES: .ronn .html 14 | 15 | .ronn: 16 | printf "%13s %-30s" "[ROFF]" "$@" 17 | ronn $< > $@+ 18 | mv $@+ $@ 19 | printf " OK\n" 20 | 21 | .ronn.html: 22 | printf "%13s %-30s" "[HTML]" "$@" 23 | ronn -5 $< > $@+ 24 | mv $@+ $@ 25 | printf " OK\n" 26 | 27 | clean: 28 | rm -vf $(MANPAGES) 29 | rm -vf $(MANPAGESHTML) 30 | 31 | .SILENT: 32 | 33 | .PHONY: clean 34 | -------------------------------------------------------------------------------- /doc/depsolver.md: -------------------------------------------------------------------------------- 1 | Dependency Solving 2 | ================== 3 | 4 | ## Stuff 5 | 6 | #### Package Lists 7 | 8 | A package list is a simple text file where each line specifies a package 9 | matching rule. It looks like this: 10 | 11 | 12 | 13 | The `` is the package name, `` is the package version, and 14 | `` is one of: `<`, `<=`, `=`, `>=`, or `>`. The `` field 15 | specifies where the requirement originated. This can be a package name (in case 16 | of dependencies), `~user` (in case of user install), or `-` to denote the 17 | requirement has no source or the source is unimportant. 18 | 19 | Example: 20 | 21 | @user rails > 2.2 22 | @user sinatra >= 0 23 | rails activesupport = 2.2 24 | rails activerecord = 2.2 25 | sinatra rack >= 1.0 26 | rails rack >= 1.0.1 27 | 28 | #### Package Indexes 29 | 30 | A package index is a simple text file where each line specifies a concrete 31 | package name and version. It looks like this: 32 | 33 | 34 | 35 | Package indexes are usually sorted by `` and then reverse by 36 | ``. This allows efficient lookups for many packages in a single pass 37 | over a file. 38 | 39 | ## How Dependencies and Conflicts are Solved 40 | 41 | ### 1. Build Master Package List 42 | 43 | Build a standard format package list from all packages provided on the command 44 | line with `rpg-package-list(1)`. It looks like this, remember: 45 | 46 | @user 47 | 48 | This is the _master package list_. 49 | 50 | ### 1.5 Build Installed Package Index and Dependency Package List 51 | 52 | Build a package index for all packages currently installed on the system with, 53 | e.g., `rpg-package-index(1)`. This is the _installed package index_. 54 | 55 | Build a package list from all dependency lists of all installed packages 56 | *except* those that are included in the master package list. This is called the 57 | _existing dependency package list_ 58 | 59 | 60 | 61 | It's important that lines whose `` is a package existing in the master 62 | package list are excluded. Otherwise, the dependency rules for already installed 63 | packages will constrain the resolver. 64 | 65 | ### 2. Resolve Package Versions 66 | 67 | Concatenate the master package list and the installed package list through `rpg-solve(1)` to find the best versions of 68 | each package based on its requirements. The output is a concrete package index 69 | called the *solved package index*: 70 | 71 | rails 2.3.1 72 | sinatra 0.9.6 73 | 74 | The package index files passed into `rpg-solve` are as follows: 75 | 76 | 1. The *solved package index* 77 | 2. The *installed package index* 78 | 3. The *release package index* 79 | 80 | If `rpg-install` is in upgrade mode, the *installed package index* 81 | is not used. This causes the all package rules to be resolved against 82 | the *release package index*, resulting in all packages included in the 83 | *master package list* being upgraded to the most recent compatible 84 | version. 85 | 86 | ### 3. Register Packages 87 | 88 | Fetch each package in the *solved package index* with `rpg-fetch(1)` and 89 | register the package version in the package database with `rpg-register(1)`. 90 | 91 | __NOTE:__ `rpg-register(1)` just loads the packages spec data into the database. 92 | It doesn't install anything. 93 | 94 | ### 4. Resolve Package Dependencies 95 | 96 | For each package resolved in step 2, add that package's dependencies to the 97 | master package list. Now maybe the master package list looks like this: 98 | 99 | @user rails > 2.2 100 | @user sinatra >= 0 101 | sinatra rack > 1.0 102 | rails rack >= 1.0.1 103 | rails activerecord = 2.2 104 | 105 | Notice how there can be multiple entries for a single package in the package 106 | list. `rpg-solve(1)` returns the best match for a package given all the 107 | requirements. 108 | 109 | ### 5. GOTO 2 110 | 111 | If the master package list at step 4 has changed from the master package list at 112 | step 2, go back and continue from step 2: resolve the new master package list 113 | down to a concrete package index, resolve dependencies, and come back here. 114 | 115 | If the master package did not change on this pass, continue on to the next step. 116 | 117 | ### 6. Conflict Resolution 118 | 119 | A number of exceptional conditions can arise at this point: 120 | 121 | 1. A package specified by the user or by dependency does not exist in any 122 | package index. This could be due to a stale index or because a package 123 | name is wrong. 124 | 125 | 2. A package version meeting all of the criteria specified by the master 126 | package list and the existing dependency package list could not be found. 127 | This could be due to conflicting dependency specifications in multiple 128 | packages. 129 | 130 | In either of the cases mentioned above, the `rpg-solve(1)` program will output 131 | a record whose version is "-". The front-end is then responsible for presenting 132 | this list to the user in some way. The user should be able to choose what should 133 | happen. 134 | 135 | 1. Change the version of the package 136 | 2. Force the package to be installed anyway (what package? what version?) 137 | 3. Abort the installation 138 | -------------------------------------------------------------------------------- /doc/rpg-solve.1.ronn: -------------------------------------------------------------------------------- 1 | rpg-solve(1) -- package version solver 2 | ====================================== 3 | 4 | ## SYNOPSIS 5 | 6 | `rpg solve` []... 7 | 8 | ## DESCRIPTION 9 | 10 | The `rpg-solve` program finds versions of packages matching specifications on 11 | standard input and writes concrete package versions on standard output. 12 | Available package versions are determined by one or more sorted files. 13 | When a package cannot be located that matches all version specifiers given on 14 | standard input, a line of output is written with the package version: '-'. 15 | 16 | The `rpg-install(1)`, `rpg-upgrade(1)`, and `rpg-fetch(1)` programs use 17 | `rpg-solve` to determine optimal package versions for each of their respective 18 | operations. 19 | 20 | ## STDIN 21 | 22 | The standard input is a *package list*. A package list is a simple text stream 23 | where each line specifies a package matching rule in the following format: 24 | 25 | 26 | 27 | Fields are separated by a single space character and each line must be 28 | terminated with a new line (`\n`) character. 29 | 30 | * ``: 31 | The package name. This can be any combination of alphanumerics, the 32 | underscore, or dash. 33 | 34 | * ``: 35 | A package version comparator specifying the basic matching criteria for the 36 | ``. Any of `<`, `<=`, `=`, `>=`, `>`, or `~>` are allowed. 37 | 38 | * ``: 39 | The package version. 40 | 41 | The package list may contain multiple rules for a single package. Only those 42 | versions matching all rules are considered *matching* and written to standard 43 | output. 44 | 45 | ## INPUT FILES 46 | 47 | The files given as operands to the `rpg-solve` program are plain text 48 | *package index* files with the following format: 49 | 50 | 51 | 52 | Fields are separated by a single space character and each line must be 53 | terminated with a new line (`\n`) character. Fields have the same meaning as 54 | defined above in the *STDIN* section. 55 | 56 | Package index files must be sorted in ascending alphabetical order by 57 | `` then descending natural order by ``. When the index files 58 | are not sorted properly, the resulting behavior from `rpg-solve` is undefined. 59 | 60 | ## OUTPUT FORMAT 61 | 62 | `rpg-solve` writes a *package index* on standard output. 63 | 64 | ## EXAMPLES 65 | 66 | The following is in a file named `some-index`: 67 | 68 | rack 1.2 69 | rack 1.1 70 | rack 1.0 71 | rack 0.9.7 72 | rails 2.3.1 73 | rails 2.3.0 74 | rails 2.1.0 75 | sinatra 0.9.6 76 | sinatra 0.9.4 77 | sinatra 0.9.3 78 | 79 | Pipe a package list into `rpg-solve` using `some-index` for lookups: 80 | 81 | $ cat < 2.2 83 | sinatra >= 0.9.6 84 | rack >= 1.0 85 | rack >= 1.0.1 86 | mustache >= 3.0 87 | EOF 88 | 89 | The `rpg-solve` standard output is: 90 | 91 | mustache - 92 | rack 1.2 93 | rack 1.1 94 | rails 2.3.1 95 | rails 2.3.0 96 | sinatra 0.9.6 97 | 98 | Note that all matching versions are written to standard output. The list can be 99 | narrowed down to only the best match for each package with `sort(1)`: 100 | 101 | $ cat <1.2' sinatra 0.9.6 | rpg-solve 116 | 117 | ## SEE ALSO 118 | 119 | `rpg-package-list(1)`, `rpg-prepare(1)`, `rpg-install(1)` 120 | -------------------------------------------------------------------------------- /misc/rpg-completion.bash: -------------------------------------------------------------------------------- 1 | # rpg bash completion 2 | 3 | # Turn on extended globbing 4 | shopt -s extglob 5 | 6 | _rpg() { 7 | local cur prev stuff word comm i 8 | 9 | COMPREPLY=() 10 | cur=${COMP_WORDS[COMP_CWORD]} 11 | prev=${COMP_WORDS[COMP_CWORD-1]} 12 | if test "$COMP_CWORD" -gt 2 13 | then prev2=${COMP_WORDS[COMP_CWORD-2]} 14 | else prev2= 15 | fi 16 | 17 | # Run over args and set comm to either "rpg" or the sub-command name. 18 | i=0 19 | comm=rpg 20 | while [ $i -lt $COMP_CWORD ] 21 | do 22 | case "${COMP_WORDS[$i]}" in 23 | rpg) comm=rpg;; 24 | -c) i=$(( i + 1 ));; 25 | [a-z]*) test "$comm" = rpg && comm="${COMP_WORDS[$i]}" 26 | break;; 27 | esac 28 | i=$(( i + 1 )) 29 | done 30 | 31 | case "$comm" in 32 | 33 | # The main rpg command 34 | rpg) 35 | if test "$prev" = '-c' 36 | then : # complete filename 37 | else 38 | case "$cur" in 39 | -*) COMPREPLY=( $(compgen -W "-c -v -q -x --help" -- "$cur") );; 40 | *) _rpg_complete commands "$cur";; 41 | esac 42 | fi 43 | ;; 44 | 45 | # The install and prepare commands 46 | install|prepare) 47 | case "$cur" in 48 | # an option argument 49 | -*) 50 | case "$prev" in 51 | -f|-s|install|prepare) 52 | COMPREPLY=( $(compgen -W "-f -s --help" -- "$cur") );; 53 | *) COMPREPLY=( $(compgen -W "-v" -- "$cur") );; 54 | esac 55 | ;; 56 | 57 | # a package name 58 | [A-Za-z]*) 59 | case "$prev" in 60 | -s) ;; # session names 61 | -v) # package versions 62 | _rpg_complete versions "$prev2" "$cur";; 63 | *) _rpg_complete available "$cur";; 64 | esac 65 | ;; 66 | 67 | # a package version 68 | [0-9]*) 69 | case "$prev" in 70 | -s) ;; 71 | -v) _rpg_complete versions "$prev2" "$cur";; 72 | *) _rpg_complete versions "$prev" "$cur";; 73 | esac 74 | ;; 75 | 76 | # no argument or something really crazy 77 | *) 78 | case "$prev" in 79 | -s) ;; # session name 80 | -v) _rpg_complete versions "$prev2" "$cur";; 81 | *) _rpg_complete available "$cur";; 82 | esac;; 83 | 84 | esac 85 | ;; 86 | 87 | # The status command 88 | status) 89 | _rpg_complete available "$cur";; 90 | 91 | package-install) 92 | _rpg_complete available "$cur";; 93 | 94 | resolve) 95 | _rpg_complete available "$cur";; 96 | 97 | uninstall|list|upgrade|dependencies|manifest) 98 | _rpg_complete installed "$cur";; 99 | 100 | esac 101 | } 102 | 103 | _rpg_complete () { 104 | local cur stuff 105 | COMPREPLY=() 106 | cur="${COMP_WORDS[COMP_CWORD]}" 107 | stuff="$(rpg complete "$@")" 108 | COMPREPLY=( $(compgen -W "$stuff" -- "$cur") ) 109 | } 110 | 111 | complete -F _rpg rpg 112 | -------------------------------------------------------------------------------- /munge.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # Usage: munge SYMBOL FILE 3 | # Super simple file munger. Reads stdin and replaces SYMBOL with the 4 | # contents of FILE before writing it to stdout. 5 | 6 | data = STDIN.read 7 | data.gsub!(ARGV[0]) { File.read(ARGV[1]) } 8 | STDOUT.write data 9 | -------------------------------------------------------------------------------- /rpg-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | . rpg-sh-setup 4 | 5 | [ "$*" ] || set -- '--help' 6 | ARGV="$@" 7 | USAGE '${PROGNAME} 8 | Build native extensions for a package. 9 | 10 | The paths to newly built libraries are written on standard output. Exits with 11 | success if the build succeeds, failure otherwise.' 12 | 13 | path="$(cd "$1" && pwd)" 14 | 15 | test -d "$path/ext" || 16 | exit 0 17 | 18 | find "$path/ext" -name "extconf.rb" | 19 | while read file 20 | do 21 | heed "$(basename "$path") $(basename $(dirname "$file"))" 22 | cd "$(dirname "$file")" 23 | if (ruby extconf.rb && 24 | { make clean || true; } && 25 | make) 1> build.log 2>&1 26 | then 27 | $RPGSHOWBUILD && cat build.log 1>&2 28 | find "$(dirname "$file")" -name "*.$(ruby_dlext)" 29 | else 30 | status=$? 31 | cat build.log 1>&2 32 | exit $status 33 | fi 34 | done 35 | -------------------------------------------------------------------------------- /rpg-complete.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | . rpg-sh-setup 4 | 5 | ARGV="$@" 6 | USAGE '${PROGNAME} 7 | Command line completion for rpg. 8 | 9 | The must be one of: 10 | commands List rpg commands matching 11 | available List available packages matching 12 | installed List installed packages matching ' 13 | 14 | commands () { 15 | for p in "$libexecdir"/rpg-$1* 16 | do test -x "$p" && echo ${p##*/rpg-} 17 | done 18 | } 19 | 20 | installed () { 21 | rpg-package-index $1\* | cut -d ' ' -f 1 22 | } 23 | 24 | available () { 25 | grep -e "^$1[^ ]* " < "$RPGINDEX/release-recent" | 26 | cut -d ' ' -f 1 | 27 | head -1000 28 | } 29 | 30 | versions () { 31 | grep -e "^$1 " < "$RPGINDEX/release" | 32 | cut -d ' ' -f 2 33 | } 34 | 35 | "$@" 36 | -------------------------------------------------------------------------------- /rpg-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # The `rpg-config` program dumps configuration variables to standard output 3 | # in a format suitable for sourcing into a shell or opens an editor on rpg 4 | # configuration files. 5 | set -e 6 | . rpg-sh-setup 7 | 8 | ARGV="$@" 9 | USAGE '${PROGNAME} 10 | ${PROGNAME} -u 11 | ${PROGNAME} -s 12 | Show or edit rpg configuration. 13 | 14 | Options 15 | -u Edit the user configuration file 16 | -s Edit the system configuration file 17 | ' 18 | 19 | # With `-u` or `-s`, open an editor on the configuration file. If the file 20 | # doesn't exist, create it with the program's current output but comment out 21 | # all variable assignment lines. 22 | editfile= 23 | while getopts su opt 24 | do 25 | case $opt in 26 | s) editfile="$RPGSYSCONF";; 27 | u) editfile="$RPGUSERCONF";; 28 | ?) helpthem;exit 2;; 29 | esac 30 | done 31 | shift $(( $OPTIND - 1 )) 32 | 33 | test -n "$editfile" && { 34 | test -f "$editfile" || { 35 | { 36 | echo "# $(date)" 37 | echo "# This file was generated by "$PROGNAME" on" \ 38 | "behalf of $(id -un)" 39 | echo "# Remove comment characters and edit values to" \ 40 | "change the default config" 41 | echo 42 | "$0" 43 | } |sed 's/^\([^#]\)/# \1/' >"$editfile" 44 | } 45 | ${EDITOR:-vi} "$editfile" 46 | exit $? 47 | } 48 | 49 | # Dump config values with some comments so we can use it to generate 50 | # config files in edit mode. 51 | cat < [] 7 | ${PROGNAME} -a 8 | Show package dependency information. 9 | 10 | Options 11 | -a Write dependency information for all installed packages 12 | -r List dependencies recursively 13 | -t List dependencies recursively in a tree 14 | -p Include name in output (default with -a)' 15 | 16 | showall=false;recursive=false;tree=false;prefix=false 17 | while getopts arpt opt 18 | do 19 | case $opt in 20 | a) showall=true;; 21 | r) recursive=true;; 22 | p) prefix=true;; 23 | t) tree=true;; 24 | ?) helpthem 25 | exit 2;; 26 | esac 27 | done 28 | shift $(( $OPTIND - 1 )) 29 | 30 | # With -t and no package argument, show a tree of dependencies for all packages, 31 | # rooted at packages no other package depends. 32 | $tree && test -z "$*" && { 33 | rpg leaves | xargs -n 1 -- "$0" $ARGV 34 | exit 35 | } 36 | 37 | # With the `-a` argument, write all dependencies for all packages in the 38 | # following format: 39 | # 40 | # 41 | # 42 | # Where `` is the name of the package that has a dependency 43 | # on ``. The `` may be any valid version expression: 44 | # `<`, `<=`, `=`, `>=`, `>`, or `~>`. 45 | $showall && { 46 | test "$*" && { helpthem; exit 2; } 47 | grep '^runtime ' "$RPGDB"/*/active/dependencies 2>/dev/null | 48 | sed -e 's|^.*/\(.*\)/active/dependencies:runtime |\1 |' 49 | exit 0 50 | } 51 | 52 | # Find the package and write its dependencies in this format: 53 | # 54 | # 55 | # 56 | # Exit with failure if the package or package version is not found in the 57 | # database. 58 | package="$1" 59 | version="${2:-active}" 60 | packagedir="$RPGDB/$package" 61 | 62 | test -d "$packagedir" || { 63 | warn "package not found: $package" 64 | exit 1 65 | } 66 | 67 | test -d "$packagedir/$version" || { 68 | warn "package version not found: $package $version" 69 | exit 1 70 | } 71 | 72 | sed -n 's|^runtime ||p' <"$packagedir/$version/dependencies" | 73 | 74 | if $tree 75 | then 76 | while read pack spec vers 77 | do 78 | echo "$pack $spec $vers" 79 | "$0" -r -t $pack |sed ' 80 | s/^/|-- / 81 | s/-- |/ |/ 82 | ' 83 | done 84 | else 85 | if $recursive 86 | then recurse="$0 -r" 87 | $prefix && recurse="$recurse -p" 88 | else recurse=true 89 | fi 90 | 91 | while read pack spec vers 92 | do 93 | output="$pack $spec $vers" 94 | $prefix && output="$package $output" 95 | echo "$output" 96 | $recurse "$pack" 97 | done | 98 | sort -u 99 | fi 100 | -------------------------------------------------------------------------------- /rpg-diff.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | . rpg-sh-setup 4 | 5 | [ "$*" ] || set -- --help; ARGV="$@" 6 | USAGE '${PROGNAME} 7 | ${PROGNAME} 8 | ${PROGNAME} 9 | Show diff between package versions. With no , show diff between 10 | most recent available version and installed version. With one , show diff 11 | between currently installed version and . With and , show 12 | diff between and .' 13 | 14 | package="$1" 15 | ver1="$2" 16 | ver2="$3" 17 | 18 | dir1=$(rpg-unpack -nP "$package" "$ver1") 19 | dir2=$(rpg-unpack -nP "$package" "$ver2") 20 | 21 | diff -ruN "$dir1" "$dir2" 22 | -------------------------------------------------------------------------------- /rpg-env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | . rpg-sh-setup 4 | 5 | ARGV="$@" 6 | USAGE '${PROGNAME} [name=value ...] [ [ ...]] 7 | Execute command under rpg environment. 8 | 9 | The RPGLIB dir is placed on RUBYLIB and exported, and RPGBIN is add to 10 | added PATH before executing command. 11 | 12 | If no is specified, ${PROGNAME} writes the name and values 13 | of variables in the environment to stdout with one = per line.' 14 | 15 | # Put RPGBIN on PATH if it isn't there already. 16 | if ! expr "$PATH" : ".*$RPGBIN" >/dev/null && 17 | ! echo "$PATH" | tr ':' '\n' | grep -q -e "^$RPGBIN$" >/dev/null 18 | then PATH="$RPGBIN:$PATH" 19 | fi 20 | 21 | # Put RUBYLIB on PATH if it isn't there already. 22 | if test -z "$RUBYLIB" 23 | then RUBYLIB="$RPGLIB:$RPGLIB/$RUBYARCH" 24 | elif ! echo "$RUBYLIB" | tr ':' '\n' | grep -e "^$RPGLIB$" >/dev/null 25 | then RUBYLIB="$RPGLIB:$RPGLIB/$RUBYARCH:$RUBYLIB" 26 | fi 27 | 28 | export RUBYLIB PATH 29 | 30 | # Leave the rest of the work to env(1). 31 | exec /usr/bin/env "$@" 32 | -------------------------------------------------------------------------------- /rpg-fetch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | . rpg-sh-setup 4 | 5 | [ "$*" ] || set -- "--help"; ARGV="$@" 6 | USAGE '${PROGNAME} [] 7 | Fetch a package into the cache, writing the filename to stdout. 8 | 9 | No network operations are performed when a package exists in the cache 10 | that satisfies the version spec.' 11 | 12 | package="$1" 13 | version="${2:->=0}" 14 | 15 | # Find the best (most recent) version of the package matching the 16 | # supplied version spec. Bail out with a failure status if nothing is 17 | # found satisfying the requested version. 18 | # 19 | # When a concrete version is given (e.g. `=0.5.3` or `1.2`), the version is 20 | # not resolved against the index. 21 | if expr "$version" : '.*[><~]' >/dev/null 22 | then bestver=$(rpg-resolve -n 1 "$package" "$version") || { 23 | warn "$package $version not found." 24 | exit 1 25 | } 26 | else bestver="${version#=}" 27 | fi 28 | 29 | gemfile="${package}-${bestver}.gem" 30 | if test -f "$RPGCACHE/$gemfile" 31 | then notice "$package $version [cached: $bestver]" 32 | else 33 | # We're going to need to pull the gem off the server. 34 | mkdir -p "$RPGCACHE" 35 | cd "$RPGCACHE" 36 | 37 | if test "$bestver" != "$version" 38 | then heed "$package $version [resolved: $bestver]" 39 | else heed "$package $version" 40 | fi 41 | 42 | # Grab the gem with `curl(1)` and write to a temporary file just 43 | # in case something goes wrong during transfer. 44 | if ! curl --fail -s -L "$RPGGEMURL/${gemfile}" > "${gemfile}+" 45 | then warn "$package $version not found." 46 | exit 1 47 | fi 48 | 49 | mv "${gemfile}+" "$gemfile" 50 | fi 51 | 52 | echo "$RPGCACHE/$gemfile" 53 | -------------------------------------------------------------------------------- /rpg-fsck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | . rpg-sh-setup 4 | 5 | ARGV="$@" 6 | USAGE '${PROGNAME} 7 | Verify integrity of the package db and release index.' 8 | 9 | checking () { 10 | printf "checking %-35s" "$* ..." 11 | } 12 | 13 | diagnose () { 14 | if problems=$(command "$@" 2>&1) 15 | then ok 16 | else fail "$problems" 17 | fi 18 | } 19 | 20 | 21 | ok () { 22 | printf " OK" 23 | if test "$*" 24 | then printf " (%s)" "$*" 25 | fi 26 | printf "\n" 27 | } 28 | 29 | fail () { printf " FAIL\n[%s]\n" "$*"; } 30 | 31 | checking "recent index readability" 32 | diagnose test -r "$RPGINDEX/release-recent" 33 | 34 | checking "index readability" 35 | diagnose test -r "$RPGINDEX/release" 36 | 37 | checking "recent index joinability" 38 | diagnose sort -c -b -k 1,1 "$RPGINDEX/release-recent" 39 | 40 | checking "index joinability" 41 | diagnose sh -c " 42 | cut -f 1 -d ' ' < '$RPGINDEX/release' | 43 | sort -c -b -k 1,1 44 | " 45 | 46 | checking "recent index data" 47 | if lines=$(wc -l "$RPGINDEX/release-recent" | sed 's/[^0-9]//g') && 48 | test "$lines" -gt 0 49 | then ok "$lines packages" 50 | else fail "${lines:-no} packages" 51 | fi 52 | 53 | checking "index data" 54 | if lines=$(wc -l "$RPGINDEX/release" | sed 's/[^0-9]//g') && 55 | test "$lines" -gt 0 56 | then ok "$lines package versions" 57 | else fail "${lines:-no} packages" 58 | fi 59 | -------------------------------------------------------------------------------- /rpg-help.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | . rpg-sh-setup 4 | 5 | ARGV="$@" 6 | USAGE '${PROGNAME} [] 7 | Show help and usage for ' 8 | 9 | case "$1" in 10 | help) helpthem;; 11 | rpg) exec rpg --help;; 12 | [a-z]*) exec rpg "$1" --help;; 13 | *) exec rpg --help;; 14 | esac 15 | -------------------------------------------------------------------------------- /rpg-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # TODO Documentation and cleanup 3 | set -e 4 | . rpg-sh-setup 5 | 6 | [ "$*" ] || set -- '--help'; ARGV="$@" 7 | USAGE '${PROGNAME} [-f] [[-v] ] ... 8 | ${PROGNAME} [-f] [/]... 9 | ${PROGNAME} [-f] -s 10 | Install packages into rpg environment. 11 | 12 | Options 13 | -f Force package installation even if already installed 14 | -s Install from a session created with rpg-prepare' 15 | 16 | session=default 17 | force=false 18 | while getopts fs: opt 19 | do case $opt in 20 | s) session="$OPTARG";; 21 | f) force=true;; 22 | ?) helpthem;; 23 | esac 24 | done 25 | shift $(( $OPTIND - 1 )) 26 | 27 | sessiondir="$RPGSESSION/$session" 28 | packlist="$sessiondir/package-list" 29 | delta="$sessiondir/delta" 30 | solved="$sessiondir/solved" 31 | 32 | test "$session" = "default" -a -d "$sessiondir" && { 33 | notice "rm'ing crusty session dir: $sessiondir" 34 | rm -rf "$sessiondir" 35 | } 36 | 37 | if $force 38 | then packageinstallargs=-f 39 | installfrom="$solved" 40 | else packageinstallargs= 41 | installfrom="$delta" 42 | fi 43 | 44 | test -d "$sessiondir" || { 45 | trap "rm -rf '$sessiondir'" 0 46 | rpg-prepare -i -s "$session" "$@" 47 | } 48 | 49 | numpacks=$(grep -c . <"$installfrom") 50 | if $force 51 | then heed "installing $numpacks packages (forced)" 52 | else heed "installing $numpacks packages" 53 | fi 54 | 55 | <"$installfrom" xargs -n 2 rpg-package-install $packageinstallargs 56 | 57 | heed "installation complete" 58 | 59 | true 60 | -------------------------------------------------------------------------------- /rpg-leaves.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | . rpg-sh-setup 4 | 5 | ARGV="$@" 6 | USAGE '${PROGNAME} 7 | List installed packages that no other package depends on.' 8 | 9 | : ${TMPDIR:=/tmp} 10 | 11 | index=$(mktemp -t $PROGNAME) 12 | trap "rm -f $index" 0 13 | 14 | rpg-package-index | 15 | cut -d ' ' -f 1 | 16 | sort > "$index" 17 | 18 | rpg-dependencies -a | 19 | cut -d ' ' -f 2 | 20 | sort | 21 | comm -31 - "$index" 22 | -------------------------------------------------------------------------------- /rpg-list.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # The `rpg-list` program compares installed packages to packages available 3 | # in the remote repository. It's useful for determining the versions of 4 | # packages and how they relate to same named packages available in the 5 | # repository. 6 | # 7 | # This is somewhere between a plumbing and porcelain command. It's useful 8 | # for building other programs and provides options for generating easily 9 | # parseable output. However, it's also useful to humans and the default 10 | # output is optimize for human consumption. 11 | set -e 12 | . rpg-sh-setup 13 | 14 | ARGV="$@" 15 | USAGE '${PROGNAME} [-u] [-a] [-p|-s] [...] 16 | List installed and/or available packages. 17 | 18 | Passing one or more s filters the list to matching packages. 19 | 20 | Options 21 | -a Include packages available but not installed 22 | -l Long list format: show package status and version fields 23 | -p Generate easily parseable output 24 | -u Sync the package index with remote repository first' 25 | 26 | sync=false 27 | parsey=false 28 | long=false 29 | all=false 30 | joiner= 31 | while getopts alpus opt 32 | do 33 | case $opt in 34 | a) all=true 35 | joiner="-a2";; 36 | l) long=true;; 37 | u) sync=true;; 38 | p) long=true; 39 | parsey=true;; 40 | ?) helpthem; 41 | exit 2;; 42 | esac 43 | done 44 | shift $(( $OPTIND - 1 )) 45 | 46 | # Sync the package index. Force the sync right now if we were given 47 | # the `-u` arg; otherwise, maybe update it based on the configured stale 48 | # time. 49 | if $sync 50 | then rpg-sync 51 | else rpg-sync -s 52 | fi 53 | 54 | # Parsey Mode 55 | # ----------- 56 | 57 | # The `-p` argument causes the output to be varied slightly. These variables 58 | # control how output lines are formatted and what symbols they use. In 59 | # parsey mode, simple alpha characters are used since those are a bit easier 60 | # to `grep` / `sed` without escaping. 61 | if $parsey 62 | then st_outdate="o" 63 | st_up2date="u" 64 | st_missing="x" 65 | st_format="%s %s %s %s\n" 66 | else 67 | st_up2date=" " 68 | st_outdate="*" 69 | st_missing="X" 70 | st_format="%1s %-35s %-12s %-12s\n" 71 | fi 72 | 73 | # Package Selection / Glob Filter 74 | # ------------------------------- 75 | 76 | # Default to matching all installed packages -- or all available packages 77 | # when `-a` was given -- if no ``s were given. 78 | [ "$*" ] || set -- '*' 79 | 80 | # Build glob BREs for filtering the remote package list. The local package 81 | # list is filtered by `rpg-package-index` so we don't need to worry about that 82 | # side. 83 | # 84 | # If there's only one glob and it's `*`, don't do any `grep` nonsense, 85 | # just throw a `cat` in there. 86 | if [ "$*" = '*' ] 87 | then remotefilter="cat" 88 | else remotefilter="grep" 89 | for glob in "$@" 90 | do glob=$( 91 | echo "$glob" | 92 | sed -e 's@\*@[^ ]*@g' -e 's/\?/[^ ]/g' 93 | ) 94 | remotefilter="$remotefilter -e '^$glob ' -e ' $glob\$'" 95 | done 96 | fi 97 | 98 | # Main Pipeline 99 | # ------------- 100 | 101 | # Kick off a pipeline by listing installed packages. The output from 102 | # `rpg-package-index` looks something like this: 103 | # 104 | # 105 | # RedCloth 4.2.3 106 | # abstract 1.0.0 107 | # actionmailer 2.3.5 108 | # actionpack 2.3.5 109 | # activerecord 2.3.5 110 | # activeresource 2.3.5 111 | # activesupport 2.3.5 112 | # ansi 1.1.0 113 | # builder 2.1.2 114 | # classifier 1.3.1 115 | # coffee-script 0.3.2 116 | # ... 117 | # 118 | # So we have the ` ` pairs separated by whitespace, 119 | # basically. 120 | rpg-package-index -x "$@" | 121 | 122 | # Okay ... 123 | # 124 | # This is going to blow your mind. 125 | # 126 | # Use `join(1)` to perform a relational join between the installed 127 | # package list and the recent release list from the index. The recent 128 | # release list looks nearly identical, format-wise, to the installed 129 | # list. 130 | # 131 | # There's a few things needed for this to work properly. First, both 132 | # files need to be sorted (as with `sort -b`) on the join field. Here, 133 | # the join field is the `` -- we don't need to specify it 134 | # explicitly on the command line because it's the first field in both 135 | # files. 136 | # 137 | # The stream text that comes out of `join(1)` looks like: 138 | # 139 | # 140 | # 141 | # Without any other options, `join(1)` performs an "inner join", 142 | # excluding unpaired lines from output entirely. Which is great because 143 | # that's exactly what we need to determine which packages are out of 144 | # date. 145 | # 146 | # This is a really insanely fast operation because it need only take a 147 | # single pass over each file. It simply walks through each file line by 148 | # line and, because both files are sorted, knows immediately whether the 149 | # lines intersect or are unpairable. 150 | # 151 | # Additional, we selectively enable some of `join(1)`'s other options (`-a` 152 | # and `-e`) for achieving "outer joins" and "full joins" when querying 153 | # against all remote packages. 154 | join -a 1 $joiner \ 155 | -o 1.1,1.2,2.2,2.1 \ 156 | -e '-' \ 157 | - "$RPGINDEX/release-recent" | 158 | 159 | # Grep out remote packages based on our globs. See the *Glob Filter* section 160 | # above for more information. 161 | /bin/sh -c "exec $remotefilter" | 162 | 163 | # Grep out lines that don't match a package. Also, the regular expression 164 | # is amazing. 165 | grep -v '. - - .' | 166 | 167 | # All that's left is to read the output from `join` and apply some light 168 | # formatting. 169 | if ! $long 170 | then 171 | if $all 172 | then awk '$4 != "-" { print $4, $3; }' 173 | else awk '$2 != "-" { print $1, $2; }' 174 | fi 175 | else 176 | while read package curvers recvers pdup 177 | do 178 | test "$package" = '-' && 179 | package="$pdup" 180 | 181 | if test "$recvers" = '-' 182 | then sig="$st_missing" 183 | 184 | elif test "$curvers" = "-" 185 | then sig="$st_missing" 186 | curvers="-" 187 | 188 | elif test "$curvers" = "$recvers" 189 | then sig="$st_up2date" 190 | 191 | else sig="$st_outdate" 192 | fi 193 | 194 | printf "$st_format" "$sig" "$package" "$curvers" "$recvers" 195 | done 196 | fi 197 | -------------------------------------------------------------------------------- /rpg-manifest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | . rpg-sh-setup 4 | 5 | [ "$*" ] || set -- --help; ARGV="$@" 6 | USAGE '${PROGNAME} [-a] ... 7 | Show files installed for packages. 8 | 9 | Options 10 | -a Show absolute paths to files instead of abbreviating.' 11 | 12 | abbreviate () { 13 | sed " 14 | s|^$RPGLIB/|lib/| 15 | s|^$RPGBIN/|bin/| 16 | s|^$RPGMAN/|man/| 17 | " 18 | } 19 | 20 | if test "$1" = '-a' 21 | then filter='cat' 22 | shift 23 | else filter='abbreviate' 24 | fi 25 | 26 | for package in "$@" 27 | do 28 | packagedir="$RPGDB/$package/active" 29 | if test -d "$packagedir" 30 | then $filter < "$packagedir/manifest" 31 | else die "package not installed: $package" 32 | fi 33 | done 34 | -------------------------------------------------------------------------------- /rpg-outdated.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | . rpg-sh-setup 4 | 5 | ARGV="$@" 6 | USAGE '${PROGNAME} [-u] [...] 7 | List locally installed packages that can be updated. 8 | 9 | Options 10 | -u Sync the available package index before running.' 11 | 12 | # `rpg-list` implements `-u` so just pass everything right on over. 13 | rpg-list -l "$@" | 14 | grep '^\*' | 15 | sed 's/..//' 16 | -------------------------------------------------------------------------------- /rpg-package-index.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # The `rpg-package-index` program walks over the installed package database 3 | # and writes a line with the ` ` to standard output for 4 | # each installed package. 5 | set -e 6 | . rpg-sh-setup 7 | 8 | ARGV="$@" 9 | USAGE '${PROGNAME} [-x] [...] 10 | List packages installed in the rpg database. 11 | 12 | Only packages matching a are output, or all packages when no 13 | is specified. 14 | 15 | Options 16 | -x Include non-matching globs in output.' 17 | 18 | shownonmatch=false 19 | test "$1" = '-x' && { 20 | shownonmatch=true 21 | shift 22 | } 23 | 24 | # With no ``s, list everything. 25 | [ "$*" ] || set -- '*' 26 | 27 | # Switch into package database dir or bail out if it doesn't exist. 28 | test -d "$RPGDB" && 29 | cd "$RPGDB" || 30 | exit 0 31 | 32 | # Run over globs gives on the command line and locate matching installed 33 | # packages. By default, nothing is written for glob patterns that don't match an 34 | # installed package. The `-x` option changes this behavior so that a single line 35 | # is output with the package version '-'. 36 | for glob in "$@" 37 | do 38 | matched=false 39 | for path in $(ls -1d $glob/active 2>/dev/null) 40 | do 41 | matched=true 42 | package=${path%/active} 43 | vers=$(readlink $path) 44 | echo "$package" "$vers" 45 | done 46 | 47 | if $shownonmatch && ! $matched 48 | then echo "$glob" - 49 | fi 50 | done | 51 | 52 | # It's possible for multiple globs to match the same package. Run the stream 53 | # text through `sort -u` to sort the list and prevent dupes from showing up. 54 | sort -u 55 | -------------------------------------------------------------------------------- /rpg-package-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # The `rpg-package-install` program perform the actual installation of files 3 | # into the system installation locations. The `` and `` 4 | # supplied must already be registered in the package database as by invoking 5 | # the `rpg-package-register` program. 6 | # 7 | # If the package is already installed and at the version specified, 8 | # `rpg-package-install` exits immediately with a success exit status. The 9 | # `-f` argument can be used to force the install operations to be performed 10 | # on an already installed program. 11 | set -e 12 | . rpg-sh-setup 13 | 14 | [ "$*" ] || set -- '--help'; ARGV="$@" 15 | USAGE '${PROGNAME} [-f] ... 16 | Install a registered package from the database. 17 | 18 | This is a low level command. For an install front-end, see rpg-install(1).' 19 | 20 | force=false 21 | test "$1" = '-f' && { 22 | force=true 23 | shift 24 | } 25 | 26 | [ "$1$2" ] || { 27 | warn "invalid arguments: '$*'"; 28 | exit 2 29 | } 30 | 31 | # Utility Functions 32 | # ----------------- 33 | 34 | # Usage: `installfile ` 35 | # 36 | # Attempt to hard link `` to `` but fall back to `cp(1)` if 37 | # you're crossing file systems or `ln` fails otherwise. 38 | installfile () { 39 | if ln -f "$1" "$2" 2>/dev/null 40 | then notice "$2 [ln]" 41 | else notice "$2 [cp]" 42 | cp "$1" "$2" 43 | fi 44 | } 45 | 46 | # Usage: `installdir ` 47 | # 48 | # Recursive file hierarchy copy routine. Attempts to hardlink files 49 | # and falls back to normal copies. 50 | installdir () { 51 | mkdir -p "$2" 52 | for file in "$1"/* 53 | do 54 | if ! test -e "$file" 55 | then 56 | # no files in directory - * was not expanded 57 | : 58 | 59 | elif test -f "$file" 60 | then # link dest to source 61 | installfile "$file" "$2/$(basename $file)" 62 | echo "$2/$(basename $file)" 63 | 64 | elif test -d "$file" 65 | then # recurse into directories 66 | installdir "$file" "$2/$(basename $file)" 67 | 68 | else warn "unknown file type: $file" 69 | return 1 70 | fi 71 | done 72 | return 0 73 | } 74 | 75 | # Package Database Prep 76 | # --------------------- 77 | 78 | # Establish our directories in the package database. These should 79 | # have already been created by `rpg-package-register`. If not, bail 80 | # out now since something isn't right. 81 | package="$1" version="$2"; shift 2 82 | test "$version" = '=' && { version="$1"; shift; } 83 | packagedir="$RPGDB/$package" 84 | 85 | test -d "$packagedir/$version" || { 86 | warn "package not registered: $package $version" 87 | exit 1 88 | } 89 | 90 | heed "$package $version" 91 | 92 | # Fetch the gem into the cache and unpack into the packs area if 93 | # its not already there. 94 | if ! $force && test -d "$RPGPACKS/$package-$version" 95 | then notice "$package $version sources exist. bypassing fetch / unpack." 96 | else rm -rf "$RPGPACKS/$package-$version" 97 | gemfile=$(rpg-fetch "$package" "$version") 98 | notice "unpacking $gemfile into $RPGPACKS" 99 | mkdir -p "$RPGPACKS" 100 | rpg-unpack -p "$RPGPACKS" "$gemfile" >/dev/null 101 | rpg-shit-list "$package" "$version" "$RPGPACKS/$package-$version" 102 | fi 103 | 104 | # If the package already has an active/installed version, check if it's 105 | # the same as the one we're installing and bail if so. Otherwise unlink 106 | # the active version and install over it for now. 107 | test -e "$packagedir/active" && { 108 | activevers=$(readlink $packagedir/active) 109 | if test "$activevers" = "$version" 110 | then 111 | if $force 112 | then notice "$package $version is current; reinstalling due to -f" 113 | unlink "$packagedir/active" 114 | else notice "$package $version is current; skipping package install" 115 | exit 0 116 | fi 117 | else notice "$package $activevers is installed but $version requested" 118 | rpg-uninstall "$package" 119 | fi 120 | } 121 | 122 | # Path to the unpacked package directory. 123 | pack="$RPGPACKS/$package-$version" 124 | 125 | # Symlink the `installing` file to the version directory. This will let us 126 | # detect in progress or failed installations. 127 | ln -sf "$version" "$packagedir/installing" 128 | 129 | # Anything written to standard output within the main install block is 130 | # written to the install manifest. The manifest should include full paths to 131 | # all files installed 132 | manifest="$packagedir/$version/manifest" 133 | { 134 | echo "# $package $version ($(date))" 135 | 136 | # Extension Libraries 137 | # ------------------- 138 | 139 | # Build extension libraries if they exist. Bail out if the build fails. 140 | exts="$(rpg-build "$pack")" || { 141 | warn "extension failed to build" 142 | exit 1 143 | } 144 | 145 | # Install any extensions to `RPGLIB`. This is kind of tricky. We should 146 | # be running `make` in the extension directory but I haven't had time to 147 | # make it work right so just pull the prefix out of the `Makefile` and 148 | # install the shared libs manually. 149 | test -n "$exts" && { 150 | mkdir -p "$RPGLIB" 151 | echo "$exts" | 152 | while read dl 153 | do 154 | prefix=$(sed -n 's/^target_prefix *= *//p' "$(dirname $dl)/Makefile") 155 | test "$prefix" = "/ext" && prefix="" 156 | dest="${RPGLIB}/${RUBYARCH}${prefix}/$(basename $dl)" 157 | mkdir -p "${RPGLIB}/${RUBYARCH}${prefix}" 158 | installfile "$dl" "$dest" 159 | echo "$dest" 160 | done 161 | } 162 | 163 | # Ruby Library Files 164 | # ------------------ 165 | 166 | # Recursively install all library files into `RPGLIB`. 167 | # 168 | # A big majority of packages have a single lib directory but some use an 169 | # alternative libdir (ruby-debug) and it's also possible to have multiple 170 | # lib directories. Use the `require_paths` gemspec value to determine lib 171 | # sub-directories, ignoring certain incorrect values (`test`, `ext`, `spec`, 172 | # etc.). 173 | 174 | libdirs=$(cat "$packagedir/$version/require_paths" 2>/dev/null) 175 | : ${libdirs:=lib} 176 | 177 | for libdir in $libdirs 178 | do 179 | test "$libdir" = "ext" && continue 180 | test "$libdir" = "test" && continue 181 | test "$libdir" = "spec" && continue 182 | 183 | if test -d "$pack/$libdir" 184 | then mkdir -p "$RPGLIB" 185 | installdir "$pack/$libdir" "$RPGLIB" 186 | else notice "warning: $package libdir '$libdir' does not exist" 187 | fi 188 | done 189 | 190 | # Ruby Executables 191 | # ---------------- 192 | 193 | bindir=$(cat "$packagedir/$version/bindir" 2>/dev/null) 194 | : ${bindir:=bin} 195 | 196 | # Write executable scripts into `RPGBIN` and rewrite shebang lines. 197 | test -d "$pack/$bindir" && { 198 | mkdir -p "$RPGBIN" 199 | for file in "$pack/$bindir"/* 200 | do dest="$RPGBIN/$(basename $file)" 201 | notice "$dest [!]" 202 | sed "s@^#!.*ruby.*@#!$(ruby_command)@" <"$file" >"$dest" 203 | chmod 0755 "$dest" 204 | echo "$dest" 205 | done 206 | } 207 | 208 | # Manpages 209 | # -------- 210 | 211 | # Install any manpages included with the package into `RPGMAN`. Make 212 | # sure files are being installed under the prescribed hierarchy. 213 | test -d "$pack/man" && { 214 | for file in "$pack/man"/* 215 | do 216 | if test -f "$file" && 217 | expr "$file" : '.*\.[0-9][0-9A-Za-z]*$' >/dev/null 218 | then 219 | section=${file##*\.} 220 | dest="$RPGMAN/man$section/$(basename $file)" 221 | mkdir -p "$RPGMAN/man$section" 222 | installfile "$file" "$dest" 223 | echo "$dest" 224 | fi 225 | done 226 | } 227 | 228 | } > "$manifest" 229 | 230 | # Mark this package as active 231 | unlink "$packagedir/installing" 232 | ln -sf "$version" "$packagedir/active" 233 | -------------------------------------------------------------------------------- /rpg-package-list.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Parses a package list given in argv or piped in standard input (or both) 3 | # and writes a formatted package list to standard output. This is used by 4 | # most commands that accept multiple package arguments to get consistent 5 | # behavior. 6 | # 7 | # A variety of argument styles are supported: 8 | # 9 | # $ rpg-package-list rdiscount '>=1.8.7' sinatra/1.0 rails \> 3 10 | # rdiscount >= 1.8.7 11 | # sinatra = 1.0 12 | # rails > 3 13 | # 14 | # The output format is: 15 | # 16 | # 17 | # 18 | set -e 19 | . rpg-sh-setup 20 | 21 | ARGV="$@" 22 | USAGE '${PROGNAME} [] ... 23 | Parse list of packages as args or on standard input and output 24 | in standard package list format.' 25 | 26 | # These variables are used to keep the current package and version. 27 | package= 28 | verspec= 29 | vers= 30 | 31 | # Parse argv 32 | parse_packages () { 33 | # Run over arguments adding packages as needed. 34 | for arg in $(cat) 35 | do 36 | case "$arg" in 37 | -v|--version) :;; 38 | -*) warn "invalid argument: '$arg'"; 39 | exit 2;; 40 | [\>\<=~]*) verspec="$arg";; 41 | *.gem) write_package; package="$arg";; 42 | *.*) vers="$arg";; 43 | [0-9]|[0-9][0-9]|[0-9][0-9][0-9]) vers="$arg";; 44 | [A-Za-z0-9_]*) write_package; package="$arg";; 45 | *) warn "invalid argument: '$arg'"; 46 | exit 2;; 47 | esac 48 | done 49 | write_package 50 | } 51 | 52 | # Write a ` ` pair to standard output and reset the 53 | # `package`, `verspec`, and `vers` variables. 54 | write_package () { 55 | test -n "$package" || return 0 56 | 57 | # Use `>=0` if no version was given 58 | test -n "$vers" || { 59 | verspec='>=' 60 | vers='0' 61 | } 62 | 63 | # Write single package list line to standard output. 64 | echo "$package ${verspec:-=} ${vers:-0}" 65 | 66 | # Reset variables and start over. 67 | package= 68 | verspec= 69 | vers= 70 | } 71 | 72 | # Massage input to make option parsing a bit easier. Substitutions are: 73 | # 74 | # * `foo -v1.2.3` turns into `foo -v 1.2.3` 75 | # * `>=0.3.1` turns into `>= 0.3.1` 76 | # * `rails/2.3.4` turns into `rails 2.3.4` 77 | # 78 | preformat () { 79 | sed -e "s/[ ]\{1,\}/$ENEWLINE/g" | 80 | sed -e "s/^-\([a-z]\)\([^ ]\)/-\1$ENEWLINE\2/g" \ 81 | -e "s@^\([a-z][a-z]*\)/\([0-9.]\)@\1$ENEWLINE\2@g" \ 82 | -e "s/\([><=~]\)\([0-9]\)/\1$ENEWLINE\2/g" 83 | } 84 | 85 | # Read package list from stdin if - given 86 | test "$1" = - && { 87 | shift 88 | notice "parsing package list items on stdin" 89 | preformat | 90 | parse_packages 91 | } 92 | 93 | # Now format arguments 94 | notice "parsing package list items in $# arguments" 95 | echo "$@" | 96 | preformat | 97 | parse_packages 98 | -------------------------------------------------------------------------------- /rpg-package-register.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Register a gem in the local package database. 3 | set -e 4 | . rpg-sh-setup 5 | 6 | [ "$*" ] || set -- '--help'; ARGV="$@" 7 | USAGE '${PROGNAME} [-f] ... 8 | ${PROGNAME} [-f] 9 | Register a gem in the package database.' 10 | 11 | force=false 12 | test "$1" = '-f' && { 13 | force=true 14 | shift 15 | } 16 | 17 | # Under the second synopsis form, we first perform a `rpg-fetch` on the 18 | # `` and `` given and then continue with the resulting 19 | # filename. 20 | if ! expr -- "$1" : '.*\.gem' >/dev/null 21 | then 22 | if test $# -eq 2 23 | then gemfile=$(rpg-fetch "$1" "$2") 24 | set -- "$gemfile" 25 | else echo "$PROGNAME: '$1' package version required" 26 | exit 2 27 | fi 28 | fi 29 | 30 | for file in "$@" 31 | do 32 | # Information we can extract from the gem name. 33 | gemname=$(basename $file .gem) 34 | package=${gemname%-*} 35 | version=${gemname##*-} 36 | 37 | # These are directories and file locations into the package database. 38 | packagedir="$RPGDB/$package/$version" 39 | gemspec="$packagedir/gemspec" 40 | deps="$packagedir/deps" 41 | 42 | # Try to exit if the package is already registered and looks okay. The 43 | # `-f` argument can be used to override and force the package to be 44 | # registered again. 45 | if test -f "$packagedir/gemspec" -a -f "$packagedir/name" 46 | then 47 | if $force 48 | then notice "$package $version already registered: proceeding due to -f" 49 | else notice "$package $version already registered: bypassing" 50 | echo "$packagedir" 51 | exit 0 52 | fi 53 | else 54 | notice "$package $version -> $packagedir" 55 | fi 56 | 57 | # Create the package directory, write `name` and `version` files, 58 | # extract and write gemspec related files. 59 | # 60 | # The `name` and `version` files are redundant since that info can be 61 | # obtained from `$(basename $(dirname ))` and `$(basename )`, 62 | # but having them there makes some things a bit easier. 63 | mkdir -p "$packagedir" 64 | echo "$package" > "$packagedir/name" 65 | echo "$version" > "$packagedir/version" 66 | rpg-unpack -cm "$file" > "$gemspec" 67 | rpg-package-spec -i "$gemspec" 68 | echo "$packagedir" 69 | done 70 | -------------------------------------------------------------------------------- /rpg-package-spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # Read YAML gemspecs given as arguments or on standard input and write 3 | # rpg formatted spec hierarchies to the package database. 4 | USAGE = < 6 | rpg-package-spec [-i|--import] ... 7 | Deal with gemspecs. 8 | 9 | In the first form, read gemspec YAML from a gemspec or gem file and write 10 | parseable output to standard output. In the second form, read multiple 11 | gemspec files into the package database. 12 | 13 | Options 14 | -i, --import Load gemspec data into the package database. No output 15 | is written. Multiple files may be specified. 16 | 17 | This is a low level command used by the rpg package database machinery. 18 | BANNER 19 | 20 | # Bail out with no arguments or --help. 21 | if ARGV.empty? 22 | puts USAGE 23 | exit 2 24 | elsif ARGV.include?('--help') 25 | puts USAGE 26 | exit 0 27 | end 28 | 29 | # Re-execute through rpg if the environment isn't right. 30 | exec "rpg", "package-spec", *ARGV if ENV['RPGDB'].to_s == '' 31 | 32 | # Figure out if we're in import mode or print formatted output mode. 33 | if ['-i', '--import'].include?(ARGV.first) 34 | ARGV.shift 35 | import_mode = true 36 | else 37 | import_mode = false 38 | end 39 | 40 | # Load the YAML in from file or extract from gem using `rpg-unpack(1)`. 41 | require 'ostruct' 42 | require 'yaml' 43 | 44 | ARGV.each do |file| 45 | yaml = 46 | if file =~ /\.gem$/ 47 | `rpg unpack -cm '#{file}'` 48 | else 49 | File.read(file) 50 | end 51 | doc = YAML.load(yaml) 52 | 53 | # Turn this fucker into something sensible. 54 | spec = doc.ivars.dup 55 | spec['version'] = spec['version'].ivars['version'] 56 | spec['dependencies'] = 57 | spec['dependencies'].map do |dep| 58 | vars = dep.ivars 59 | [ 60 | vars['name'], 61 | vars['type'] || 'runtime', 62 | vars['version_requirements'].ivars['requirements'].map do |op,vers| 63 | vers = vers.ivars['version'] 64 | [op, vers] 65 | end 66 | ] 67 | end 68 | spec['date'] = 69 | case spec['date'] 70 | when Time; spec['date'].utc.strftime('%Y-%m-%d') 71 | when Date; spec['date'].to_s 72 | when String 73 | # Some date formats are not parsed by YAML despite being legitimate; 74 | # e.g. 2011-08-25 00:00:00.000000000Z. 75 | # Use Time.parse to parse such dates. 76 | # Sadly, it looks like Time.parse will silently accept any garbage 77 | # fed to it, meaning truly invalid input is unlikely to be caught. 78 | require 'time' 79 | Time.parse(spec['date']).utc.strftime('%Y-%m-%d') 80 | else fail "unexpected date value: #{spec['date'].inspect}" 81 | end 82 | spec.reject! { |k,v| v.respond_to?(:ivars) } 83 | 84 | if import_mode 85 | 86 | # Grab the package name and version. 87 | package = spec['name'] 88 | version = spec['version'] 89 | package_dir = "#{ENV['RPGDB']}/#{package}/#{version}" 90 | 91 | if !File.directory?(package_dir) 92 | Dir.mkdir File.dirname(package_dir) rescue nil 93 | Dir.mkdir package_dir rescue nil 94 | if !File.directory?(package_dir) 95 | abort "#{File.basename($0)}: package directory missing." 96 | end 97 | end 98 | 99 | package_write = 100 | lambda do |file, value| 101 | File.open("#{package_dir}/#{file}", 'wb') do |fd| 102 | fd.puts(value.to_s) 103 | end 104 | end 105 | 106 | %w[name version date homepage platform email bindir summary description].each do |key| 107 | package_write.call key, spec[key] 108 | end 109 | 110 | %w[authors files extensions executables test_files require_paths].each do |key| 111 | next if spec[key].nil? 112 | package_write.call key, spec[key].join("\n") 113 | end 114 | 115 | File.open "#{package_dir}/dependencies", 'wb' do |fd| 116 | spec['dependencies'].each do |name, type, reqs| 117 | reqs.each do |op, vers| 118 | fd.puts "#{type} #{name} #{op} #{vers}" 119 | end 120 | end 121 | end 122 | 123 | else 124 | 125 | %w[name version date homepage platform email bindir].each do |key| 126 | puts "#{key}: #{spec[key]}" 127 | end 128 | 129 | puts "rdoc: #{spec['rdoc_options'].join(' ')}" 130 | 131 | %w[authors files extensions executables test_files require_paths].each do |key| 132 | next if spec[key].nil? 133 | label = key.chomp('s') 134 | label = 'test' if key == 'test_files' 135 | label = 'lib' if key == 'require_paths' 136 | spec[key].each do |line| 137 | puts "#{label}: #{line}" 138 | end 139 | end 140 | 141 | spec['dependencies'].each do |name, type, reqs| 142 | reqs.each do |op, vers| 143 | puts "dependency: #{type} #{name} #{op} #{vers}" 144 | end 145 | end 146 | 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /rpg-parse-gemfile.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # The `rpg-parse-gemfile` program reads a Bundler Gemfile and writes a 3 | # list of packages on standard output suitable for feeding to `rpg-install`. 4 | # 5 | # Usage 6 | # ----- 7 | # 8 | # Given a project with a Gemfile, it is possible to install its dependencies 9 | # by running: 10 | # 11 | # rpg install $(rpg parse-gemfile /path/to/Gemfile) 12 | # 13 | # Caveats 14 | # ------- 15 | # 16 | # At this time only top-level gem statements are actually supported. 17 | # All options are ignored. All groups other than the top-level group are 18 | # ignored. All other statements are ignored. 19 | # 20 | # Bundler uses dependency declarations in all groups when resolving 21 | # dependencies, even in groups that are not being installed. 22 | # See http://yehudakatz.com/2010/05/09/the-how-and-why-of-bundler-groups/, 23 | # section "Consistency". This may render a Gemfile unresolvable if, for 24 | # example, the test group specifies conflicting or broken dependencies, even 25 | # for users who don't want to install the test group. rpg ignores all groups 26 | # that are not being installed during dependency resolution. As a consequence, 27 | # rpg and Bundler may install different sets of packages from the same Gemfile. 28 | USAGE = < 1 || ARGV.empty? && STDIN.tty? 39 | puts USAGE 40 | exit 2 41 | end 42 | 43 | class GemfileParser 44 | def initialize 45 | @packages = [] 46 | end 47 | 48 | def parse(gemfile_text, file=nil) 49 | eval(gemfile_text, nil, file) 50 | end 51 | 52 | def method_missing(name, *args) 53 | #puts "Ignoring #{name}" 54 | end 55 | 56 | def gem(name, *args) 57 | unless args.first.is_a?(Hash) 58 | version = args.shift 59 | end 60 | options = args.shift || {} 61 | @packages << { 62 | :name => name, :version => version, :options => options, 63 | :group => @in_group, 64 | } 65 | end 66 | 67 | def group(name) 68 | if @in_group 69 | raise ArgumentError, 'Nested groups are not supported' 70 | end 71 | @in_group = name 72 | yield 73 | @in_group = nil 74 | end 75 | 76 | def print 77 | @packages.each do |package| 78 | next if package[:group] 79 | version_op, version_value = canonicalize_version(package[:version]) 80 | puts "#{package[:name]} #{version_op} #{version_value}" 81 | end 82 | end 83 | 84 | def canonicalize_version(version) 85 | if version.nil? 86 | return ['>=', '0'] 87 | end 88 | unless version =~ /^([>=~]*)(\d.+)$/ 89 | raise ArgumentError, "Invalid version specification: #{version}" 90 | end 91 | op, value = $1, $2 92 | if op.empty? 93 | op = '=' 94 | end 95 | return [op, value] 96 | end 97 | end 98 | 99 | if (file = ARGV.shift) && file != '-' 100 | text = File.read(file) 101 | else 102 | file = '' 103 | text = STDIN.read 104 | end 105 | 106 | parser = GemfileParser.new 107 | parser.parse(text, file) 108 | parser.print 109 | -------------------------------------------------------------------------------- /rpg-parse-index.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # The `rpg-parse-index` program reads a Rubygems "modern index" stream on 3 | # standard input and writes a parseable version of the index data on standard 4 | # output. 5 | # 6 | # About Rubygems Spec Indexes 7 | # --------------------------- 8 | # 9 | # Rubygems spec index files are built with the `gem generate_index` command 10 | # and are typically served from gem repositories at predefined locations. For 11 | # example, the canonical Rubygems spec index lives at: 12 | # 13 | # http://rubygems.org/specs.4.8.gz 14 | # 15 | # Output Format Details 16 | # --------------------- 17 | # 18 | # Spec indexes are gzip compressed. This program assumes uncompressed data 19 | # on standard input. You will need to pipe the file off the network through 20 | # `gzip -dc` before feeding it in here. To see the main release spec index 21 | # on stdout, you might: 22 | # 23 | # curl -Ls http://rubygems.org/specs.4.8.gz | gzip -dc | 24 | # rpg-parse-index 25 | # 26 | # A randomly selected bit of output from the above command: 27 | # 28 | # ... 29 | # desert 0.5.3 ruby 30 | # desert 0.5.2 ruby 31 | # desutwo 0.0.3 ruby 32 | # detective 0.3.0 ruby 33 | # detective 0.2.0 ruby 34 | # detective 0.1.0 ruby 35 | # detective 0.0.0 ruby 36 | # devball 0.7 ruby 37 | # devball 0.6 ruby 38 | # devball 0.5 ruby 39 | # devball 0.4 ruby 40 | # devball 0.3 ruby 41 | # ... 42 | # 43 | # The format is: 44 | # 45 | # 46 | # 47 | # Where `` and `` are obvious and `` is an open field 48 | # that can be anything. Popular `` values at time of writing are: 49 | # 50 | # $ rpg-parse-index < spec.4.8 | cut -f 3 | sort | uniq -c | sort -rn 51 | # 48766 ruby 52 | # 363 mswin32 53 | # 265 x86-mswin32-60 54 | # 176 java 55 | # 95 x86-mingw32 56 | # 90 x86-linux 57 | # 87 x86-mswin32 58 | # 52 i386-mswin32 59 | # 46 darwin 60 | # 32 universal-darwin-9 61 | # 30 jruby 62 | # ... 63 | # 64 | # Version Sorting 65 | # --------------- 66 | # 67 | # Another important attribute of the output generated from this command is that 68 | # it's sorted based on Rubygems version comparison rules. The first line of 69 | # output for a given package is that package's "most recent" version. Adjacent 70 | # lines are successively less recent. 71 | # 72 | # This allows the output from this command to be used with `sort -u` and 73 | # `uniq(1)` to generate a most recent index. Utilities like `join(1)` may also 74 | # be used on the output to perform relational operations with other package 75 | # lists having the same format. 76 | USAGE = < n2)) == 0 128 | v2 <=> v1 129 | else 130 | cmp 131 | end 132 | end 133 | 134 | # Finally, run over the sorted list and write a line of output for each package. 135 | packages.each do |name,version,platform| 136 | platform.gsub!(/[^A-Za-z0-9_-]/, '_') 137 | puts "#{name} #{version} #{platform}" 138 | end 139 | 140 | # vim: tw=80 sw=2 ts=2 sts=0 expandtab 141 | -------------------------------------------------------------------------------- /rpg-prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # TODO Documentation and cleanup 3 | set -e 4 | . rpg-sh-setup 5 | 6 | [ "$*" ] || set -- '--help'; ARGV="$@" 7 | USAGE '${PROGNAME} [-s ] [[-v] ] ... 8 | ${PROGNAME} [-s ] [/]... 9 | Prepare packages to be installed into an rpg environment later. 10 | 11 | Options 12 | -s Give this prepared installation a name' 13 | 14 | # TODO add rpg-prepare -e for editing an existing package list 15 | session=default 16 | installing=false 17 | while getopts is: opt 18 | do case $opt in 19 | s) session="$OPTARG";; 20 | i) installing=true;; 21 | ?) helpthem;; 22 | esac 23 | done 24 | shift $(( $OPTIND - 1 )) 25 | 26 | # Session Prep 27 | # ------------ 28 | # 29 | # We create a directory to hold files for this install session. Installing 30 | # requires building multiple package lists and indexes from the set of installed 31 | # and available packages and their dependencies and then taking a few passes 32 | # through the dependency solver. 33 | 34 | # See if we need to sync the index before doing anything. 35 | rpg-sync -s 36 | 37 | # Session directories are stored under `RPGDB` prefixed with an "@" for now. This 38 | # should probably be moved to its own top-level directory. 39 | : ${RPGSESSION:?'not configured'} 40 | mkdir -p "$RPGSESSION" 41 | sessiondir="$RPGSESSION/$session" 42 | packlist="$sessiondir/package-list" 43 | solved="$sessiondir/solved" 44 | existing="$sessiondir/existing" 45 | delta="$sessiondir/delta" 46 | release="$RPGINDEX/release" 47 | 48 | # Get rid of any crusty session directory and then create a new one. It might be 49 | # cool to add an `-e` option so that sessions could be edited to add or 50 | # remove packages. 51 | rm -rf "$sessiondir" 52 | mkdir -p "$sessiondir" 53 | 54 | # Store argv in a file so we can recalculate this session from the beginning. 55 | notice "writing session argv" 56 | for arg in "$@" 57 | do echo "$arg" 58 | done > "$sessiondir/argv" 59 | 60 | # Create the master package list from the packages specified on the command 61 | # line. Each line in the file has the format: 62 | # 63 | # @user 64 | # 65 | # As we go through the dep solve loop below, we'll add packages to this file 66 | # for each dependency of each package we're installing (recursively). Lines 67 | # corresponding to a dependency have the depending package name in the first 68 | # field instead of `@user`. 69 | notice "writing user package-list" 70 | rpg-package-list "$@" | 71 | sort -b -u | 72 | sed "s/^/@user /" > "$packlist" 73 | 74 | # Create the pre-existing package index. This is a simple list of all packages 75 | # currently installed in the standard ` ` package index 76 | # format. We try to resolve packages and dependencies against this list before 77 | # the main release index. 78 | notice "writing pre-existing package index" 79 | rpg-package-index > "$existing" 80 | 81 | # Create the existing dependencies package list from all dependencies of 82 | # all existing installed packages but exclude dependencies of packages specified 83 | # in master package list for this session. 84 | notice "gathering existing dependencies" 85 | alldeps=$(rpg-dependencies -a) 86 | 87 | # Grab some stats and let 'em know we're about to begin. 88 | numpacks=$(<"$packlist" sort -u -k2,2 |grep -c .) 89 | if test $numpacks -eq 1 90 | then packname=$(head -1 "$packlist" |cut -d ' ' -f 2) 91 | heed "calculating dependencies for $packname ..." 92 | else heed "calculating dependencies for $numpacks package(s) ..." 93 | fi 94 | 95 | # Dependency Solving 96 | # ------------------ 97 | # 98 | # Dependency solving and conflict resolution works in multiple passes 99 | # over the master package list. On each iteration, concrete package versions 100 | # are resolved from the version requirements in the package list. Each resolved 101 | # packages's dependency rules are then added to the master package list. If no 102 | # rules are added to the master package list in an iteration then the solving is 103 | # complete. 104 | 105 | : >"$solved" 106 | changed=true 107 | runcount=0 108 | while $changed 109 | do 110 | runcount=$(( runcount + 1 )) 111 | notice "this is depsolve run #$runcount" 112 | 113 | # Prune packages we're installing now from the existing dependencies list. 114 | # Since these packages are being installed, we don't want the already 115 | # installed package versions's dependencies to come into play during 116 | # solving. The `-v` option `join(1)` causes only lines from our existing 117 | # dependencies list that cannot be paired with the master package list to be 118 | # included in the output. 119 | alldeps=$( 120 | echo "$alldeps" |sort | 121 | join -1 1 -2 2 -v 1 \ 122 | -o 1.1,1.2,1.3,1.4 \ 123 | - "$packlist" | 124 | sort -b -k 2,4 125 | ) 126 | 127 | # Now take all dependencies for all existing packages that *aren't* being 128 | # installed here and add them to the master package list, retaining the 129 | # proper sort order. 130 | echo "$alldeps" | 131 | join -1 2 -2 2 -o 1.1,1.2,1.3,1.4 \ 132 | - "$packlist" | 133 | sort -mbu -k 2,4 -k 1,1 "$packlist" - >"$packlist+" 134 | 135 | # Solve all packages in the master package list and write the resulting 136 | # package index to the newly solved file (`solved+`). The solved file is 137 | # a sorted package index in ` ` format. 138 | cut -d ' ' -f 2- "$packlist+" | 139 | uniq | 140 | rpg-solve "$solved" "$existing" "$release" | 141 | sort -u -k 1,1 >"$solved+" 142 | 143 | # Use `comm(1)` to select only those lines in the newly solved file that 144 | # were not present on the previous iteration, exclude packages that could 145 | # not be solved to a concrete version, and pass the remaining 146 | # package/version combos into `rpg-package-register` to fetch and enter 147 | # the package into the database. Using `xargs -P 8` allows as many as eight 148 | # concurrent fetch/register operations to run in parallel. 149 | { 150 | comm -13 "$solved" "$solved+" | 151 | grep -v ' -$' | 152 | xargs -P 8 -n 2 rpg-package-register 153 | } >/dev/null || die "fetch/register package failed. aborting." 154 | 155 | # Rebuild the master package list by concatenating the original user- 156 | # specified packages with all dependencies of all packages solved so far. 157 | # Make sure the newly built package list is sorted by the package name -- 158 | # the `uniq` in the `rpg-solve` pipeline above relies on this. 159 | { 160 | grep '^@user' "$packlist" 161 | grep -v ' -$' "$solved+" | 162 | xargs -P 4 -n 2 rpg-dependencies -p 163 | } |sort -b -k 2,4 >"$packlist+" 164 | 165 | # Check whether the package list has changed at all. If so, new rules were 166 | # added to the package list by dependencies. If not, we're done and can 167 | # leave the dep solve loop. 168 | if cmp -s "$packlist" "$packlist+" 169 | then changed=false 170 | else changed=true 171 | fi 172 | 173 | mv "$packlist+" "$packlist" 174 | mv "$solved+" "$solved" 175 | done 176 | 177 | # Build a package list with only solved packages that are not already installed. 178 | # This is our package install manifest and includes only packages that aren't 179 | # already installed. 180 | comm -13 "$existing" "$solved" >"$delta" 181 | 182 | # Figure out how many packages were involved and how many packages need to be 183 | # installed. 184 | totalpacks=$(grep -c . <"$solved") 185 | deltapacks=$(grep -c . <"$delta") || { 186 | heed "$totalpacks packages already installed and up to date" 187 | exit 0 188 | } 189 | 190 | # Calculate the total number of packages that are already installed. 191 | freshpacks=$(( totalpacks - deltapacks )) 192 | heed "$freshpacks of $totalpacks packages already installed and up to date" 193 | 194 | # Check for unsolved packages in our solved list. Unsolved packages have 195 | # a dash "-" in their version field. 196 | if badpacks=$(grep ' -$' "$delta") 197 | then 198 | heed "$(echo "$badpacks" |grep -c .) packages failed to resolve:" 199 | for pack in $(echo "$badpacks" | cut -d ' ' -f 1) 200 | do 201 | heed "$pack ($(cut -d ' ' -f 3- "$packlist"))" 202 | done 203 | versions=$(grep "^$pack " "$release" | cut -d ' ' -f 2) 204 | if test -n "$versions" 205 | then 206 | heed "available versions: $(echo $versions)" 207 | fi 208 | exit 1 209 | fi 210 | 211 | # Note the number of packages that are now queued up for installation. 212 | ! $installing && { 213 | goodpacks=$(grep -v ' -$' "$delta") 214 | heed "$(echo "$goodpacks" |grep -c .) packages ready for installation: 215 | $goodpacks" 216 | } 217 | 218 | true 219 | -------------------------------------------------------------------------------- /rpg-resolve.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | . rpg-sh-setup 4 | 5 | [ "$*" ] || set -- "--help"; ARGV="$@" 6 | USAGE '${PROGNAME} [-f ] [-n ] [-p] ... 7 | Write available package versions matching version s. 8 | 9 | Options 10 | -f Package index to resolve versions against; the main 11 | release index is used when not specified. 12 | -n Write no more than versions. 13 | -p Include package name in output.' 14 | 15 | max=100 16 | packagelist=false 17 | while getopts p1f:n: opt 18 | do case $opt in 19 | f) index="$OPTARG";; 20 | n) max="$OPTARG";; 21 | 1) max=1;; 22 | p) packagelist=true;; 23 | ?) helpthem;; 24 | esac 25 | done 26 | shift $(( $OPTIND - 1 )) 27 | 28 | package="$1"; shift 29 | [ "$*" ] || helpthem 30 | 31 | # Use the default release index with no `-f` option, and sync it if it 32 | # doesn't exist. If we were given an explicit index file, exit with failure 33 | # if it doesn't exist. 34 | if test -z "$index" 35 | then index="$RPGINDEX/release" 36 | test -f "$index" || rpg-sync -s 37 | else test -f "$index" 38 | fi 39 | 40 | versions=$( 41 | rpg-package-list "$package" "$@" | 42 | rpg-solve "$index" | 43 | head -$max | 44 | cut -d ' ' -f 2 | 45 | grep -v '^-$' 46 | ) 47 | 48 | # Exit with success if we found at least one version, failure otherwise. 49 | if test -n "$versions" 50 | then notice "hit $package $* in ${index##*/}" 51 | if $packagelist 52 | then echo "$versions" | sed "s/^/$package /" 53 | else echo "$versions" 54 | fi 55 | else notice "miss $package $* in ${index##*/}" 56 | exit 1 57 | fi 58 | -------------------------------------------------------------------------------- /rpg-sh-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # RPG shell utility library. 3 | # 4 | # This file is sourced by all `rpg-*` utilities. It handles environment 5 | # setup and provides utility functions. 6 | # 7 | # A typical rpg program should look like this: 8 | # 9 | # #!/bin/sh 10 | # # Launch a rocket missile or something like that. 11 | # set -e 12 | # . rpg-sh-setup 13 | # 14 | # ARGV="$@" 15 | # USAGE '${PROGNAME} [-f] 16 | # Launch a rocket missile.' 17 | # 18 | # # missile launching code 19 | # 20 | # That will handle `--help` usage messages and load the RPG environment. 21 | 22 | # Guard against sourcing this file multiple times 23 | test $__rpg_sh_setup_sourced && return 0 24 | __rpg_sh_setup_sourced=true 25 | 26 | # Constants 27 | # --------- 28 | 29 | # Useful BRE patterns for matching various gem stuffs. 30 | GEMNAME_BRE='[0-9A-Za-z_.-]\{1,\}' 31 | GEMVERS_BRE='[0-9][0-9.]*' 32 | GEMPRES_BRE='[0-9A-Za-z][0-9A-Za-z.]*' 33 | 34 | # This seems to be the most portable way of getting a variable with 35 | # embedded newline. `$'\n'` is POSIX but doesn't work in my version of 36 | # `dash(1)`. This works in `bash` and `dash` at least. 37 | # 38 | # The `ENEWLINE` is just an escaped version, useful for `sed` patterns. 39 | NEWLINE=' 40 | ' 41 | ENEWLINE="\\$NEWLINE" 42 | 43 | # Usage Messages, Logging, and Stuff Like That 44 | # -------------------------------------------- 45 | 46 | # The program name used in usage messages, log output, and other places 47 | # probably. You can set this before sourcing rpg-sh-setup to override 48 | # the default `$(basename $0)` value but it's probably what you want. 49 | : ${PROGNAME:=$(basename $0)} 50 | 51 | # The progam's usage message. See the documentation for the `USAGE` 52 | # function for information on setting this and how it plays with the 53 | # other usage related functions. 54 | : ${__USAGE__:='${PROGNAME} '} 55 | 56 | # This is the main usage setting thingy. Scripts should start as 57 | # follows to take advantage of it: 58 | # 59 | # set -e # always 60 | # . rpg-sh-setup # bring in support lib 61 | # 62 | # ARGV="$@" 63 | # USAGE '${PROGNAME} ... 64 | # A short, preferably < 50 char description of the script. 65 | # 66 | # Options: 67 | # -b Booooyaaahhh.' 68 | # 69 | # That will automatically trigger option parsing for a `--help` 70 | # argument and whatnot. 71 | # 72 | # Note that the string passed in is single-quote escape. The string will 73 | # evaluated at help time so you can do wild/expensive interpolations if 74 | # that's your thing. 75 | # 76 | # One more quick usage tip. Some scripts want to show usage when `$@` is 77 | # empty and others don't. These `USAGE` routines default to *not* 78 | # showing the usage message when no arguments were passed. If you want 79 | # to show usage when no arguments are passed, put this immediately 80 | # before setting the `ARGV` variable: 81 | # 82 | # [ "$*" ] || set -- --help 83 | # ARGV="$@" 84 | # USAGE ... 85 | # 86 | # That'll cause empty arg invocations to show the help message. 87 | USAGE () { 88 | __USAGE__="${1:-$(cat)}" 89 | case "$ARGV" in 90 | *--h|*--he|*--hel|*--help*|*-h|*-\?*) 91 | helpthem 0 92 | exit 0;; # just in case 93 | esac 94 | } 95 | 96 | # Show usage message defined in USAGE environment variable. The usage message 97 | # is first evaluated as a string so interpolations can be performed if 98 | # necessary. 99 | helpthem () { 100 | : ${REAL_USAGE:=$(eval "echo \"$__USAGE__\"")} 101 | echo "Usage: $REAL_USAGE" 102 | exit ${1:-2} 103 | } 104 | 105 | 106 | # Write a warning to stderr. The message is prefixed with the 107 | # program's basename. 108 | warn () { echo "$PROGNAME:" "$@" 1>&2; } 109 | 110 | # Write an informationational message to stderr prefixed with the name 111 | # of the current script. Don't use this, use `notice`. 112 | heed () { 113 | printf "%18s %s\n" "${PROGNAME#rpg-}:" "$*" | 114 | sed 's/^\([^ ]\)/ \1/' 1>&2 115 | } 116 | 117 | # We rewite the `notice` function to `heed` if `RPGVERBOSE` is enabled 118 | # after sourcing config files. 119 | notice () { true; } 120 | 121 | # Abort with a message and exit with failure. 122 | die () { warn "$@"; exit 1; } 123 | 124 | # Ruby Related Utility Functions 125 | # ------------------------------ 126 | 127 | # Retrieve a rbconfig value. 128 | rbconfig () { $RUBY -rrbconfig -e "puts RbConfig::CONFIG['$1']"; } 129 | 130 | # The file extension for dynamic libraries on this operating system. 131 | # e.g., `so` on Linux, `dylib` on MacOS. 132 | ruby_dlext() { echo "$RUBYDLEXT"; } 133 | 134 | # The command that should be executed to run `ruby`. This is used to 135 | # rewrite shebang lines. 136 | # 137 | # TODO this is only used in rpg-build, which should be changed. 138 | ruby_command () { 139 | command -v ruby 2>/dev/null || 140 | echo "/usr/bin/env ruby" 141 | } 142 | 143 | # Misc Utility Functions 144 | # ---------------------- 145 | 146 | # `readlink(1)` for systems that don't have it. 147 | readlink () { 148 | test -L "$1" 149 | command readlink "$1" 2>/dev/null || { 150 | _p=$(ls -l "$1") 151 | echo ${_p##* -> } 152 | } 153 | } 154 | 155 | # Alias `yes`, `no`, `1`, `0` to `true` and `false` so options can be 156 | # set to any of those values. 157 | yes () { true; } 158 | no () { false; } 159 | alias 1=true 160 | alias 0=false 161 | 162 | # Turn on the shell's built in tracing facilities if RPGTRACE is enabled. 163 | rpg_init () { 164 | ${RPGTRACE:-false} && set -x 165 | 166 | ${RPGVERBOSE:-false} && { 167 | notice () { heed "$@"; } 168 | } 169 | 170 | true 171 | } 172 | 173 | # This is replaced with the `config.sh` file that's generated when the 174 | # `./configure` script is run. It includes a bunch of environment variables 175 | # for program paths and defaults for the `RPGPATH`, `RPGBIN`, `RPGLIB`, etc. 176 | # options. 177 | : __RPGCONFIG__ 178 | 179 | # rpg's default installation and database locations are based on the 180 | # currently active ruby environment. We use Ruby's `rbconfig` module to 181 | # load the `bin`, `lib`, `man`, and `var` directories then set and export 182 | # the `__RPGENV__` variable so that we only do this once per rpg 183 | # process hierarchy. 184 | # 185 | # Any of the variables exported below may be used in `rpgrc` config files to 186 | # determine the best locations for various RPG paths. 187 | test -n "$__RPGENV__" && { rpg_init; return 0; } 188 | 189 | PATH="${libexecdir}:$PATH" 190 | RUBY="$(command -v ruby 2>/dev/null || echo "${RUBY:-ruby}")" 191 | __RPGENV__="$RUBY" 192 | 193 | eval "$( 194 | $RUBY <<__RUBY__ 195 | require 'rbconfig' 196 | conf = RbConfig::CONFIG 197 | puts " 198 | RUBYPREFIX='#{conf['prefix']}' 199 | RUBYDLEXT='#{conf['DLEXT']}' 200 | RUBYARCH='#{conf['arch']}' 201 | RUBYSITEDIR='#{conf['sitelibdir']}' 202 | RUBYVENDORDIR='#{conf['vendorlibdir']}' 203 | RUBYMANDIR='#{conf['mandir']}' 204 | RUBYBINDIR='#{conf['bindir']}' 205 | RUBYSTATEDIR='#{conf['localstatedir']}' 206 | RUBYVERSION='#{conf['ruby_version']}' 207 | RUBYLIBDIR='#{File.dirname(conf['rubylibdir'])}' 208 | " 209 | __RUBY__ 210 | )" 211 | 212 | # Determine if this is the MacOS Ruby framework 213 | RUBYMACFRAMEWORK=false 214 | expr -- "$RUBYPREFIX" : "/System/Library/Frameworks" >/dev/null && { 215 | RUBYMACFRAMEWORK=true 216 | RUBYLIBDIR=/usr/lib/ruby 217 | RUBYSTATEDIR=$RUBYLIBDIR 218 | RUBYSITEDIR=/usr/lib/ruby/site_ruby/$RUBYVERSION 219 | RUBYVENDORDIR=/usr/lib/ruby/vendor_ruby/$RUBYVERSION 220 | RUBYPREFIX=/usr 221 | RUBYBINDIR=$RUBYPREFIX/bin 222 | RUBYMANDIR=$RUBYPREFIX/share/man 223 | } 224 | 225 | export __RPGENV__ RUBY 226 | export RUBYPREFIX RUBYDLEXT RUBYARCH RUBYSITEDIR RUBYVENDORDIR RUBYMANDIR RUBYBINDIR 227 | export RUBYSTATEDIR RUBYLIBDIR RUBYVERSION RUBYMACFRAMEWORK 228 | 229 | # With `configure --development`, set all paths to be inside a work dir. 230 | if $develmode 231 | then 232 | : ${RPGPATH:="$prefix"} 233 | : ${RPGLIB:="$RPGPATH/lib"} 234 | : ${RPGMAN:="$RPGPATH/man"} 235 | : ${RPGBIN:="$RPGPATH/bin"} 236 | fi 237 | 238 | # Configuration Files 239 | # ------------------- 240 | 241 | # The system configuration file. Sourced near the end of this script. This 242 | # is one of the variables that the configure script overrides. 243 | : ${RPGSYSCONF:=/etc/rpgrc} 244 | 245 | # The user configuration file. Sourced immediately after the system 246 | # configuration file. 247 | : ${RPGUSERCONF:=~/.rpgrc} 248 | 249 | # Store current RPG environment settings 250 | : oldrpgenv=$(env | grep '^RPG') 251 | 252 | # Source the system `/etc/rpgrc` file. 253 | test -r "$RPGSYSCONF" && { . "$RPGSYSCONF" || true; } 254 | 255 | # Source the user `~/.rpgrc` file. 256 | test -r "$RPGUSERCONF" && { . "$RPGUSERCONF" || true; } 257 | 258 | # Restore previous RPG settings 259 | eval "${oldrpgenv}" 260 | 261 | # Install Paths 262 | # ------------- 263 | 264 | # This is the psuedo root directory where `rpg` keeps all its stuff. The 265 | # default locations of other rpg paths use this as a base. *HOWEVER*, 266 | # no rpg utility targets this directory -- every significant location must 267 | # have a separate path variable so that things stay flexible in 268 | # configuration. 269 | : ${RPGPATH:=${rpgdir:-$( 270 | if $RUBYMACFRAMEWORK 271 | then echo "/Library/Ruby/RPG/$RUBYVERSION" 272 | else echo "${RUBYLIBDIR:-/var/lib}/rpg" 273 | fi 274 | )}} 275 | 276 | # `RPGLIB` is the shared Ruby `lib` directory where library files are 277 | # installed. It defaults to the currently active ruby's `vendor_ruby` 278 | # directory (or `site_ruby` when Ruby < 1.8.7). If neither of those 279 | # locations be determined for some reason, `RPGPATH/lib` is assumed. 280 | if test -n "$rpgdir" 281 | then 282 | : ${RPGLIB:=$rpgdir/lib} 283 | else 284 | : ${RPGLIB:="${RUBYVENDORDIR:-${RUBYSITEDIR:-$RPGPATH/lib}}"} 285 | fi 286 | 287 | # `RPGBIN` is where executable scripts included in packages are installed. 288 | # It defaults to the currently active ruby's `bindir` and falls back to 289 | # `RPGPATH/bin` if no ruby `bindir` can be determined. 290 | if test -n "$rpgdir" 291 | then 292 | : ${RPGBIN:=$rpgdir/bin} 293 | else 294 | : ${RPGBIN:="${RUBYBINDIR:-$RPGPATH/bin}"} 295 | fi 296 | 297 | # `RPGMAN` is where manpages included with packages are installed. This 298 | # is basically the whole reason `rpg` was written in the first place. 299 | if test -n "$rpgdir" 300 | then 301 | : ${RPGMAN:=$RPGPATH/man} 302 | else 303 | : ${RPGMAN:="${RUBYMANDIR:-$RPGPATH/man}"} 304 | fi 305 | 306 | # RPG Paths 307 | # --------- 308 | 309 | # `RPGCACHE` is where `rpg-fetch(1)` looks for and stores gem files. 310 | # Set this to your Rubygems `cache` directory to share the gem cache. 311 | : ${RPGCACHE:=$RPGPATH/cache} 312 | 313 | # `RPGPACKS` is where `rpg-install(1)` unpacks gems before installing. The 314 | # package directories are not used after package installation is complete. 315 | : ${RPGPACKS:=$RPGPATH/packs} 316 | 317 | # `RPGDB` is where the local package database is kept. It's a 318 | # filesystem hierarchy. It looks like this: 319 | # 320 | # $ rpg-env sh -c 'cd $RPGDB && tree' 321 | # RPGDB 322 | # |-- bcrypt-ruby 323 | # | |-- 2.1.2 324 | # | | |-- authors 325 | # | | |-- bindir 326 | # | | |-- date 327 | # | | |-- dependencies 328 | # | | |-- description 329 | # | | |-- email 330 | # | | |-- executables 331 | # | | |-- extensions 332 | # | | |-- files 333 | # | | |-- gemspec 334 | # | | |-- homepage 335 | # | | |-- manifest 336 | # | | |-- name 337 | # | | |-- platform 338 | # | | |-- require_paths 339 | # | | |-- summary 340 | # | | |-- test_files 341 | # | | `-- version 342 | # | `-- active -> 2.1.2 343 | # |-- do_sqlite3 344 | # | |-- 0.10.1.1 345 | # | | |-- authors 346 | # | | |-- bindir 347 | # | | |-- date 348 | # | | |-- dependencies 349 | # | | |-- description 350 | # | | |-- email 351 | # | | |-- executables 352 | # | | |-- extensions 353 | # | | |-- files 354 | # | | |-- gemspec 355 | # | | |-- homepage 356 | # | | |-- manifest 357 | # | | |-- name 358 | # | | |-- platform 359 | # | | |-- require_paths 360 | # | | |-- summary 361 | # | | |-- test_files 362 | # | | `-- version 363 | # | `-- active -> 0.10.1.1 364 | # `-- sinatra 365 | # |-- 0.9.6 366 | # | |-- authors 367 | # | |-- bindir 368 | # | |-- date 369 | # | |-- dependencies 370 | # | |-- description 371 | # | |-- email 372 | # | |-- executables 373 | # | |-- extensions 374 | # | |-- files 375 | # | |-- gemspec 376 | # | |-- homepage 377 | # | |-- manifest 378 | # | |-- name 379 | # | |-- platform 380 | # | |-- require_paths 381 | # | |-- summary 382 | # | |-- test_files 383 | # | `-- version 384 | # |-- 1.0.b 385 | # | |-- authors 386 | # | |-- bindir 387 | # | |-- date 388 | # | |-- dependencies 389 | # | |-- description 390 | # | |-- email 391 | # | |-- executables 392 | # | |-- extensions 393 | # | |-- files 394 | # | |-- gemspec 395 | # | |-- homepage 396 | # | |-- manifest 397 | # | |-- name 398 | # | |-- platform 399 | # | |-- require_paths 400 | # | |-- summary 401 | # | |-- test_files 402 | # | `-- version 403 | # `-- active -> 0.9.6 404 | # 405 | # The database is meant to be "stable". That is, you can write programs 406 | # that rely on this structure. Maybe it should be documented first. 407 | : ${RPGDB:=$RPGPATH/db} 408 | 409 | # `RPGINDEX` is where the index of available gems is kept. It's a 410 | # directory. The `rpg-sync(1)` program manages the files under it. 411 | # 412 | # * `release`: 413 | # All available packages and all versions of all packages. Each line is a 414 | # ` ` pair, separated by whitespace. The file is sorted 415 | # alphabetically by package name, reverse by version number, such that the 416 | # first line for a package is the most recent version. 417 | # 418 | # * `release-recent`: 419 | # The most recent versions of all packages. The format is otherwise 420 | # identical to the `release` file. This mostly exists so that 421 | # `join(1)` can be used on it. Otherwise, we'd just build it from 422 | # `release` when we needed it. 423 | # 424 | # * `prerelease`: 425 | # **NOT YET IMPLEMENTED.** 426 | # This is the same as `release` but includes only prelease packages. 427 | # 428 | # * `prerelease-recent`: 429 | # **NOT YET IMPLEMENTED.** 430 | # This is the same as `release-recent` but includes only prelease 431 | # packages. 432 | # 433 | : ${RPGINDEX:=$RPGPATH/index} 434 | 435 | # Path where installation session data is stored. The `rpg-prepare(1)` and 436 | # `rpg-install(1)` programs store information about the packages being 437 | # installed in subdirectories of this path. 438 | : ${RPGSESSION:=$RPGPATH/session} 439 | 440 | # Enable verbose logging to stderr. 441 | : ${RPGVERBOSE:=false} 442 | 443 | # Enable the shell's trace facility (`set -x`) in all rpg programs. 444 | : ${RPGTRACE:=false} 445 | 446 | # Show extconf.rb and make output when building extensions. 447 | : ${RPGSHOWBUILD:=false} 448 | 449 | # Default stale time for use with `rpg-sync -s`. Values can be stuff 450 | # like `10 days` or `10d`, `30 minutes` or `30m`. A number with no time 451 | # designator is considered in days. This value can also be `never`, in 452 | # which case the database will never be automatically sync'd in the 453 | # course of running other programs. 454 | : ${RPGSTALETIME:=14 days} 455 | 456 | # URL to the specs file used to build the package index. 457 | : ${RPGSPECSURL:='http://rubygems.org/specs.4.8.gz'} 458 | 459 | # URL to used to fetch gems. 460 | : ${RPGGEMURL:='http://rubygems.org/downloads'} 461 | 462 | # Export all RPG variables. 463 | export RPGLIB RPGBIN RPGMAN 464 | export RPGPATH RPGCACHE RPGPACKS RPGDB RPGINDEX RPGSESSION 465 | export RPGTRACE RPGSHOWBUILD RPGSTALETIME RPGSPECSURL RPGGEMURL 466 | export RPGSYSCONF RPGUSERCONF 467 | 468 | # Setup logging and other stuff like that now that our variables are set. 469 | rpg_init 470 | 471 | # make sure we don't accidentally exit with a non-zero status 472 | : 473 | -------------------------------------------------------------------------------- /rpg-shit-list.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Modify package to work around issues 3 | set -e 4 | . rpg-sh-setup 5 | 6 | ARGV="$@" 7 | USAGE '${PROGNAME} 8 | Patch package to work with rpg.' 9 | 10 | package="$1" 11 | version="$2" 12 | path="$3" 13 | 14 | # Usage: `sedi ` 15 | # 16 | # Run `sed` on `` in-place. 17 | sedi () { 18 | sed "$1" < "$2" > "$2+" 19 | mv "$2+" "$2" 20 | } 21 | 22 | # Note that this package is on the shit-list. 23 | fixable () { 24 | heed "incompatible package detected: $package/$version (fixing)" 25 | test -n "$*" && notice "$*" 26 | } 27 | 28 | # Master list of shit list packages with hacks. 29 | case "$package" in 30 | haml) 31 | fixable "haml reads VERSION, VERSION_NAME, REVISION files from package root" 32 | cd "$path" 33 | revision=$(cat REVISION 2>/dev/null || true) 34 | vername=$(cat VERSION_NAME 2>/dev/null || true) 35 | sedi " 36 | s/File.read(scope('VERSION'))/'$version'/g 37 | s/File.read(scope('REVISION'))/'$revision'/g 38 | s/File.read(scope('VERSION_NAME'))/'$vername'/g 39 | " lib/haml/version.rb 40 | 41 | case "$version" in 42 | 3.1.*) 43 | fixable "haml 3.1.x bundles sass" 44 | cd "$path" 45 | rm -r lib/sass lib/sass.rb 46 | ;; 47 | esac 48 | ;; 49 | 50 | json|json_pure) 51 | fixable "json includes ext/ in require_paths" 52 | echo "lib" > "$RPGDB/$package/$version/require_paths" 53 | ;; 54 | 55 | memcached) 56 | fixable "memcached.rb reads VERSION file from package root" 57 | cd "$path" 58 | sedi "s/VERSION = File.read.*/VERSION = '$version'/" lib/memcached.rb 59 | ;; 60 | 61 | capistrano) 62 | fixable "capistrano/version.rb reads VERSION file from package root" 63 | cd "$path" 64 | sedi "s/CURRENT = /CURRENT = '$version' #/" lib/capistrano/version.rb 65 | sedi "s/ require 'rubygems'//" lib/capistrano/ssh.rb 66 | sedi "s/ gem 'net-ssh', \">= 2.0.10\"//" lib/capistrano/ssh.rb 67 | ;; 68 | 69 | mongrel) 70 | # The mongrel_rails executable acts more like a library. Rails and other 71 | # commands require it to be on the load path. It also doesn't have a shebang 72 | # so is executed with /bin/sh by default. We move it over to the lib 73 | # directory -- sub'ing some LOAD_PATH modifications on the way -- and then 74 | # write simple executable wrapper. 75 | test -f "$path/lib/mongrel_rails" || { 76 | fixable "mongrel_rails is more library than executable" 77 | cd "$path" 78 | sed 's/\($LOAD_PATH.unshift\)/# \1/' lib/mongrel_rails 79 | printf "#!$(ruby_command)\nload 'mongrel_rails'" >bin/mongrel_rails 80 | chmod +x bin/mongrel_rails 81 | } 82 | ;; 83 | 84 | RedCloth) 85 | fixable "RedCloth.rb clobbers original redcloth.rb" 86 | cd "$path" 87 | rm -rf "lib/case_sensitive_require" 88 | rm -rf "lib/tasks" 89 | ;; 90 | 91 | sass) 92 | fixable "sass reads VERSION, VERSION_NAME, REVISION files from package root" 93 | cd "$path" 94 | revision=$(cat REVISION 2>/dev/null || true) 95 | vername=$(cat VERSION_NAME 2>/dev/null || true) 96 | sedi " 97 | s/File.read(scope('VERSION'))/'$version'/g 98 | s/File.read(scope('REVISION'))/'$revision'/g 99 | s/File.read(scope('VERSION_NAME'))/'$vername'/g 100 | " lib/sass/version.rb 101 | ;; 102 | 103 | SystemTimer) 104 | fixable "system_timer.rb and system_timer_stub.rb requires rubygems" 105 | cd "$path" 106 | sedi "s/require 'rubygems'//" lib/system_timer.rb 107 | sedi "s/require 'rubygems'//" lib/system_timer_stub.rb 108 | ;; 109 | 110 | thin) 111 | fixable "thin assumes thin_parser extension is in package root" 112 | cd "$path" 113 | sedi 's@require "#{Thin::ROOT}/thin_parser"@require "thin_parser"@' lib/thin.rb 114 | ;; 115 | 116 | taps) 117 | cd "$path" 118 | test -f VERSION.yml && { 119 | fixable "taps VERSION.yml" 120 | yaml=$(sed 's|$|\\|' < VERSION.yml) 121 | sedi "s/@@version_yml ||= /@@version_yml ||= YAML.load('$yaml') #/" \ 122 | lib/taps/config.rb 123 | } 124 | 125 | fixable "taps relative bin paths" 126 | sedi "s|bin_path = |bin_path = '$RPGBIN/schema' #|" lib/taps/utils.rb 127 | ;; 128 | 129 | esac 130 | 131 | # Make sure we exit with success. 132 | : 133 | -------------------------------------------------------------------------------- /rpg-solve.c: -------------------------------------------------------------------------------- 1 | /* Fast package list solver. 2 | * 3 | * THIS IS A MESS. SORRY. I STILL NEED TO CLEAN IT UP. 4 | * 5 | * TODO: 6 | * [ ] usage message 7 | * [ ] default to release index file 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "strnatcmp.h" 14 | 15 | enum OPER { lt, le, eq, ge, gt, st, err }; 16 | 17 | /* A package list entry. */ 18 | struct plent { 19 | int found; 20 | enum OPER oper; 21 | char pack[100]; 22 | char vers[50]; 23 | struct plent *next; 24 | }; 25 | 26 | /* Parse a comparison operator string and return the corresponding OPER 27 | * value. The err value is returned if the string is not a valid operator. 28 | */ 29 | static inline enum OPER 30 | operparse (char * oper) 31 | { 32 | enum OPER res = err; 33 | if ( oper[0] == '<' ) { 34 | if ( oper[1] == 0 ) { 35 | res = lt; 36 | } else if ( oper[1] == '=' ) { 37 | res = le; 38 | } 39 | } else if ( oper[0] == '>' ) { 40 | if ( oper[1] == 0 ) { 41 | res = gt; 42 | } else if ( oper[1] == '=' ) { 43 | res = ge; 44 | } 45 | } else if ( oper[0] == '=' && oper[1] == 0 ) { 46 | res = eq; 47 | } else if ( oper[0] == '~' && oper[1] == '>' ) { 48 | res = st; 49 | } 50 | 51 | return res; 52 | } 53 | 54 | /* Replace vers with its next successive version. */ 55 | inline void 56 | versucc (char * vers) { 57 | char *pdot = strrchr(vers, '.'); 58 | char *pend = NULL; 59 | 60 | if ( pdot == NULL ) { 61 | pdot = strrchr(vers, '\0'); 62 | *pdot = '.'; 63 | } 64 | 65 | pdot++; 66 | for (pend = pdot + 10; pdot < pend; pdot++) 67 | *pdot = '9'; 68 | *pdot = '\0'; 69 | } 70 | 71 | 72 | /* Normalize a version string by removing any trailing ".0" parts. This is 73 | * required to for proper behavior when comparing versions like: "3.1 >= 3.1.0". */ 74 | static void 75 | versnorm (char * vers) 76 | { 77 | char * pe = vers + strlen(vers) - 1; 78 | while (pe > vers) { 79 | if (*pe == '.') { 80 | *pe = '\0'; 81 | pe--; 82 | } else if (*pe == '0') { 83 | pe--; 84 | } else { 85 | break; 86 | } 87 | } 88 | 89 | return; 90 | } 91 | 92 | /* Expand squiggly comparisons into separate ge and lt comparisons. e.g., 93 | * foo ~> 0.2.3 would become foo >= 0.2.3 and foo < 0.3.*/ 94 | static void 95 | plsquig (struct plent * pe) 96 | { 97 | struct plent * pnew; 98 | while (pe) { 99 | if (pe->oper != st) { 100 | pe = pe->next; 101 | continue; 102 | } 103 | 104 | pe->oper = ge; 105 | pnew = malloc(sizeof(struct plent)); 106 | memcpy(pnew, pe, sizeof(struct plent)); 107 | pnew->oper = lt; 108 | versucc(pnew->vers); 109 | 110 | versnorm(pe->vers); 111 | pe->next = pnew; 112 | pe = pnew->next; 113 | } 114 | } 115 | 116 | /* Parse a package list from the stream. */ 117 | static struct plent * 118 | plparse (FILE * stream) 119 | { 120 | int res = 0, lineno = 0; 121 | struct plent *pe, *ppe = NULL, *pfe = NULL; 122 | char stroper[3]; 123 | const char * format = "%100s%*[ ]%2[~><=]%*[ ]%50s\n"; 124 | 125 | while (1) { 126 | lineno++; 127 | pe = malloc(sizeof(struct plent)); 128 | pe->found = 0; 129 | pe->next = NULL; 130 | 131 | res = fscanf(stream, format, pe->pack, stroper, pe->vers); 132 | if ( res == 3 ) { 133 | pe->oper = operparse(stroper); 134 | } else { 135 | free(pe); 136 | break; 137 | } 138 | 139 | if ( ppe ) ppe->next = pe; 140 | else pfe = pe; 141 | ppe = pe; 142 | } 143 | 144 | plsquig(pfe); 145 | return pfe; 146 | } 147 | 148 | 149 | /* Mark all package list entries whose package name matches pack as found. */ 150 | static inline void 151 | plmark (struct plent * pe, char * pack) 152 | { 153 | int cmp; 154 | for (; pe != NULL; pe = pe->next) { 155 | cmp = strcmp(pe->pack, pack); 156 | if (cmp == 0) { 157 | pe->found = 1; 158 | } else if (cmp < 0) { 159 | continue; 160 | } else { 161 | break; 162 | } 163 | } 164 | } 165 | 166 | /* Remove entries marked as found from the list. */ 167 | static struct plent * 168 | plpurge (struct plent * pe) { 169 | struct plent * ptail = NULL, 170 | * phead = NULL; 171 | 172 | for (;pe != NULL; pe = pe->next) { 173 | if ( pe->found ) continue; 174 | 175 | if ( ptail ) 176 | ptail->next = pe; 177 | else 178 | phead = pe; 179 | 180 | ptail = pe; 181 | } 182 | 183 | if (ptail) ptail->next = NULL; 184 | 185 | return phead; 186 | } 187 | 188 | /* Compare two versions. */ 189 | static inline int 190 | verscmp(char const * v1, char const * v2) 191 | { 192 | return strnatcmp(v1, v2); 193 | } 194 | 195 | /* Test if versions compare according to oper. */ 196 | static inline int 197 | verstest(enum OPER oper, char const * v1, char const * v2) 198 | { 199 | int res = 0; 200 | int cmp; 201 | if (oper == eq) { 202 | cmp = strcmp(v1, v2); 203 | if (cmp == 0) res = 1; 204 | } else { 205 | cmp = verscmp(v1, v2); 206 | if ((cmp == 0 && (oper == le || oper == ge)) || 207 | (cmp < 0 && (oper == lt || oper == le)) || 208 | (cmp > 0 && (oper == gt || oper == ge))) 209 | res = 1; 210 | } 211 | return res; 212 | } 213 | 214 | /* Run over all package list entries with the same name as ppack and compare 215 | * versions. 216 | */ 217 | static int 218 | pdxrun(char * ppack, char * pvers, struct plent * pe) 219 | { 220 | while ( pe && strcmp(ppack, pe->pack) == 0 ) { 221 | if (verstest(pe->oper, pvers, pe->vers)) { 222 | pe = pe->next; 223 | } else { 224 | return 0; 225 | } 226 | } 227 | return 1; 228 | } 229 | 230 | #define MAXLINE 256 231 | 232 | static void 233 | pdxscan(FILE * stream, struct plent * pe) 234 | { 235 | char line[MAXLINE]; 236 | char *ppack, *pvers; 237 | int cmp; 238 | 239 | while ( fgets(line, MAXLINE - 1, stream) ) { 240 | if (pe->pack[0] > line[0]) continue; 241 | 242 | pvers = line; 243 | ppack = strsep(&pvers, " "); 244 | while ((cmp = strcmp(pe->pack, ppack)) < 0) { 245 | pe = pe->next; 246 | if (pe == NULL) return; 247 | } 248 | 249 | if (cmp > 0) continue; 250 | 251 | pvers = strsep(&pvers, " \n"); 252 | if (pdxrun(ppack, pvers, pe)) { 253 | if ( pe->found == 0 ) 254 | plmark(pe, ppack); 255 | printf("%s %s\n", ppack, pvers); 256 | } 257 | } 258 | } 259 | 260 | #ifdef DEBUG 261 | static void 262 | pldump (FILE * stream, struct plent * pe) 263 | { 264 | fprintf(stream, "dumping package list entries:\n"); 265 | while (pe) { 266 | fprintf(stream, "plent: %s %d %s\n", pe->pack, pe->oper, pe->vers); 267 | pe = pe->next; 268 | } 269 | } 270 | #endif 271 | 272 | int main (int argc, char *argv[]) 273 | { 274 | struct plent * pe = plparse(stdin); 275 | FILE * fidx; 276 | int i; 277 | 278 | for (i=1; i < argc; i++) { 279 | /* pldump(stderr, pe) */ 280 | 281 | if (pe == NULL) 282 | break; 283 | 284 | if ((fidx = fopen(argv[i], "r"))) { 285 | pdxscan(fidx, pe); 286 | fclose(fidx); 287 | } else { 288 | fprintf(stderr, "%s: could not open: %s", argv[0], argv[i]); 289 | exit(1); 290 | } 291 | 292 | pe = plpurge(pe); 293 | } 294 | 295 | /* some packages were not found */ 296 | if ( pe ) { 297 | while ( pe ) { 298 | printf("%s -\n", pe->pack); 299 | pe = pe->next; 300 | } 301 | return 1; 302 | } 303 | 304 | return 0; 305 | } 306 | -------------------------------------------------------------------------------- /rpg-steal.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | . rpg-sh-setup 4 | 5 | ARGV="$@" 6 | USAGE '${PROGNAME} [-n] 7 | Transplant packages from Rubygems to rpg. 8 | 9 | With the -n option, show what would be installed but do not.' 10 | 11 | if test "$1" = '-n' 12 | then tampon="-n 2 echo" 13 | shift 14 | else tampon="rpg-install" 15 | fi 16 | 17 | gem list --local | 18 | sed "s|^\(${GEMNAME_BRE}\) *(\([$GEMVERS_BRE\).*|GEM \1 \2|" | 19 | grep '^GEM ' | 20 | sed 's/^GEM //' | 21 | xargs $tampon 22 | -------------------------------------------------------------------------------- /rpg-sync.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # The `rpg-sync` program is responsible for building the remote package 3 | # index and keeping it up to date. The package index is a set of simple text 4 | # files kept under the `RPGINDEX` directory. They include basic information 5 | # on all gems available at [rubygems.org](http://rubygems.org) and are 6 | # optimized for use with utilities like `grep(1)`, `sed(1)`, and `join(1)`. 7 | # 8 | # `rpg-sync` has two modes of operation: 9 | # 10 | # 1. Someone runs `rpg-sync` directly to bring the package index in sync 11 | # with the remote repository. If the command completes successfully, 12 | # the index is guaranteed to be up to date with the repository. 13 | # 14 | # 2. Some other rpg program (like `rpg-upgrade` or `rpg-list`) executes 15 | # `rpg-sync -s` before performing an operation on the index. In this 16 | # mode, `rpg-sync` attempts to determine if the index is overly stale 17 | # (based on the `RPGSTALETIME` option) and may or may not perform an 18 | # sync. 19 | # 20 | # Philosophy on Automatic Index Sync 21 | # ---------------------------------- 22 | # 23 | # Generally speaking, rpg's philosophy is that the user should control 24 | # when the package index is synchronized. The primary reason for this is that 25 | # rpg should be *fast* *fast* *fast* -- *fast* like a rocket missile -- unless 26 | # specifically told not to be. Network operations destroy any chance at 27 | # predictable performance. 28 | # 29 | # Rubygems's `gem` command has nearly the opposite philosophy. It tries 30 | # hard to make sure it's working with current data consistent with 31 | # the package repository when performing operations involving remote packages 32 | # (like `gem list --remote`, `gem outdated`, or `gem install`). This has 33 | # obvious benefits: running `gem install foo` is guaranteed to install the 34 | # most recent version of `foo` at the time the command is run. Similarly, 35 | # `gem outdated` is guaranteed to show the most recent package versions 36 | # available. This is convenient behavior because it removes the 37 | # responsibility of managing the package index from the user. The downside 38 | # is wildly unpredictable performance in most commands. 39 | # 40 | # rpg attempts to strike a balance between these two extremes in its default 41 | # configuration and can be customized to get any behavior along the spectrum. 42 | # By default, the package index is automatically synchronized when it's more 43 | # than two weeks old: 44 | # 45 | # # auto sync the package index when it's more than 14 days old 46 | # RPGSTALETIME=14d 47 | # 48 | # Setting the stale time to `0`, causes the index to be synchronized before 49 | # performing any operation that involves remote packages. This is closest to 50 | # the `gem` commands behavior: 51 | # 52 | # # keep the package index in sync 53 | # RPGSTALETIME=0 54 | # 55 | # Finally, automatic sync can be disabled completely with: 56 | # 57 | # # never auto sync the package index 58 | # RPGSTALETIME=never 59 | # 60 | # See the stale time code below for more information on acceptable values. 61 | 62 | set -e 63 | . rpg-sh-setup 64 | 65 | ARGV="$@" 66 | USAGE '${PROGNAME} [-v] [-m |-d |-s] 67 | Create or sync the remote package index. Maybe. 68 | 69 | Options 70 | -d Do nothing if db is less than days old 71 | -m Do nothing if db is less than minutes old 72 | -s Do nothing if db is less than $RPGSTALETIME old 73 | -F Strict failures when package index cannot be updated 74 | -v Write newly available packages to stdout after updating 75 | 76 | The -s option is used by various rpg commands when a sync may be 77 | necessary. Its default stale time can be configured by setting the 78 | RPGSTALETIME option in ~/.rpgrc or /etc/rpgrc.' 79 | 80 | verbose=false 81 | staletime= 82 | strict=false 83 | while getopts svFm:d: opt 84 | do 85 | case $opt in 86 | v) verbose=true;; 87 | m) staletime="$OPTARG min";; 88 | d) staletime="$OPTARG day";; 89 | F) strict=true;; 90 | s) staletime="$RPGSTALETIME";; 91 | ?) helpthem;; 92 | esac 93 | done 94 | shift $(($OPTIND - 1)) 95 | 96 | # Bail out if we have more args. 97 | [ "$*" ] && 98 | { warn "invalid argument: $*"; exit 2; } 99 | 100 | # The Release Index 101 | # ----------------- 102 | # 103 | # The `release` file includes all versions of all packages. One line per 104 | # ` ` pair. 105 | 106 | # Here's where the file is kept. There's also `release-recent` and 107 | # `release-diff` files, which we'll see in a second. 108 | release="$RPGINDEX/release" 109 | 110 | # Maybe bail out if a stale time was given. `RPGSTALETIME` values can be 111 | # stuff like `10 days` or `10d`, `30 minutes` or `30m`. A number with no 112 | # time designator is considered in days. When the value is `never`, 113 | # don't sync the index due to staleness in the course of running 114 | # other programs. 115 | if test "$staletime" 116 | then 117 | case "$staletime" in 118 | never|none) notice "index is in never auto sync mode" 119 | exit 0;; 120 | [0-9]*m*) fargs="-mmin -${staletime%%[!0-9]*}";; 121 | [0-9]*) fargs="-mtime -${staletime%%[!0-9]*}";; 122 | *) fargs="" 123 | warn "bad RPGSTALETIME value: '$staletime'. ignoring.";; 124 | esac 125 | 126 | if test -z "$(find "$release" -maxdepth 0 $fargs 2>/dev/null)" 127 | then notice "release index is missing or stale [> $staletime old]" 128 | if test -f "$release" 129 | then heed "package index is stale [> $staletime old]. retrieving now." 130 | else heed "package index not found. retrieving now." 131 | fi 132 | else notice "release index is fresh [< $staletime old]" 133 | exit 0 134 | fi 135 | else 136 | heed "retrieving package index: $RPGSPECSURL" 137 | fi 138 | 139 | # First thing we do, we create the `RPGINDEX` directory if it doesn't exist. 140 | test -d "$RPGINDEX" || { 141 | notice "creating index directory: $RPGINDEX" 142 | mkdir -p "$RPGINDEX" 143 | } 144 | 145 | notice "building release file: $release" 146 | 147 | # Fetching and Formatting The Spec Index 148 | # -------------------------------------- 149 | 150 | { 151 | 152 | # Fetch the latest specs file from rubygems.org. 153 | curl -sL "$RPGSPECSURL" | 154 | 155 | # Decompress. 156 | gzip -dc - | 157 | 158 | # Now turn this mess of Marshal data into something we can deal with using 159 | # `rpg-parse-index`. See that file for more info on the `specs.gz` file 160 | # format and the output from `rpg-parse-index`. 161 | rpg-parse-index | 162 | 163 | # We only want packages with a "ruby" platform. This may be too aggressive a 164 | # filter but seems to work fine in 99.9% of cases. 165 | grep ' ruby$' | 166 | 167 | # We don't need the platform, yet. Grab only the `` and `` 168 | # fields. After `cut`, our stream text looks like this: 169 | # 170 | # clown 0.0.8 171 | # ClsRuby 1.0.1 172 | # ClsRuby 1.0.0 173 | # clusterer 0.1.9 174 | # clusterer 0.1.0 175 | # clusterfuck 0.1.0 176 | # cmd 0.7.2 177 | # cmd 0.7.1 178 | # cmd 0.7.0 179 | # cmd_line_test 0.1.5 180 | # cmd_line_test 0.1.4 181 | # 182 | # One line per package and package version. Output is sorted on package name 183 | # and then reverse by version. 184 | # 185 | # Write that out to our staged release file so we can pass over it a bit. 186 | cut -d ' ' -f 1,2 187 | 188 | } > "$release+" 2>/dev/null 189 | 190 | # There's a chance that `curl` or `gzip` or something else in the above 191 | # pipeline will have failed. `set -e` won't catch that since it's not the 192 | # last command in the pipeline. Detect it by checking the contents of the 193 | # file and bail if there's nothing there. Exit with failure when the strict 194 | # option (-F) was given and also when the index doesn't already exist. 195 | if test -z "$(head -1 "$release+" 2>/dev/null)" 196 | then 197 | if $strict || ! test -f "$release" 198 | then heed "could not retrieve package index. failing." 199 | exit 1 200 | else heed "could not retrieve package index. using existing." 201 | exit 0 202 | fi 203 | fi 204 | 205 | # The Release Diff 206 | # ---------------- 207 | 208 | # We wrote the new index to a separate file, so we can take a quick diff 209 | # now. We can show the diff directly, which is awesome, but this could also 210 | # be used to roll back an update (`patch -R`) if something goes wrong. 211 | # 212 | # We also don't care that much if this doesn't work due to, e.g. 213 | # `diff(1)` not being available. 214 | notice "building release diff: $release-diff" 215 | 216 | diff -u "$release" "$release+" > "$release-diff+" 2>&1 && true 217 | 218 | # Write a list of new packages to stdout if the verbose flag was given. 219 | if $verbose 220 | then echo "# New packages:" 221 | grep '^+' < "$release-diff+" | 222 | grep -v '^++' | 223 | cut -c2- 224 | fi 225 | 226 | # The Recent Release Index 227 | # ------------------------ 228 | 229 | # The recent release index contains only the most recent versions of 230 | # release packages but otherwise identical to the `release` file. 231 | notice "building recent release index [$release-recent+]" 232 | 233 | # Since the big release index comes down from the server with versions 234 | # in reverse order (most recent first), we can push it through `sort(1)` 235 | # using the `` field as the key (only) and have it uniq the list 236 | # down for us. `sort -u` uses the first line with a distinct `` 237 | # name and discards adjacent matches, leaving us with a sorted list of 238 | # the most recent versions. 239 | sort -u -b -k 1,1 < "$release+" > "$release-recent+" 240 | 241 | # Commit 242 | # ------ 243 | 244 | # Move the new index files into place. 245 | notice "committing new release index files..." 246 | for file in "$release-diff" "$release-recent" "$release" 247 | do mv "$file+" "$file" 248 | done 249 | notice "index rebuild complete" 250 | 251 | # Write some stats on the number of packages available, both total and 252 | # newly available since the last sync. 253 | packs="$(grep -c . <"$release-recent" || true)" 254 | new="$(grep -e '^+[^+]' "$release-diff" | { grep -c . || true; })" 255 | message="complete. $packs packages available." 256 | test "$new" -gt 0 && message="$message +$new since last sync." 257 | heed "$message" 258 | 259 | # Careful now. 260 | : 261 | -------------------------------------------------------------------------------- /rpg-uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | . rpg-sh-setup 4 | 5 | [ "$*" ] || set -- --help; ARGV="$@" 6 | USAGE '${PROGNAME} ... 7 | Uninstall packages from local system.' 8 | 9 | # If more than one package was given, re-exec for each package: 10 | test $# -gt 1 && { 11 | echo "$@" | xargs -n 1 rpg-uninstall 12 | exit $? 13 | } 14 | 15 | package="$1" 16 | packagedir="$RPGDB/$package" 17 | manifest="$packagedir/active/manifest" 18 | 19 | # Bail out if the db doesn't have this package or the package 20 | # isn't active. 21 | test -d "$packagedir" -a -f "$manifest" || { 22 | warn "$package is not installed" 23 | exit 1 24 | } 25 | 26 | # Grab the currently installed version from the active symlink. 27 | version=$(readlink "$packagedir/active") 28 | notice "$package $version" 29 | 30 | # Remove all files installed by this package 31 | grep -v '^#' <"$packagedir/active/manifest" | 32 | if $RPGVERBOSE 33 | then 34 | while read file 35 | do notice "$file [unlink]" 36 | echo "$file" 37 | done 38 | else 39 | cat 40 | fi | 41 | { xargs -P 4 -n 1 unlink || true; } 42 | 43 | # Cleanup empty directories 44 | find $RPGLIB -depth -type d -empty -exec rmdir {} \; 45 | 46 | # Unlink the active symlink 47 | unlink "$packagedir/active" 48 | 49 | true 50 | -------------------------------------------------------------------------------- /rpg-unpack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # The `rpg-unpack` program reads the gem file's internal tar-based structure 3 | # and either untars into a new directory or writes the data segment's tar 4 | # stream to stdout. 5 | # 6 | # Gem files are more or less normal tarballs that looks like this: 7 | # 8 | # $ tar tv < sinatra-0.9.6.gem 9 | # -rw-r--r-- wheel/wheel 117190 1969-12-31 16:00:00 data.tar.gz 10 | # -rw-r--r-- wheel/wheel 1225 1969-12-31 16:00:00 metadata.gz 11 | # 12 | # The `metadata.gz` file is a gzip compressed YAML gemspec. The 13 | # `data.tar.gz` holds the unprefixed files. 14 | # 15 | # There's also an older gem format apparently, but I'm hoping to not have to 16 | # deal with it. 17 | set -e 18 | . rpg-sh-setup 19 | 20 | USAGE '${PROGNAME} [-p |-P] [] 21 | ${PROGNAME} -c [-m] [] 22 | Unpack a gem file to disk or as a tar stream on standard output. 23 | 24 | Options 25 | -c Write gem data tar stream to stdout. Do not create any files. 26 | -f Unpack over an existing directory. 27 | -m Change the behavior of the -c option. Write gem metadata 28 | segment instead of the data segment. 29 | -n Do nothing if unpack directory already exists. 30 | -p Unpack under instead of the working directory. 31 | -P Unpack under RPGPACKS instead of the working directory. 32 | 33 | The may be a package name or path to a gem file on disk. When a 34 | package name is given, the may also be specified.' 35 | workdir=. 36 | filter=untar 37 | segment=data.tar.gz 38 | noop=false 39 | force=false 40 | while getopts cfmnPp: opt 41 | do 42 | case $opt in 43 | p) workdir="$OPTARG";; 44 | P) workdir="$RPGPACKS";; 45 | c) filter=cat;; 46 | m) segment=metadata.gz;; 47 | f) force=true;; 48 | n) noop=true;; 49 | ?) helpthem 50 | exit 2;; 51 | esac 52 | done 53 | shift $(( $OPTIND - 1 )) 54 | 55 | # Piping the gemspec through tar isn't going to help anyone. Fail fast. 56 | if test $segment = "metadata.gz" -a $filter = "untar" 57 | then warn "illegal argument: -m must be used with -c" 58 | exit 2 59 | fi 60 | 61 | # Check whether a gem file or package name was given. 62 | if expr "$1" : '.*\.gem' >/dev/null 63 | then file="$1" 64 | test -r "$file" || { 65 | warn "gem file can not be read: $file" 66 | exit 1 67 | } 68 | else file=$(rpg-fetch "$1" "${2:->0}") 69 | fi 70 | 71 | # Extract the package name and version from the gem file. 72 | basename=$(basename "$file" .gem) 73 | package=${basename%-*} 74 | version=${basename##*-} 75 | 76 | # This takes the gem's `data.tar` stream on stdin and untars it into a 77 | # newly created directory after the gem name. When the `-c` option is not 78 | # given, the gem tar stream is piped through here. 79 | untar () { 80 | if $force 81 | then mkdir -p "$workdir/$package-$version" 82 | else mkdir "$workdir/$package-$version" 83 | fi 84 | tar -xom -C "$workdir/$package-$version" -f - 2>/dev/null 85 | } 86 | 87 | # Bail out with just the unpack directory on stdout if the -n option was 88 | # given. 89 | $noop && test "$filter" = "untar" -a -d "$workdir/$package-$version" && { 90 | notice "$workdir/$package-$version already exists. noop'ing." 91 | echo "$workdir/$package-$version" 92 | exit 0 93 | } 94 | 95 | # Pipe the gem directly into `tar` and extract only the file/segment we're 96 | # interested in (the `-O` option causes the file to be written to stdout 97 | # instead of to disk). Next, pipe that thing through gzip to decompress and 98 | # finally into whatever filter was configured (`cat` with the `-c` option or 99 | # our `untar` function above otherwise). 100 | tar -xOmf - $segment < "$file" 2>/dev/null | 101 | gzip -dc | 102 | $filter 103 | 104 | # Write the path to the unpacked package directory on standard output. 105 | if test "$filter" = "untar" 106 | then echo "$workdir/$package-$version" 107 | fi 108 | 109 | true 110 | -------------------------------------------------------------------------------- /rpg-upgrade.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | . rpg-sh-setup 4 | 5 | ARGV="$@" 6 | USAGE '${PROGNAME} [-u] [...] 7 | Upgrade packages to the latest available version. With no , upgrade 8 | all outdated packages. 9 | 10 | Options 11 | -u Sync the remote package index to be sure the latest version 12 | is available.' 13 | 14 | # Update the package index. Force the update right now with the `-u` 15 | # arg; otherwise, maybe update it based on the configured stale time. 16 | if test "$1" = '-u' 17 | then rpg-sync 18 | shift 19 | else rpg-sync -s 20 | fi 21 | 22 | # Have `rpg-list` generate a list of all installed package with parseable 23 | # output. Grab only outdated packages and pass them all to `rpg-install`. 24 | rpg-list -p "$@" | 25 | awk '/^o / { print $2, $4 }' | 26 | xargs rpg-install 27 | -------------------------------------------------------------------------------- /rpg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # The main rpg user executable. `rpg` sets environment variables for global 3 | # options and execs child commands. 4 | set -e 5 | 6 | # This usage message only lists porcelainish commands. The rpg(1) manpage 7 | # documents all commands in detail. 8 | PROGNAME="$(basename $0)" 9 | usage="Usage: ${PROGNAME} [-vx] [-c ] [...] 10 | Manage gem packages, quickly. 11 | 12 | The most commonly used rpg commands are: 13 | config Show or edit rpg configuration 14 | dependencies Show dependency information for a package or all packages 15 | install Install a package from file or remote repository 16 | list Show status of local packages vs. respository 17 | steal Transplant packages from Rubygems into rpg environment 18 | sync Sync the package index with repository 19 | outdated List packages with a newer version 20 | uninstall Uninstall packages from local system 21 | upgrade Upgrade installed packages to latest version 22 | 23 | Options 24 | -c Read rcfile at instead of standard rpgrc locations 25 | -v Enable verbose logging to stderr 26 | -q Disable verbose logging to stderr (when enabled in config) 27 | -x Enable shell tracing to stderr (extremely verbose) 28 | 29 | See \`${PROGNAME} help ' for more information on a specific command." 30 | 31 | [ "$*" ] || set -- "--help" 32 | 33 | # Look for --help before the child command. 34 | for a in "$@" 35 | do 36 | case "$a" in 37 | --h|--he|--hel|--help|-h|-\?) echo "$usage"; exit 0;; 38 | -*) continue;; 39 | *) break;; 40 | esac 41 | done 42 | 43 | # Global options. 44 | while getopts qvxc: opt 45 | do 46 | case $opt in 47 | c) export RPGRCFILE="$OPTARG";; 48 | v) export RPGVERBOSE=true;; 49 | x) export RPGTRACE=true;; 50 | q) export RPGVERBOSE=false;; 51 | ?) echo "$usage" 52 | exit 2;; 53 | esac 54 | done 55 | shift $(( $OPTIND - 1 )) 56 | 57 | # This is replaced by the generated config.sh file at build time. 58 | : __RPGCONFIG__ 59 | 60 | # Bring in the rpg sh library. 61 | . "$bindir"/rpg-sh-setup 62 | 63 | # Shift off the first argument to determine the real command: 64 | command="$1" 65 | shift 66 | 67 | # Exec the command or exit with failure if the command doesn't exist. 68 | exec "$libexecdir/rpg-${command}" "$@" 69 | -------------------------------------------------------------------------------- /strnatcmp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Taken, and modified somewhat, from: 3 | * 4 | * http://sourcefrog.net/projects/natsort/ 5 | * 6 | * strnatcmp.c -- Perform 'natural order' comparisons of strings in C. 7 | * Copyright (C) 2000, 2004 by Martin Pool 8 | * 9 | * This software is provided 'as-is', without any express or implied 10 | * warranty. In no event will the authors be held liable for any damages 11 | * arising from the use of this software. 12 | * 13 | * Permission is granted to anyone to use this software for any purpose, 14 | * including commercial applications, and to alter it and redistribute it 15 | * freely, subject to the following restrictions: 16 | * 17 | * 1. The origin of this software must not be misrepresented; you must not 18 | * claim that you wrote the original software. If you use this software 19 | * in a product, an acknowledgment in the product documentation would be 20 | * appreciated but is not required. 21 | * 2. Altered source versions must be plainly marked as such, and must not be 22 | * misrepresented as being the original software. 23 | * 3. This notice may not be removed or altered from any source distribution. 24 | */ 25 | 26 | /* partial change history: 27 | * 28 | * 2004-10-10 mbp: Lift out character type dependencies into macros. 29 | * 30 | * Eric Sosman pointed out that ctype functions take a parameter whose 31 | * value must be that of an unsigned int, even on platforms that have 32 | * negative chars in their default char type. 33 | */ 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include "strnatcmp.h" 41 | 42 | 43 | /* These are defined as macros to make it easier to adapt this code to 44 | * different characters types or comparison functions. */ 45 | static inline int 46 | nat_isdigit(nat_char a) 47 | { 48 | return isdigit((unsigned char) a); 49 | } 50 | 51 | 52 | static inline int 53 | nat_isspace(nat_char a) 54 | { 55 | return isspace((unsigned char) a); 56 | } 57 | 58 | 59 | static inline nat_char 60 | nat_toupper(nat_char a) 61 | { 62 | return toupper((unsigned char) a); 63 | } 64 | 65 | 66 | 67 | static int 68 | compare_right(nat_char const *a, nat_char const *b) 69 | { 70 | int bias = 0; 71 | 72 | /* The longest run of digits wins. That aside, the greatest 73 | value wins, but we can't know that it will until we've scanned 74 | both numbers to know that they have the same magnitude, so we 75 | remember it in BIAS. */ 76 | for (;; a++, b++) { 77 | if (!nat_isdigit(*a) && !nat_isdigit(*b)) 78 | return bias; 79 | else if (!nat_isdigit(*a)) 80 | return -1; 81 | else if (!nat_isdigit(*b)) 82 | return +1; 83 | else if (*a < *b) { 84 | if (!bias) 85 | bias = -1; 86 | } else if (*a > *b) { 87 | if (!bias) 88 | bias = +1; 89 | } else if (!*a && !*b) 90 | return bias; 91 | } 92 | 93 | return 0; 94 | } 95 | 96 | 97 | static int 98 | compare_left(nat_char const *a, nat_char const *b) 99 | { 100 | /* Compare two left-aligned numbers: the first to have a 101 | different value wins. */ 102 | for (;; a++, b++) { 103 | if (!nat_isdigit(*a) && !nat_isdigit(*b)) 104 | return 0; 105 | else if (!nat_isdigit(*a)) 106 | return -1; 107 | else if (!nat_isdigit(*b)) 108 | return +1; 109 | else if (*a < *b) 110 | return -1; 111 | else if (*a > *b) 112 | return +1; 113 | } 114 | 115 | return 0; 116 | } 117 | 118 | 119 | static int 120 | strnatcmp0(nat_char const *a, nat_char const *b, int fold_case) 121 | { 122 | int ai, bi; 123 | nat_char ca, cb; 124 | int fractional, result; 125 | 126 | assert(a && b); 127 | ai = bi = 0; 128 | while (1) { 129 | ca = a[ai]; cb = b[bi]; 130 | 131 | /* skip over leading spaces or zeros */ 132 | while (nat_isspace(ca)) 133 | ca = a[++ai]; 134 | 135 | while (nat_isspace(cb)) 136 | cb = b[++bi]; 137 | 138 | /* process run of digits */ 139 | if (nat_isdigit(ca) && nat_isdigit(cb)) { 140 | fractional = (ca == '0' || cb == '0'); 141 | 142 | if (fractional) { 143 | if ((result = compare_left(a+ai, b+bi)) != 0) 144 | return result; 145 | } else { 146 | if ((result = compare_right(a+ai, b+bi)) != 0) 147 | return result; 148 | } 149 | } 150 | 151 | if (!ca && !cb) { 152 | /* The strings compare the same. Perhaps the caller 153 | will want to call strcmp to break the tie. */ 154 | return 0; 155 | } 156 | 157 | if (fold_case) { 158 | ca = nat_toupper(ca); 159 | cb = nat_toupper(cb); 160 | } 161 | 162 | if (ca < cb) 163 | return -1; 164 | else if (ca > cb) 165 | return +1; 166 | 167 | ++ai; ++bi; 168 | } 169 | } 170 | 171 | 172 | 173 | int strnatcmp(nat_char const *a, nat_char const *b) { 174 | return strnatcmp0(a, b, 0); 175 | } 176 | 177 | 178 | /* Compare, recognizing numeric string and ignoring case. */ 179 | int strnatcasecmp(nat_char const *a, nat_char const *b) { 180 | return strnatcmp0(a, b, 1); 181 | } 182 | -------------------------------------------------------------------------------- /strnatcmp.h: -------------------------------------------------------------------------------- 1 | /* -*- mode: c; c-file-style: "k&r" -*- 2 | 3 | strnatcmp.c -- Perform 'natural order' comparisons of strings in C. 4 | Copyright (C) 2000, 2004 by Martin Pool 5 | 6 | This software is provided 'as-is', without any express or implied 7 | warranty. In no event will the authors be held liable for any damages 8 | arising from the use of this software. 9 | 10 | Permission is granted to anyone to use this software for any purpose, 11 | including commercial applications, and to alter it and redistribute it 12 | freely, subject to the following restrictions: 13 | 14 | 1. The origin of this software must not be misrepresented; you must not 15 | claim that you wrote the original software. If you use this software 16 | in a product, an acknowledgment in the product documentation would be 17 | appreciated but is not required. 18 | 2. Altered source versions must be plainly marked as such, and must not be 19 | misrepresented as being the original software. 20 | 3. This notice may not be removed or altered from any source distribution. 21 | */ 22 | 23 | 24 | /* CUSTOMIZATION SECTION 25 | * 26 | * You can change this typedef, but must then also change the inline 27 | * functions in strnatcmp.c */ 28 | typedef char nat_char; 29 | 30 | int strnatcmp(nat_char const *a, nat_char const *b); 31 | int strnatcasecmp(nat_char const *a, nat_char const *b); 32 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | trash 2 | -------------------------------------------------------------------------------- /test/specs.4.8.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rtomayko/rpg/4be73897e223214f8c4adc482e43707d9b357cfb/test/specs.4.8.gz -------------------------------------------------------------------------------- /test/test-lib.sh: -------------------------------------------------------------------------------- 1 | 2 | : ${VERBOSE:=false} 3 | 4 | unset CDPATH 5 | 6 | cd "$(dirname $0)" 7 | TESTDIR=$(pwd) 8 | 9 | test_count=0 10 | successes=0 11 | failures=0 12 | 13 | output="$testdir/$(basename "$0" .sh).out" 14 | trap "rm -f $output" 0 15 | 16 | succeeds () { 17 | test_count=$(( test_count + 1 )) 18 | echo "\$ ${2:-$1}" > "$output" 19 | eval "( ${2:-$1} )" 1>>"$output" 2>&1 20 | ec=$? 21 | if test $ec -eq 0 22 | then successes=$(( successes + 1 )) 23 | printf 'ok %d - %s\n' $test_count "$1" 24 | else failures=$(( failures + 1 )) 25 | printf 'not ok %d - %s [%d]\n' $test_count "$1" "$ec" 26 | fi 27 | 28 | $VERBOSE && dcat $output 29 | return 0 30 | } 31 | 32 | fails () { 33 | if test $# -eq 1 34 | then succeeds "! $1" 35 | else succeeds "$1" "! $2" 36 | fi 37 | } 38 | 39 | diag () { echo "$@" | sed 's/^/# /'; } 40 | dcat () { cat "$@" | sed 's/^/# /'; } 41 | desc () { diag "$@"; } 42 | 43 | # setup environment for a fake rpg environment under ./trash 44 | RPGPATH="$TESTDIR/trash" 45 | RPGBIN="$RPGPATH/bin" 46 | RPGLIB="$RPGPATH/lib" 47 | RPGMAN="$RPGPATH/man" 48 | RPGDB="$RPGPATH/db" 49 | RPGINDEX="$RPGPATH/index" 50 | RPGPACKS="$RPGPATH/packs" 51 | RPGCACHE="$RPGPATH/cache" 52 | export RPGPATH RPGBIN RPGLIB RPGMAN RPGDB RPGINDEX RPGPACKS RPGCACHE 53 | 54 | RPGSYSCONF=false 55 | RPGUSERCONF=false 56 | RPGTRACE=false 57 | RPGSHOWBUILD=false 58 | RPGSTALETIME='1 day' 59 | RPGSPECSURL="file://$TESTDIR/specs.4.8.gz" 60 | export RPGSYSCONF RPGUSERCONF RPGTRACE RPGSHOWBUILD RPGSTALETIME RPGSPECSURL 61 | 62 | # put source directory on PATH so we're running the right rpg commands 63 | PATH="$(dirname $TESTDIR):$PATH" 64 | export PATH 65 | 66 | setup () { 67 | rm -rf "$TESTDIR/trash" 68 | return 0 69 | } 70 | -------------------------------------------------------------------------------- /test/test-rpg.sh: -------------------------------------------------------------------------------- 1 | # 2 | . ./test-lib.sh 3 | 4 | desc 'main rpg command tests' 5 | setup 6 | 7 | succeeds 'rpg' 8 | succeeds 'rpg --help' 9 | fails 'passing invalid arguments' 'rpg -X' 10 | 11 | succeeds 'enabling verbose mode with -v' 'rpg -v help' 12 | succeeds 'enabling trace mode with -x' 'rpg -x help' 13 | 14 | succeeds 'rpg config' 15 | succeeds 'rpg env' 16 | succeeds 'rpg sync' 17 | succeeds 'rpg list' 18 | succeeds 'rpg prepare rails' 19 | succeeds 'rpg install rails' 20 | succeeds 'rpg fsck' 21 | --------------------------------------------------------------------------------