rbp 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +194 -0
- data/bin/rbp +7 -0
- data/lib/rbp.rb +7 -0
- data/lib/rbp/command.rb +153 -0
- data/lib/rbp/dependency.rb +19 -0
- data/lib/rbp/dependency_installer.rb +123 -0
- data/lib/rbp/dependency_uninstaller.rb +40 -0
- data/lib/rbp/git_dependency.rb +18 -0
- data/lib/rbp/installer.rb +16 -0
- data/lib/rbp/package.rb +225 -0
- data/lib/rbp/package_installer.rb +0 -0
- data/lib/rbp/package_manager.rb +198 -0
- metadata +58 -0
data/README.md
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
rbp: Ruby Package (manager)
|
2
|
+
===========================
|
3
|
+
|
4
|
+
rbp is a package manager for ruby. It is an alternative to rubygems as
|
5
|
+
it installs dependencies locally to your application to make version
|
6
|
+
management easier. Packages can also be installed globally, but the
|
7
|
+
default is to keep all dependencies tidly inside your app directory.
|
8
|
+
|
9
|
+
rbp is **not** required at runtime which also removes the additonal
|
10
|
+
loading required to make rbp operate. All packages are installed into
|
11
|
+
the `vendor/packages` directory inside your local directory.
|
12
|
+
|
13
|
+
**rbp is still in pre-pre-pre-pre-alpha**
|
14
|
+
|
15
|
+
Installation
|
16
|
+
------------
|
17
|
+
|
18
|
+
rbp can be installed directly by the following command. This will just
|
19
|
+
download the latest version of rbp and install its `lib` and `bin`
|
20
|
+
directories into the siteruby directory for your ruby install. Aslong as
|
21
|
+
these are in the loadpath (which they should be), then rbp will just
|
22
|
+
work.
|
23
|
+
|
24
|
+
To install rbp just do:
|
25
|
+
|
26
|
+
```
|
27
|
+
$ curl -s https://raw.github.com/adambeynon/rbp/master/setup.rb | ruby
|
28
|
+
```
|
29
|
+
|
30
|
+
If you use rbenv or rvm then you may need to run this command for
|
31
|
+
each ruby install. To make sure rbp is working, just run:
|
32
|
+
|
33
|
+
```
|
34
|
+
$ rbp version
|
35
|
+
```
|
36
|
+
|
37
|
+
If the command cannot be found, check your shells PATH variable.
|
38
|
+
|
39
|
+
How it works
|
40
|
+
-------------
|
41
|
+
|
42
|
+
In your app directory there will be a `vendor/packages` directory where
|
43
|
+
each dependancy is stored in its own directory by name. For example:
|
44
|
+
|
45
|
+
```
|
46
|
+
some_app/
|
47
|
+
|-bin/
|
48
|
+
|-lib/
|
49
|
+
|-vendor/
|
50
|
+
|-packages/
|
51
|
+
|-init.rb
|
52
|
+
|-rake/
|
53
|
+
|-otest/
|
54
|
+
```
|
55
|
+
|
56
|
+
Installing or removing (or updating) packages will be done through the
|
57
|
+
`rbp` command, which is simply a ruby library. rbp is only needed during
|
58
|
+
development - not production. Of course, the packages directory should be
|
59
|
+
added to `.gitignore` (or similar).
|
60
|
+
|
61
|
+
### init.rb
|
62
|
+
|
63
|
+
`init.rb` in the packages directory is where all the magic happens. As
|
64
|
+
packages are added or removed, their load paths are registered inside
|
65
|
+
init.rb automatically by rbp, so when your package code runs, all the
|
66
|
+
relevant load paths are setup so you can just require() away. Your
|
67
|
+
packages own load path is also added to this file, so simply requiring
|
68
|
+
this file will add your library to the load path as well.
|
69
|
+
|
70
|
+
|
71
|
+
### Why use init.rb?
|
72
|
+
|
73
|
+
Ruby can only load files from loadpaths that are set on the `$:` array.
|
74
|
+
Rubygems works by overriding `require` to perform dynamic lookup for
|
75
|
+
libraries. rbp does not have any runtime component, so dependencies must
|
76
|
+
be in the loadpath on running. To do this, `init.rb` adds all
|
77
|
+
dependency load paths automatically for you. The only requirement is
|
78
|
+
that init.rb is run straight away, and this is what the `rbp` command
|
79
|
+
does.
|
80
|
+
|
81
|
+
When deploying an application, `rbp` will not be available - and here
|
82
|
+
you have two choices. Considering deployed applications will not be used
|
83
|
+
as libraries themselves, you can load init.rb in your app directly. For
|
84
|
+
example, in rails you might do this in config.ru:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
File.expand_path('../vendor/packages.init.rb', __FILE__)
|
88
|
+
```
|
89
|
+
|
90
|
+
which will run the init.rb file for you, so all dependencies will be
|
91
|
+
ready to run. Also, this allows you to use a vendor version of rails
|
92
|
+
itself which may be very specific to your app (2.3 vs 3.0 vs 3.1 etc).
|
93
|
+
|
94
|
+
When a dependency is not found on the loadpath, a fallback on rubygems
|
95
|
+
may be used to use a global gem as the last option (probably will happen
|
96
|
+
as a lot of ruby installs will load rubygems automatically).
|
97
|
+
|
98
|
+
### Local development
|
99
|
+
|
100
|
+
rbp comes with a handy command `irb` which will open irb with the init
|
101
|
+
file already added to the load path so you can just require your app
|
102
|
+
dependencies directly. For example:
|
103
|
+
|
104
|
+
```
|
105
|
+
$ rbp irb
|
106
|
+
|
107
|
+
irb> require 'dependency_1'
|
108
|
+
```
|
109
|
+
|
110
|
+
To run bin files either inside your local package, or an included
|
111
|
+
dependency, the `exec` command will setup your load paths as well. Run
|
112
|
+
from the command line:
|
113
|
+
|
114
|
+
```
|
115
|
+
$ rbp exec bin_from_a_dependency arg1 arg2 arg3
|
116
|
+
```
|
117
|
+
|
118
|
+
To get all dependencies on the load path, init.rb needs to be called.
|
119
|
+
rbp does this for you by wrapping common ruby commands, but any
|
120
|
+
additional methods might mean having to require init.rb directly. For
|
121
|
+
example, a Rakefile:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
require File.expand_path('../vendor/packages/init.rb')
|
125
|
+
require 'your_lib_name'
|
126
|
+
```
|
127
|
+
|
128
|
+
When your lib requires its dependencies they will already be on the load
|
129
|
+
path thanks to init.rb.
|
130
|
+
|
131
|
+
**NOTE:** init.rb also adds your actual app/packages load path into the
|
132
|
+
ruby load paths as well so you don't have to.
|
133
|
+
|
134
|
+
### Listing dependencies and package.yml
|
135
|
+
|
136
|
+
An apps dependencies are listed in its package.yml file, which is in the
|
137
|
+
base directory. It is based off/inspired by commonjs' package.json. rbp
|
138
|
+
doesn't use gemspecs because it aims to be a replacement/alternative to
|
139
|
+
rubygems.
|
140
|
+
|
141
|
+
Dependencies are listed as a hash in the package.yml file and it can
|
142
|
+
list either their required version numbers or a git url. Packages with
|
143
|
+
versions numbers are download directly from rubygems.org server and
|
144
|
+
converted from gems into packages.
|
145
|
+
|
146
|
+
### Example init.rb
|
147
|
+
----------------
|
148
|
+
|
149
|
+
**Note:** this is created automatically by rbp - you do not need to
|
150
|
+
create this yourself.
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
# your packages' path
|
154
|
+
path = File.expand_path('..', __FILE__)
|
155
|
+
|
156
|
+
# your packages' lib
|
157
|
+
$:.unshift File.expand_path("#{path}/../lib")
|
158
|
+
|
159
|
+
# dependency 1: `otest'
|
160
|
+
$:.unshift File.expand_path("#{path}/../packages/otest/lib")
|
161
|
+
|
162
|
+
# dependency 2: `rake'
|
163
|
+
$:.unshift File.expand_path("#{path}/../packages/rake/lib")
|
164
|
+
```
|
165
|
+
|
166
|
+
Installing packages globally
|
167
|
+
----------------------------
|
168
|
+
|
169
|
+
Some packages may want to be installed globally, mainly as a
|
170
|
+
compatibility with the way rubygems does things. For example, rails
|
171
|
+
generates a template directory for you, so it may not be possible to add
|
172
|
+
rails as a dependency before you setup your dir structure. For this
|
173
|
+
reason, global packages are supported.
|
174
|
+
|
175
|
+
### Using rubygems as a 'legacy' system
|
176
|
+
|
177
|
+
For the short term, global packages in rbp will just be gem
|
178
|
+
installations - rbp will just search for, and install, gems. This will
|
179
|
+
allow any package that is not in the local packages directory to be
|
180
|
+
loaded by rubygems itself - we could add warnings stating that you are
|
181
|
+
using a global package. If you then install a different version locally,
|
182
|
+
then that will be used instead, as its lib path is already on the load
|
183
|
+
path before rubygems searches for an alternative.
|
184
|
+
|
185
|
+
In the longer term, the plan is to replace rubygems and global packages
|
186
|
+
will be installed in a similar manner to rubygem packages, including
|
187
|
+
wrapped bin files which allow this dynamic lib lookup. When local
|
188
|
+
packages then need to use globally installed ones, a **very** smal
|
189
|
+
runtime component from rvm can be loaded to assist lib lookup.
|
190
|
+
|
191
|
+
Global packages should still be used for development purposes, and the
|
192
|
+
idea will be to copy all required global packages to the local directory
|
193
|
+
ready for production.
|
194
|
+
|
data/bin/rbp
ADDED
data/lib/rbp.rb
ADDED
data/lib/rbp/command.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'rbp'
|
2
|
+
require 'rbp/package'
|
3
|
+
require 'rbp/package_manager'
|
4
|
+
require 'rbp/dependency'
|
5
|
+
require 'rbp/git_dependency'
|
6
|
+
require 'rbp/dependency_installer'
|
7
|
+
require 'rbp/dependency_uninstaller'
|
8
|
+
|
9
|
+
module Rbp
|
10
|
+
class Command
|
11
|
+
# All valid commands that can be run from command line
|
12
|
+
COMMANDS = [:install, :exec, :irb, :ls, :version]
|
13
|
+
|
14
|
+
# Creates a nw instance of the command utility which will then delegate
|
15
|
+
# to sub commands (methods) to handle individual commands.
|
16
|
+
#
|
17
|
+
# @param [Array<String>] args command line args
|
18
|
+
def initialize(args)
|
19
|
+
command = args.shift
|
20
|
+
|
21
|
+
if command and COMMANDS.include?(command.to_sym)
|
22
|
+
__send__ command.to_sym, *args
|
23
|
+
else
|
24
|
+
help
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def install(*)
|
29
|
+
manager = PackageManager.new
|
30
|
+
manager.install_dependencies
|
31
|
+
end
|
32
|
+
|
33
|
+
# Takes a Gem dependency and installs it into the local packages/
|
34
|
+
# directory.
|
35
|
+
#
|
36
|
+
# The installed gem/package is then inspected and all of its load
|
37
|
+
# paths are registered in init.rb so they are available at runtime.
|
38
|
+
#
|
39
|
+
# Finally, all of the dependecies of the dependency given will be
|
40
|
+
# installed.
|
41
|
+
|
42
|
+
def install_dependency(dependency, force = false)
|
43
|
+
name = dependency.name
|
44
|
+
path = package_install_path(name)
|
45
|
+
|
46
|
+
if File.exists? path
|
47
|
+
unless force
|
48
|
+
info "Skipping `#{name}'"
|
49
|
+
return
|
50
|
+
end
|
51
|
+
|
52
|
+
info "Removing `#{name}'"
|
53
|
+
uninstall_dependency name
|
54
|
+
end
|
55
|
+
|
56
|
+
spec = dependency.matching_specs.first
|
57
|
+
|
58
|
+
if spec and File.exists? spec.full_gem_path
|
59
|
+
info "Linking `#{name}'"
|
60
|
+
cp_r spec.full_gem_path, path
|
61
|
+
else
|
62
|
+
spec = install_remote_dependency dependency
|
63
|
+
end
|
64
|
+
|
65
|
+
register_load_paths spec
|
66
|
+
|
67
|
+
puts "going over dependencies.."
|
68
|
+
|
69
|
+
spec.runtime_dependencies.each do |dep|
|
70
|
+
install_dependency dep
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Takes a Gem dependency and installs it into the local packages/
|
76
|
+
# directory. This will **always** go out and retrieve a remote gem
|
77
|
+
# matching the dependency, it will never link to a locally installed
|
78
|
+
# one.
|
79
|
+
#
|
80
|
+
# This can be seen as sandboxing a gem installation just to the local
|
81
|
+
# package - nothing is done globally.
|
82
|
+
#
|
83
|
+
# This will then return the spec file for the installed dependency as
|
84
|
+
# it is used for registering load paths etc
|
85
|
+
|
86
|
+
def install_remote_dependency(dependency)
|
87
|
+
name = dependency.name
|
88
|
+
dest = package_install_path(name)
|
89
|
+
info "Installing `#{name}'"
|
90
|
+
|
91
|
+
spec, uri = begin
|
92
|
+
ins = Gem::DependencyInstaller.new
|
93
|
+
ins.find_spec_by_name_and_version(name, dependency.requirement)[0]
|
94
|
+
rescue Gem::GemNotFoundException => e
|
95
|
+
abort "Could not find dependency `#{name}'"
|
96
|
+
end
|
97
|
+
|
98
|
+
gem_path = Gem::RemoteFetcher.fetcher.download(spec, uri, gems_path)
|
99
|
+
|
100
|
+
installer = Gem::Installer.new(gem_path, :unpack => true)
|
101
|
+
installer.unpack dest
|
102
|
+
installer.build_extensions
|
103
|
+
|
104
|
+
# erm, the installer somehow gets a different spec..?
|
105
|
+
return installer.spec
|
106
|
+
end
|
107
|
+
|
108
|
+
# Execute a bin file in (local?) packages/
|
109
|
+
#
|
110
|
+
# Currently hardcoded to load bin file in current packages actual
|
111
|
+
# bin directory. Using bins from dependencies not yet supported.
|
112
|
+
#
|
113
|
+
# Usage:
|
114
|
+
#
|
115
|
+
# rbp exec bin_name arg1 arg2 arg3
|
116
|
+
#
|
117
|
+
# This command will also require the init.rb in the packages
|
118
|
+
# directory of the local package so all libs, including
|
119
|
+
# dependencies, will be available.
|
120
|
+
def exec(bin, *arg)
|
121
|
+
command = "ruby -r ./vendor/packages/init.rb bin/#{bin} #{arg.join ''}"
|
122
|
+
puts " exec `#{command}'\n\n"
|
123
|
+
system command
|
124
|
+
end
|
125
|
+
|
126
|
+
# This command will simply open a regular irb session, passing
|
127
|
+
# any given arguments.
|
128
|
+
#
|
129
|
+
# The init.rb file will automatically be required so all
|
130
|
+
# installed dependencies, and the local package itself, will be
|
131
|
+
# ready on the load path.
|
132
|
+
def irb(*args)
|
133
|
+
command = "irb -r ./vendor/packages/init.rb #{args.join ' '}"
|
134
|
+
system command
|
135
|
+
end
|
136
|
+
|
137
|
+
# List all dependencies of this package
|
138
|
+
def ls
|
139
|
+
raise "not implemented"
|
140
|
+
end
|
141
|
+
|
142
|
+
# version info
|
143
|
+
def version
|
144
|
+
puts Rbp.version
|
145
|
+
end
|
146
|
+
|
147
|
+
# Simply print help
|
148
|
+
def help
|
149
|
+
puts "Usage: rbp <command>"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Rbp
|
2
|
+
class Dependency
|
3
|
+
# dependency name, i.e. package name
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
# version required of dependency, e.g. 0.1.0
|
7
|
+
attr_reader :version
|
8
|
+
|
9
|
+
def initialize(name, version = nil)
|
10
|
+
@name = name
|
11
|
+
@version = version || ">= 0"
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
"<#{self.class} name=#{name.inspect} version=#{version.inspect}>"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'rubygems/dependency_installer'
|
4
|
+
|
5
|
+
module Rbp
|
6
|
+
class DependencyInstaller
|
7
|
+
|
8
|
+
attr_reader :package
|
9
|
+
|
10
|
+
def initialize(dependency)
|
11
|
+
@dependency = dependency
|
12
|
+
end
|
13
|
+
|
14
|
+
# Install the dependency into the given environment. The environment
|
15
|
+
# has all the paths and files etc needed for installing a dependency
|
16
|
+
# into.
|
17
|
+
#
|
18
|
+
# @param [Environment] environment the environment to install to
|
19
|
+
# @return [Package] returns the package for the installed dependency
|
20
|
+
def install(package_manager)
|
21
|
+
@package_manager = package_manager
|
22
|
+
|
23
|
+
case @dependency
|
24
|
+
when Dependency
|
25
|
+
install_dependency @dependency
|
26
|
+
when GitDependency
|
27
|
+
install_git_dependency @dependency
|
28
|
+
else
|
29
|
+
raise "Bad dependency to install"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def install_dependency(dependency)
|
36
|
+
gem_dep = Gem::Dependency.new dependency.name, dependency.version
|
37
|
+
name = dependency.name
|
38
|
+
destination = @package_manager.package_dir_for(name)
|
39
|
+
|
40
|
+
spec, uri = begin
|
41
|
+
ins = Gem::DependencyInstaller.new
|
42
|
+
ins.find_spec_by_name_and_version(name, gem_dep.requirement).first
|
43
|
+
rescue Gem::GemNotFoundException => e
|
44
|
+
raise "Could not find dependency `#{name}'"
|
45
|
+
end
|
46
|
+
|
47
|
+
tmp_path = File.join(Dir.tmpdir, "#{name}_#{Time.now.to_i}")
|
48
|
+
FileUtils.mkdir_p tmp_path
|
49
|
+
|
50
|
+
gem_path = Gem::RemoteFetcher.fetcher.download(spec, uri, tmp_path)
|
51
|
+
|
52
|
+
installer = Gem::Installer.new(gem_path, :unpack => true)
|
53
|
+
installer.unpack destination
|
54
|
+
installer.build_extensions
|
55
|
+
|
56
|
+
FileUtils.rm_rf tmp_path
|
57
|
+
|
58
|
+
@package = Package.from_gemspec installer.spec
|
59
|
+
descriptor_path = @package_manager.descriptor_file_for name
|
60
|
+
FileUtils.mkdir_p File.dirname(descriptor_path)
|
61
|
+
|
62
|
+
File.open(descriptor_path, 'w+') { |o| o.write @package.to_yaml }
|
63
|
+
|
64
|
+
register_load_paths @package
|
65
|
+
|
66
|
+
return @package
|
67
|
+
end
|
68
|
+
|
69
|
+
def install_git_dependency(dependency)
|
70
|
+
name = dependency.name
|
71
|
+
url = dependency.url
|
72
|
+
dest = @package_manager.package_dir_for(name)
|
73
|
+
|
74
|
+
system "git clone --quiet #{url} #{dest}"
|
75
|
+
|
76
|
+
gemspec = Dir["#{dest}/*.gemspec"].first
|
77
|
+
package = File.join dest, "package.yml"
|
78
|
+
|
79
|
+
if gemspec
|
80
|
+
raise "gemspec given"
|
81
|
+
elsif File.exists? package
|
82
|
+
@package = Package.load_path package
|
83
|
+
else
|
84
|
+
raise "need to make fake package"
|
85
|
+
@package = Package.new
|
86
|
+
end
|
87
|
+
|
88
|
+
register_descriptor @package
|
89
|
+
|
90
|
+
return @package
|
91
|
+
end
|
92
|
+
|
93
|
+
def register_descriptor(package)
|
94
|
+
descriptor_path = @package_manager.descriptor_file_for package.name
|
95
|
+
FileUtils.mkdir_p File.dirname(descriptor_path)
|
96
|
+
|
97
|
+
File.open(descriptor_path, 'w+') { |o| o.write package.to_yaml }
|
98
|
+
end
|
99
|
+
|
100
|
+
# Looks through the package and registers all the required load paths
|
101
|
+
# into the environment init.rb file.
|
102
|
+
#
|
103
|
+
# Currently this will just register the 'lib' directory, but it should
|
104
|
+
# really look at the vendor lib paths as well
|
105
|
+
#
|
106
|
+
# @param [Package] package the package to register
|
107
|
+
def register_load_paths(package)
|
108
|
+
name = package.name
|
109
|
+
paths = ['lib'] + package.vendor_lib_paths
|
110
|
+
|
111
|
+
code = paths.map do |p|
|
112
|
+
path = "packages/#{name}/#{p}"
|
113
|
+
"$:.unshift File.expand_path(\"\#{path}/#{path}\")\n"
|
114
|
+
end.join
|
115
|
+
|
116
|
+
out = @package_manager.init_file
|
117
|
+
read = File.read out
|
118
|
+
|
119
|
+
File.open(out, 'w+') { |o| o.write(read + code) }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Rbp
|
4
|
+
class DependencyUninstaller
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
@name = name
|
8
|
+
end
|
9
|
+
|
10
|
+
def uninstall(package_manager)
|
11
|
+
@package_manager = package_manager
|
12
|
+
|
13
|
+
unless package_manager.installed? @name
|
14
|
+
raise "No dependency to uninstall: `#{@name}'"
|
15
|
+
end
|
16
|
+
|
17
|
+
descriptor_path = package_manager.environment.descriptor_file_for(@name)
|
18
|
+
@package = Package.load_path descriptor_path
|
19
|
+
|
20
|
+
FileUtils.rm_rf package_manager.environment.package_dir_for(@name)
|
21
|
+
FileUtils.rm_f descriptor_path
|
22
|
+
|
23
|
+
unregister_load_paths @package
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def unregister_load_paths(package)
|
29
|
+
name = package.name
|
30
|
+
init = @package_manager.environment.init_file
|
31
|
+
|
32
|
+
amended = File.read(init).each_line.reject do |line|
|
33
|
+
line =~ /packages\/#{name}/
|
34
|
+
end.join
|
35
|
+
|
36
|
+
File.open(init, 'w+') { |o| o.write amended }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rbp
|
2
|
+
class GitDependency
|
3
|
+
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
attr_reader :url
|
7
|
+
|
8
|
+
def initialize(name, url)
|
9
|
+
@name = name
|
10
|
+
@url = url
|
11
|
+
end
|
12
|
+
|
13
|
+
def inspect
|
14
|
+
"<#{self.class} name=#{@name.inspect} url=#{@url.inspect}>"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems/dependency_installer'
|
2
|
+
|
3
|
+
module Rbp
|
4
|
+
class DependencyInstaller
|
5
|
+
def self.install(dependency, options = {})
|
6
|
+
installer = new
|
7
|
+
installer.install(dependency, options)
|
8
|
+
installer
|
9
|
+
end
|
10
|
+
|
11
|
+
def install(options)
|
12
|
+
name = dependency.name
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
data/lib/rbp/package.rb
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Rbp
|
4
|
+
# When package not found when loading Package
|
5
|
+
class PackageNotFoundError < StandardError; end
|
6
|
+
|
7
|
+
# A package is a gem
|
8
|
+
class Package
|
9
|
+
|
10
|
+
attr_reader :root
|
11
|
+
|
12
|
+
##
|
13
|
+
# The package's name.
|
14
|
+
#
|
15
|
+
# Most remote packages are wrapped as gems, so this name must be a
|
16
|
+
# valid rubygem name.
|
17
|
+
|
18
|
+
attr_accessor :name
|
19
|
+
|
20
|
+
# The package's version (as a string), e.g. "0.1.0"
|
21
|
+
attr_accessor :version
|
22
|
+
|
23
|
+
# @return [Array<Gem::Dependency>] an array of dependencies
|
24
|
+
attr_reader :dependencies
|
25
|
+
|
26
|
+
# Compatibility with Gem::Specification
|
27
|
+
alias_method :runtime_dependencies, :dependencies
|
28
|
+
|
29
|
+
# @return [Array<Gem::Dependency>] and array of development dependencies
|
30
|
+
attr_reader :dev_dependencies
|
31
|
+
|
32
|
+
# Compatibility with Gem::Specification
|
33
|
+
alias_method :development_dependencies, :dev_dependencies
|
34
|
+
|
35
|
+
# @return [String] path to the directory that this descriptor represents.
|
36
|
+
attr_reader :package_dir
|
37
|
+
|
38
|
+
# @return [Array<String>] array of additional load paths relevant to the
|
39
|
+
# package root. This is a way of supporting multiple load paths as offered
|
40
|
+
# by rubygems. Rbp doesn't technically support this, so if we pretend to
|
41
|
+
# support vendor paths, then this works nicely(ish).
|
42
|
+
attr_accessor :vendor_lib_paths
|
43
|
+
|
44
|
+
# Load a package from the given directory, or the current working
|
45
|
+
# directory if no path specified.
|
46
|
+
#
|
47
|
+
# @param [String] root the root directory to load package.yml from
|
48
|
+
# @return [Package] the package that is loaded
|
49
|
+
def self.load(root = Dir.getwd)
|
50
|
+
p = self.new
|
51
|
+
p.load_yaml root
|
52
|
+
p
|
53
|
+
end
|
54
|
+
|
55
|
+
# load some/path/to/package.yml (must given package.yml in filename
|
56
|
+
# as well)
|
57
|
+
def self.load_path(path)
|
58
|
+
p = self.new
|
59
|
+
|
60
|
+
unless File.exists? path
|
61
|
+
raise PackageNotFoundError, "Missing package file at `#{path}'"
|
62
|
+
end
|
63
|
+
|
64
|
+
p.load_yaml YAML.load(File.read(path))
|
65
|
+
|
66
|
+
p.package_dir = File.dirname(path)
|
67
|
+
|
68
|
+
p
|
69
|
+
end
|
70
|
+
|
71
|
+
# Creates a new Package instance
|
72
|
+
def initialize
|
73
|
+
@dependencies = []
|
74
|
+
@dev_dependencies = []
|
75
|
+
end
|
76
|
+
|
77
|
+
# @param [Hash] yml the yaml object (hash) to load
|
78
|
+
def load_yaml(yml)
|
79
|
+
@yml = yml
|
80
|
+
raise "Bad package.yml format" unless yml
|
81
|
+
|
82
|
+
@name = @yml['name']
|
83
|
+
@version = @yml['version']
|
84
|
+
|
85
|
+
# dependencies
|
86
|
+
if deps = @yml['dependencies']
|
87
|
+
raise "Bad dependencies hash in `#{@root}'" unless Hash === deps
|
88
|
+
|
89
|
+
deps.each do |name, version|
|
90
|
+
|
91
|
+
d = if version and version =~ /^git\:\/\//
|
92
|
+
GitDependency.new name, version
|
93
|
+
else
|
94
|
+
Dependency.new name, version
|
95
|
+
end
|
96
|
+
|
97
|
+
dependencies << d
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# dev dependencies
|
102
|
+
if deps = @yml['dev_dependencies']
|
103
|
+
raise "Bad dev_dependencies hash in `#{@root}'" unless Hash === deps
|
104
|
+
|
105
|
+
deps.each do |name, version|
|
106
|
+
|
107
|
+
d = if version and version =~ /^git\:\/\//
|
108
|
+
GitDependency.new name, version
|
109
|
+
else
|
110
|
+
Dependency.new name, version
|
111
|
+
end
|
112
|
+
|
113
|
+
dev_dependencies << d
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def to_s
|
119
|
+
"#<#{self.class} name=\"#{@name}\" version=\"#{@version}\">"
|
120
|
+
end
|
121
|
+
|
122
|
+
# Sets the directory for the package that this package descriptor
|
123
|
+
# represents.
|
124
|
+
#
|
125
|
+
# This may invalidate some other variables such as lib paths, so
|
126
|
+
# we reset all those variables here as well.
|
127
|
+
#
|
128
|
+
# @param [String] path the directory
|
129
|
+
def package_dir=(path)
|
130
|
+
@package_dir = path
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns an array of all lib directories for this package. By default
|
134
|
+
# this will just be `['lib']`, but additional paths may be added.
|
135
|
+
#
|
136
|
+
# Usage:
|
137
|
+
#
|
138
|
+
# package.lib_dirs
|
139
|
+
# # => ['lib', 'ext']
|
140
|
+
#
|
141
|
+
# @return [Array<String>] relative lib directories.
|
142
|
+
def lib_dirs
|
143
|
+
['lib']
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns an array of all files in the lib directory. Vendor directories
|
147
|
+
# are currently ignored.
|
148
|
+
#
|
149
|
+
# **Note:** These will be full paths, not relative.
|
150
|
+
#
|
151
|
+
# @return [Array<String>] array of paths.
|
152
|
+
def lib_files
|
153
|
+
Dir["#{package_dir}/lib/**/*.rb"]
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns an array of the relative lib files for this package. This
|
157
|
+
# takes the fully qualified libs and removes the package path. For
|
158
|
+
# example, this will return an array such as:
|
159
|
+
#
|
160
|
+
# ['lib/my_package.rb', 'lib/my_package/version.rb', 'ext/foo.rb']
|
161
|
+
#
|
162
|
+
# @return [Array<String>] array of relative paths.
|
163
|
+
def relative_lib_files
|
164
|
+
libs_regexp = /^#{package_dir}\/(.*)$/
|
165
|
+
|
166
|
+
lib_files.map do |lib|
|
167
|
+
match = libs_regexp.match lib
|
168
|
+
raise "Something went wrong in `relative_lib_files`" unless match
|
169
|
+
|
170
|
+
match[1]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# @return [Hash] returns a hash version of the package which can then
|
175
|
+
# be written to a yaml file.
|
176
|
+
def to_yaml
|
177
|
+
validate_package!
|
178
|
+
|
179
|
+
hash = {
|
180
|
+
'name' => name,
|
181
|
+
'version' => version.to_s
|
182
|
+
}
|
183
|
+
|
184
|
+
deps = {}
|
185
|
+
dev_deps = {}
|
186
|
+
|
187
|
+
dependencies.each { |dep| deps[dep.name] = dep.version.to_s }
|
188
|
+
dev_dependencies.each { |dep| dev_deps[dep.name] = dep.version.to_s }
|
189
|
+
|
190
|
+
hash['dependencies'] = deps
|
191
|
+
hash['dev_dependencies'] = dev_deps
|
192
|
+
|
193
|
+
hash.to_yaml
|
194
|
+
end
|
195
|
+
|
196
|
+
# Ensure this package is valid - needs to be done before install or
|
197
|
+
# before writing to file.
|
198
|
+
#
|
199
|
+
# FIXME: need to implement
|
200
|
+
def validate_package!
|
201
|
+
true
|
202
|
+
end
|
203
|
+
|
204
|
+
# Create a Package from the given gemspec file
|
205
|
+
def self.from_gemspec(spec)
|
206
|
+
p = self.new
|
207
|
+
|
208
|
+
p.name = spec.name
|
209
|
+
p.version = spec.version
|
210
|
+
|
211
|
+
spec.runtime_dependencies.each do |d|
|
212
|
+
p.dependencies << Dependency.new(d.name, d.requirement)
|
213
|
+
end
|
214
|
+
|
215
|
+
spec.development_dependencies.each do |d|
|
216
|
+
p.dev_dependencies << Dependency.new(d.name, d.requirement)
|
217
|
+
end
|
218
|
+
|
219
|
+
p.vendor_lib_paths = spec.require_paths.reject { |p| p == 'lib' }
|
220
|
+
|
221
|
+
p
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
File without changes
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Rbp
|
4
|
+
# PackageManager is used for installing, updating and managing packages
|
5
|
+
# installed locally inside an app/development package.
|
6
|
+
#
|
7
|
+
# The package manager can either be used through the command interface,
|
8
|
+
# or directly, but the cli tools are the recomended approach.
|
9
|
+
class PackageManager
|
10
|
+
include FileUtils
|
11
|
+
|
12
|
+
# @return [Package] the package for the local context
|
13
|
+
attr_reader :package
|
14
|
+
|
15
|
+
# @return [Array<Package>] an array of locally installed packages
|
16
|
+
attr_reader :installed_packages
|
17
|
+
|
18
|
+
# @return [Hash<String, Dependency>] a hash of packnames to their
|
19
|
+
# dependency. The top package registers its dependencies here, and
|
20
|
+
# as those are added then their dependencies are added recursively.
|
21
|
+
# We can use this to find dependency mismatches, i.e. when two
|
22
|
+
# packages depend on different versions of packages.
|
23
|
+
attr_reader :dependencies
|
24
|
+
|
25
|
+
# Returns a new package manager with the given root. The root is the
|
26
|
+
# local app dir which packages should be managed for. This defaults to
|
27
|
+
# the current working directory.
|
28
|
+
#
|
29
|
+
# @param [String] root the root directory
|
30
|
+
def initialize(root = Dir.getwd)
|
31
|
+
@root = root
|
32
|
+
@dependencies = {}
|
33
|
+
|
34
|
+
if @package = load_package(File.join(@root, 'package.yml'))
|
35
|
+
# register_package @package
|
36
|
+
end
|
37
|
+
|
38
|
+
# find installed packages
|
39
|
+
@installed_packages = Dir['vendor/packages/descriptors/*.yml'].map do |p|
|
40
|
+
Package.load_path p
|
41
|
+
end
|
42
|
+
end
|
43
|
+
# @return [String] the directory where our dependencies go within the
|
44
|
+
# local app dir. This is 'vendor/packages'.
|
45
|
+
def dependencies_dir
|
46
|
+
@dependencies_dir ||= File.join @root, 'vendor', 'packages'
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [String] the directory where all the package.yml for locally
|
50
|
+
# installed packages go. This is '$dependencies_dir/descriptors'
|
51
|
+
def descriptors_dir
|
52
|
+
@descriptors_dir ||= File.join dependencies_dir, 'descriptors'
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [String] the directory where actual packages are installed
|
56
|
+
# to. This is '$dependencies_dir/packages'
|
57
|
+
def packages_dir
|
58
|
+
@packages_dir ||= File.join dependencies_dir, 'packages'
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [String] the file where all the load paths are stored and
|
62
|
+
# manipulated. This is '$dependencies/init.rb'
|
63
|
+
def init_file
|
64
|
+
@init_file ||= File.join dependencies_dir, 'init.rb'
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [String] the directory where a package with the given name
|
68
|
+
# should be installed to. Will be '$dependencies/packages/$name'
|
69
|
+
def package_dir_for(name)
|
70
|
+
File.join packages_dir, name
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [String] the file where a packages' yaml will be written to.
|
74
|
+
# This will be `$dependencies/descriptors/$name.yml'
|
75
|
+
def descriptor_file_for(name)
|
76
|
+
File.join descriptors_dir, "#{name}.yml"
|
77
|
+
end
|
78
|
+
|
79
|
+
# Tries to load a package with the given name in the given directory.
|
80
|
+
# If it can't, it returns nil. We don't throw an error as some methods
|
81
|
+
# use this just to see if one exists.
|
82
|
+
#
|
83
|
+
# @param [String] path the full path to the package
|
84
|
+
# @return [Package] the package loaded
|
85
|
+
def load_package(path)
|
86
|
+
begin
|
87
|
+
Package.load_path path
|
88
|
+
rescue PackageNotFoundError => e
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Install all of the dependencies for this package recursively. This
|
94
|
+
# will start with the top level package and install all of its
|
95
|
+
# packages, and then those packages' dependencies.
|
96
|
+
#
|
97
|
+
# If a dependency has already been installed, then it is skipped.
|
98
|
+
def install_dependencies
|
99
|
+
raise "Cannot find root package" unless @package
|
100
|
+
|
101
|
+
bootstrap
|
102
|
+
|
103
|
+
install_dependencies_for @package, true
|
104
|
+
end
|
105
|
+
|
106
|
+
# do the work for a given package..
|
107
|
+
def install_dependencies_for(package, include_dev = false)
|
108
|
+
deps = package.dependencies
|
109
|
+
deps += package.dev_dependencies if include_dev
|
110
|
+
|
111
|
+
puts "* Need to install for `#{package.name}'"
|
112
|
+
|
113
|
+
deps.each do |dep|
|
114
|
+
if installed? dep.name
|
115
|
+
log "Skipping #{dep.name}"
|
116
|
+
next
|
117
|
+
end
|
118
|
+
|
119
|
+
pkg = install_dependency dep
|
120
|
+
puts "installed: #{pkg}"
|
121
|
+
|
122
|
+
install_dependencies_for pkg
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Checkes whether a package with the given name has been installed or
|
127
|
+
# not. Returns true/false.
|
128
|
+
def installed?(name)
|
129
|
+
@installed_packages.each { |p| return true if p.name == name }
|
130
|
+
|
131
|
+
false
|
132
|
+
end
|
133
|
+
|
134
|
+
private
|
135
|
+
|
136
|
+
# @return [Package] returns the package for the installed dependency
|
137
|
+
def install_dependency(dependency = nil, force = false)
|
138
|
+
|
139
|
+
name = dependency.name
|
140
|
+
|
141
|
+
if installed? name
|
142
|
+
raise "Already installed `#{name}'" unless force
|
143
|
+
|
144
|
+
uninstaller = DependencyUninstaller.new name
|
145
|
+
|
146
|
+
log "Uninstalling `#{name}'"
|
147
|
+
uninstaller.uninstall self
|
148
|
+
end
|
149
|
+
|
150
|
+
installer = DependencyInstaller.new dependency
|
151
|
+
|
152
|
+
log "Installing `#{name}'"
|
153
|
+
package = installer.install self
|
154
|
+
|
155
|
+
return package
|
156
|
+
end
|
157
|
+
|
158
|
+
# This setups the local environment by ensuring that the packages/
|
159
|
+
# directory and init.rb files are ready to use. This should not be
|
160
|
+
# called directly as methods that require the environment call it
|
161
|
+
# automatically.
|
162
|
+
#
|
163
|
+
# The `force` option can be used to reset the environment. This will
|
164
|
+
# simply remove the existing packages directory, and recreate all
|
165
|
+
# needed files. This will therefore require all dependencies to be
|
166
|
+
# re-installed locally.
|
167
|
+
#
|
168
|
+
# @param [Boolean] force whether to clear the environment to start
|
169
|
+
# from scratch.
|
170
|
+
def bootstrap(force = false)
|
171
|
+
packages_dir = self.packages_dir
|
172
|
+
init_file = self.init_file
|
173
|
+
|
174
|
+
rm_rf packages_dir if force and File.exists? packages_dir
|
175
|
+
return if File.exists?(packages_dir) && File.exists?(init_file)
|
176
|
+
|
177
|
+
mkdir_p packages_dir
|
178
|
+
|
179
|
+
File.open(init_file, 'w+') do |o|
|
180
|
+
init = <<-OUT
|
181
|
+
# root packages' path
|
182
|
+
path = File.expand_path('..', __FILE__)
|
183
|
+
|
184
|
+
# root packages' lib directory
|
185
|
+
$:.unshift File.expand_path("\#{path}/../../lib")
|
186
|
+
OUT
|
187
|
+
|
188
|
+
o.write init.each_line.map { |l| l.lstrip }.join("")
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Simple log we can control with @verbose
|
193
|
+
def log(str)
|
194
|
+
puts str
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rbp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Adam Beynon
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-09-22 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: A ruby package manager for managing local packages
|
15
|
+
email:
|
16
|
+
- adam@adambeynon.com
|
17
|
+
executables:
|
18
|
+
- rbp
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- bin/rbp
|
23
|
+
- lib/rbp/command.rb
|
24
|
+
- lib/rbp/dependency.rb
|
25
|
+
- lib/rbp/dependency_installer.rb
|
26
|
+
- lib/rbp/dependency_uninstaller.rb
|
27
|
+
- lib/rbp/git_dependency.rb
|
28
|
+
- lib/rbp/installer.rb
|
29
|
+
- lib/rbp/package.rb
|
30
|
+
- lib/rbp/package_installer.rb
|
31
|
+
- lib/rbp/package_manager.rb
|
32
|
+
- lib/rbp.rb
|
33
|
+
- README.md
|
34
|
+
homepage: http://opalscript.org
|
35
|
+
licenses: []
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 1.8.10
|
55
|
+
signing_key:
|
56
|
+
specification_version: 3
|
57
|
+
summary: A ruby package manager for managing local packages
|
58
|
+
test_files: []
|