rbp 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rbp'
4
+ require 'rbp/command'
5
+
6
+ Rbp::Command.new ARGV
7
+
@@ -0,0 +1,7 @@
1
+ require 'rbp/package'
2
+
3
+ module Rbp
4
+ VERSION = "0.0.1"
5
+ def self.version; VERSION; end
6
+ end
7
+
@@ -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
+
@@ -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: []