rbp 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|