ocra 1.2.0 → 1.3.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,20 @@
1
+ === 1.3.0.rc1
2
+
3
+ * Fixed some additional corner cases with absolute and relative
4
+ require & load paths. Extended test suite to cover a lot more
5
+ cases.
6
+
7
+ * Now provides a meaningful exit status code (1 on error, 0 on
8
+ success). (DavidMikeSimon)
9
+
10
+ * New option to _not_ run the script to detect dependencies
11
+ (--no-dep-run). (DavidMikeSimon)
12
+
13
+ * Bundler support using the --gemfile option. (DavidMikeSimon)
14
+
15
+ * Debug mode support in the stub (--debug). Also --debug-extract to
16
+ keep extracted files from executable. (DavidMikeSimon)
17
+
1
18
  === 1.2.0
2
19
 
3
20
  * Ignore console events (Ctrl-C, Ctrl-Break). Ruby process handles
@@ -1,6 +1,6 @@
1
1
  History.txt
2
2
  Manifest.txt
3
- README.txt
3
+ README.rdoc
4
4
  Rakefile
5
5
  bin/ocra
6
6
  share/ocra/lzma.exe
@@ -13,30 +13,62 @@ source code. The executable is a self-extracting, self-running
13
13
  executable that contains the Ruby interpreter, your source code and
14
14
  any additionally needed ruby libraries or DLL.
15
15
 
16
- == FEATURES/PROBLEMS:
16
+ == FEATURES
17
17
 
18
18
  * LZMA Compression (optional, default on)
19
- * Windows support only
20
19
  * Ruby 1.9 support
21
20
  * Both windowed/console mode supported
21
+ * Includes gems based on usage, or from a Bundler Gemfile
22
22
 
23
- If you experience problems with OCRA or have found a bug, please use
24
- the issue tracker on GitHub
25
- (http://github.com/larsch/ocra/issues). You can also join the Google
26
- Group discussion forum to ask questions and get help
27
- (http://groups.google.com/group/ruby-ocra).
23
+ == PROBLEMS & BUG REPORTING:
28
24
 
29
- == SYNOPSIS:
25
+ * Windows support only
30
26
 
31
- ocra [option] script.rb
27
+ If you experience problems with OCRA or have found a bug, please use
28
+ the issue tracker on GitHub (http://github.com/larsch/ocra/issues).
29
+ You can also join the Google Group discussion forum to ask questions
30
+ and get help (http://groups.google.com/group/ruby-ocra).
32
31
 
33
- * Will package "script.rb", the Ruby interpreter and all dependencies
34
- (gems and DLLs) into an executable named "script.exe".
32
+ == SYNOPSIS:
35
33
 
34
+ === Building an executable:
35
+
36
+ ocra script.rb
37
+
38
+ Will package "<tt>script.rb</tt>", the Ruby interpreter and all
39
+ dependencies (gems and DLLs) into an executable named
40
+ "<tt>script.exe</tt>".
41
+
42
+ === Command line:
43
+
44
+ ocra [options] script.rb [<other files> ...] [-- <script arguments> ...]
45
+
46
+ === Options:
47
+
48
+ ocra --help
49
+
50
+ --dll dllname Include additional DLLs from the Ruby bindir.
51
+ --no-lzma Disable LZMA compression of the executable.
52
+ --no-dep-run Don't run script.rb to check for dependencies.
53
+ --add-all-core Add all core ruby libraries to the executable.
54
+ --output <file> Name the exe to generate. Defaults to ./<scriptname>.exe.
55
+ --gemfile <file> Add all gems and dependencies listed in a Bundler Gemfile.
56
+ --quiet Suppress output while building executable.
57
+ --verbose Show extra output while building executable.
58
+ --debug Executable will be verbose.
59
+ --debug-extract Executable will unpack to local dir and not delete after.
60
+ --help Display this information.
61
+ --windows Force Windows application (rubyw.exe)
62
+ --console Force console application (ruby.exe)
63
+ --no-autoload Don't load/include script.rb's autoloads.
64
+ --icon <ico> Replace icon with a custom one.
65
+ --version Display version number and exit.
66
+ --no-gem-filter Don't filter readme's, doc, C-source, etc. from gems.
67
+
36
68
  === Compilation:
37
69
 
38
- * OCRA will load your script (using Kernel#load) and build the
39
- executable when it exits.
70
+ * OCRA will load your script (using <tt>Kernel#load</tt>) and build
71
+ the executable when it exits.
40
72
 
41
73
  * Your program should 'require' all necessary files when invoked without
42
74
  arguments, so OCRA can detect all dependencies.
@@ -46,8 +78,8 @@ Group discussion forum to ask questions and get help
46
78
 
47
79
  * .rb files will become console applications. .rbw files will become
48
80
  windowed application (without a console window popping
49
- up). Alternatively, use the <tt>--console</tt> or <tt>--windows</tt>
50
- options.
81
+ up). Alternatively, use the "<tt>--console</tt>" or
82
+ "<tt>--windows</tt>" options.
51
83
 
52
84
  === Running your application:
53
85
 
@@ -83,20 +115,26 @@ Group discussion forum to ask questions and get help
83
115
  * Windows
84
116
 
85
117
  * Working Ruby installation, tested with:
86
- * One-Click Installer (187_27_rc2)
87
- * RubyInstaller (1.8.7p299, 1.9.1p249, 1.9.2p0)
118
+ * One-Click Installer (187_27_rc2)
119
+ * RubyInstaller (1.8.7p299, 1.9.1p249, 1.9.2p0)
88
120
 
89
121
  * MinGW Installation (when working with the source code only)
90
122
 
91
123
  == INSTALL:
92
124
 
93
- === Gem
125
+ === Gem Package
126
+
127
+ Install by running:
94
128
 
95
129
  gem install ocra
96
130
 
97
- Can also be downloaded from http://rubyforge.org/frs/?group_id=8185
131
+ === Alternate Gem download:
132
+
133
+ * http://rubyforge.org/frs/?group_id=8185
134
+
135
+ * http://rubygems.org/gems/ocra
98
136
 
99
- === Stand-alone
137
+ === Stand-alone version
100
138
 
101
139
  Get ocrasa.rb from http://rubyforge.org/frs/?group_id=8185. Requires
102
140
  nothing but a working Ruby installation on Windows.
@@ -104,7 +142,8 @@ nothing but a working Ruby installation on Windows.
104
142
  == TECHNICAL DETAILS
105
143
 
106
144
  OCRA first runs the target script in order to detect any files that
107
- are loaded and used at runtime (Using Kernel#require and Kernel#load).
145
+ are loaded and used at runtime (Using <tt>Kernel#require</tt> and
146
+ <tt>Kernel#load</tt>).
108
147
 
109
148
  OCRA embeds everything needed to run a Ruby script into a single
110
149
  executable file. The file contains the .exe stub which is compiled
@@ -120,18 +159,19 @@ your application will be put in the 'src' subdirectory.
120
159
 
121
160
  === Libraries
122
161
 
123
- Any code that is loaded through Kernel#require when your script is
124
- executed will be included in the OCRA executable. Conditionally loaded
125
- code will not be loaded and included in the executable unless the code
126
- is actually run when OCRA invokes your script. Otherwise, OCRA won't
127
- know about it and will not include the source files.
162
+ Any code that is loaded through <tt>Kernel#require</tt> when your
163
+ script is executed will be included in the OCRA
164
+ executable. Conditionally loaded code will not be loaded and included
165
+ in the executable unless the code is actually run when OCRA invokes
166
+ your script. Otherwise, OCRA won't know about it and will not include
167
+ the source files.
128
168
 
129
169
  RubyGems are handled specially. Whenever a file from a Gem is
130
170
  detected, OCRA will attempt to include all the files from that
131
171
  specific Gem (all files listed in the manifest), expect some unlikely
132
- needed files such as readme's and other documentation. This can be
133
- overruled using the '--no-gem-filter' which will make OCRA include
134
- every file that is listed in the Gem's manifest.
172
+ needed files such as readme's and other documentation. This latter can
173
+ be overruled using the "<tt>--no-gem-filter</tt>" which will make OCRA
174
+ include every file that is listed in the Gem's manifest.
135
175
 
136
176
  Libraries found in non-standard path (for example, if you invoke OCRA
137
177
  with "ruby -I some/path") will be placed into the site dir
@@ -141,13 +181,45 @@ tree, since OCRA may place the files elsewhere when extracted into the
141
181
  temporary directory.
142
182
 
143
183
  In case your script (or any of its dependencies) sets up autoloaded
144
- module using Kernel#autoload, OCRA will automatically try to load them
145
- to ensure that they are all included in the executable. Modules that
146
- doesn't exist will be ignored (a warning will be logged).
184
+ module using <tt>Kernel#autoload</tt>, OCRA will automatically try to
185
+ load them to ensure that they are all included in the
186
+ executable. Modules that doesn't exist will be ignored (a warning will
187
+ be logged).
147
188
 
148
189
  Dynamic link libraries (.dll files, for example WxWidgets, or other
149
190
  source files) will be detected and included by OCRA.
150
191
 
192
+ === Including libraries non-automatically
193
+
194
+ If an application or framework is complicated enough that it tends
195
+ to confuse Ocra's automatic dependency resolution, then you can
196
+ use other means to specify what needs to be packaged with your app.
197
+
198
+ To disable automatic dependency resolution, use the --no-dep-run
199
+ option; with it, Ocra will skip executing your program during the
200
+ build process. You will also probably need to use the --add-all-core
201
+ option to include the Ruby core libraries.
202
+
203
+ If your app uses gems, then you can specify them in a
204
+ Bundler (http://gembundler.com/) Gemfile, then use the --gemfile
205
+ option to supply it to Ocra. Ocra will automatically include all
206
+ gems specified, and all their dependencies.
207
+
208
+ (Note: This assumes that the gems are installed in your system,
209
+ *not* locally packaged inside the app directory by "bundle package")
210
+
211
+ These options are particularly useful for packaging Rails
212
+ applications. For example, to package a Rails 3 app in the
213
+ directory "someapp" and create an exe named "someapp.exe", without
214
+ actually running the app during the build, you could use the
215
+ following command:
216
+
217
+ ocra someapp/script/server someapp --no-dep-run --add-all-core --gemfile someapp/Gemfile
218
+
219
+ Rails 2 apps can be packaged similarly, though you will have to
220
+ integrate them with Bundler (http://gembundler.com/rails23.html)
221
+ first.
222
+
151
223
  === Environment variables
152
224
 
153
225
  OCRA executables clear the RUBYLIB environment variable before your
@@ -177,13 +249,13 @@ through the Windows Explorer), the users' current working directory
177
249
  <tt>C:\\WINDOWS\\SYSTEM32</tt> when the executable is invoked through
178
250
  a file association. You can optionally change the directory yourself:
179
251
 
180
- Dir.chdir File.dirname($0)
252
+ Dir.chdir File.dirname($0)
181
253
 
182
254
  If you wish to maintain the user's working directory, but need to
183
255
  'require' additional Ruby scripts from the source directory, you can
184
256
  add the following line to your script:
185
257
 
186
- $LOAD_PATH.unshift File.dirname($0)
258
+ $LOAD_PATH.unshift File.dirname($0)
187
259
 
188
260
  === Load path mangling
189
261
 
@@ -193,7 +265,7 @@ directory being the same as where the script is located (See
193
265
  above). If you have additional library files in directories below the
194
266
  directory containing your source script you can use this idiom:
195
267
 
196
- $LOAD_PATH.unshift File.join(File.dirname($0), 'path/to/script')
268
+ $LOAD_PATH.unshift File.join(File.dirname($0), 'path/to/script')
197
269
 
198
270
  === Detecting OCRA
199
271
 
@@ -202,10 +274,10 @@ looking for the 'Ocra' constant. If it is defined, OCRA is currenly
202
274
  building the executable from your script. For example, you can use
203
275
  this to avoid opening a GUI window when compiling executables:
204
276
 
205
- app = MyApp.new
206
- if not defined?(Ocra)
207
- app.main_loop
208
- end
277
+ app = MyApp.new
278
+ if not defined?(Ocra)
279
+ app.main_loop
280
+ end
209
281
 
210
282
  === Additional files and resources
211
283
 
@@ -213,32 +285,35 @@ You can add additional files to the OCRA executable (for example
213
285
  images) by appending them to the command line. They should be placed
214
286
  in the source directory with your main script (or a subdirectory).
215
287
 
216
- ocra mainscript.rb someimage.jpeg docs/document.txt
288
+ ocra mainscript.rb someimage.jpeg docs/document.txt
217
289
 
218
290
  This will create the following layout in the temporary directory when
219
291
  your program is executed:
220
292
 
221
- src/mainscript.rb
222
- src/someimage.jpeg
223
- src/docs/document.txt
293
+ src/mainscript.rb
294
+ src/someimage.jpeg
295
+ src/docs/document.txt
224
296
 
225
- Paths on the command line can include ** globs to include a hierachy
226
- of files, for example
297
+ Both files, directoriess and glob patterns can be specified on the
298
+ command line. Files will be added as-is. If a directory is specified,
299
+ OCRA will include all files found below that directory. Glob patterns
300
+ (See Dir.glob) can be used to specify a specific set of files, for
301
+ example:
227
302
 
228
- ocra script.rb assets/**/*.png
303
+ ocra script.rb assets/**/*.png
229
304
 
230
305
  === Command Line Arguments
231
306
 
232
307
  To pass command line argument to your script (both while building and
233
- when run from the resulting executable), specify them after a "--"
234
- marker. For example:
308
+ when run from the resulting executable), specify them after a
309
+ "<tt>--</tt>" marker. For example:
235
310
 
236
- ocra script.rb -- --some-options=value
311
+ ocra script.rb -- --some-options=value
237
312
 
238
- This will pass "--some-options=value" to the script when build and
239
- when running the executable. Any extra argument specified by the user
240
- when invoking the executable will be appended after the compile-time
241
- arguments.
313
+ This will pass "<tt>--some-options=value</tt>" to the script when
314
+ build and when running the executable. Any extra argument specified by
315
+ the user when invoking the executable will be appended after the
316
+ compile-time arguments.
242
317
 
243
318
  === Window/Console
244
319
 
@@ -248,10 +323,10 @@ windowed applications (without console window) from .rbw-files.
248
323
  Ruby on Windows provides two executables: ruby.exe is a console mode
249
324
  application and rubyw.exe is a windowed application which does not
250
325
  bring up a console window when launched using the Windows Explorer.
251
- By default, or if the <tt>--console</tt> option is used, OCRA will use
326
+ By default, or if the "<tt>--console</tt>" option is used, OCRA will use
252
327
  the console runtime (rubyw.exe). OCRA will automatically select the
253
328
  windowed runtime when your script has the ".rbw" extension, or if you
254
- specify the <tt>--windows</tt> command line option.
329
+ specify the "<tt>--windows</tt>" command line option.
255
330
 
256
331
  If your application works in console mode but not in windowed mode,
257
332
  first check if your script works without OCRA using rubyw.exe. A
@@ -262,14 +337,14 @@ buffers run full).
262
337
  You can also try wrapping your script in an exception handler that
263
338
  logs any errors to a file:
264
339
 
265
- begin
266
- # your script here
267
- rescue Exception => e
268
- File.open("except.log") do |f|
269
- f.puts e.inspect
270
- f.puts e.backtrace
271
- end
272
- end
340
+ begin
341
+ # your script here
342
+ rescue Exception => e
343
+ File.open("except.log") do |f|
344
+ f.puts e.inspect
345
+ f.puts e.backtrace
346
+ end
347
+ end
273
348
 
274
349
  == CREDITS:
275
350
 
data/Rakefile CHANGED
@@ -8,6 +8,7 @@ Hoe.spec 'ocra' do |spec|
8
8
  spec.email = "larsch@belunktum.dk"
9
9
  spec.url = "http://ocra.rubyforge.org/"
10
10
  spec.readme_file = 'README.rdoc'
11
+ spec.extra_rdoc_files = ['README.rdoc']
11
12
  end
12
13
 
13
14
  task :build_stub do
@@ -19,10 +20,6 @@ end
19
20
 
20
21
  file 'share/ocra/stub.exe' => :build_stub
21
22
 
22
- file 'README.txt' => 'README.rdoc' do
23
- cp 'README.rdoc', 'README.txt'
24
- end
25
-
26
23
  task :test => :build_stub
27
24
 
28
25
  task :standalone => [ 'bin/ocrasa.rb' ]
data/bin/ocra CHANGED
@@ -2,47 +2,190 @@
2
2
  # -*- ruby -*-
3
3
 
4
4
  module Ocra
5
- Signature = [0x41, 0xb6, 0xba, 0x4e]
6
- OP_END = 0
7
- OP_CREATE_DIRECTORY = 1
8
- OP_CREATE_FILE = 2
9
- OP_CREATE_PROCESS = 3
10
- OP_DECOMPRESS_LZMA = 4
11
- OP_SETENV = 5
12
- OP_POST_CREATE_PROCESS = 6
5
+ # Path handling class. Ruby's Pathname class is not used because it
6
+ # is case sensitive and doesn't handle paths with mixed path
7
+ # separators.
8
+ class Pathname
9
+ def Pathname.pwd
10
+ Pathname.new(Dir.pwd)
11
+ end
12
+
13
+ def Pathname.pathequal(a, b)
14
+ a.downcase == b.downcase
15
+ end
16
+
17
+ attr_reader :path
18
+ SEPARATOR_PAT = /[#{Regexp.quote File::ALT_SEPARATOR}#{Regexp.quote File::SEPARATOR}]/ # }
19
+ ABSOLUTE_PAT = /\A([A-Z]:)?#{SEPARATOR_PAT}/i
20
+
21
+ def initialize(path)
22
+ @path = path
23
+ end
24
+
25
+ def to_native
26
+ @path.tr File::SEPARATOR, File::ALT_SEPARATOR
27
+ end
28
+
29
+ def to_posix
30
+ @path.tr File::ALT_SEPARATOR, File::SEPARATOR
31
+ end
32
+
33
+ # Compute the relative path from the 'src' path (directory) to 'tgt'
34
+ # (directory or file). Return the absolute path to 'tgt' if it can't
35
+ # be reached from 'src'.
36
+ def relative_path_from(other)
37
+ a = @path.split(SEPARATOR_PAT)
38
+ b = other.path.split(SEPARATOR_PAT)
39
+ while a.first && b.first && Pathname.pathequal(a.first, b.first)
40
+ a.shift
41
+ b.shift
42
+ end
43
+ return other if Pathname.new(b.first).absolute?
44
+ b.size.times { a.unshift '..' }
45
+ return Pathname.new(a.join('/'))
46
+ end
47
+
48
+ # Determines if 'src' is contained in 'tgt' (i.e. it is a subpath of
49
+ # 'tgt'). Both must be absolute paths and not contain '..'
50
+ def subpath?(other)
51
+ other = Ocra.Pathname(other)
52
+ src_normalized = to_posix.downcase
53
+ tgt_normalized = other.to_posix.downcase
54
+ src_normalized =~ /^#{Regexp.escape tgt_normalized}#{SEPARATOR_PAT}/i
55
+ end
13
56
 
14
- VERSION = "1.2.0"
57
+ def /(other)
58
+ Ocra.Pathname(@path + '/' + Ocra.Pathname(other).path)
59
+ end
60
+
61
+ def append_to_filename!(s)
62
+ @path.sub!(/(\.[^.]*?|)$/) { s.to_s + $1 }
63
+ end
64
+
65
+ def ext(new_ext = nil)
66
+ if new_ext
67
+ Pathname.new(@path.sub(/(\.[^.]*?)?$/) { new_ext })
68
+ else
69
+ File.extname(@path)
70
+ end
71
+ end
72
+
73
+ def ext?(expected_ext)
74
+ Pathname.pathequal(ext, expected_ext)
75
+ end
76
+
77
+ def ==(other); to_posix.downcase == other.to_posix.downcase; end
78
+ def =~(o); @path =~ o; end
79
+ def <=>(other); @path.casecmp(other.path); end
80
+ def exist?; File.exist?(@path); end
81
+ def file?; File.file?(@path); end
82
+ def directory?; File.directory?(@path); end
83
+ def absolute?; @path =~ ABSOLUTE_PAT; end
84
+ def dirname; Pathname.new(File.dirname(@path)); end
85
+ def basename; Pathname.new(File.basename(@path)); end
86
+ def expand(dir = nil); Pathname.new(File.expand_path(@path, dir && Ocra.Pathname(dir))); end
87
+
88
+ alias to_s to_posix
89
+ alias to_str to_posix
90
+ end
91
+
92
+ # Type conversion for the Pathname class. Works with Pathname,
93
+ # String, NilClass and arrays of any of these.
94
+ def self.Pathname(obj)
95
+ case obj
96
+ when Pathname
97
+ obj
98
+ when Array
99
+ obj.map { |x| Pathname(x) }
100
+ when String
101
+ Pathname.new(obj)
102
+ when NilClass
103
+ nil
104
+ else
105
+ raise ArgumentError, obj
106
+ end
107
+ end
108
+
109
+ # Variables describing the host's build environment.
110
+ module Host
111
+ class << self
112
+ def exec_prefix
113
+ @exec_prefix ||= Ocra.Pathname(RbConfig::CONFIG['exec_prefix'])
114
+ end
115
+ def sitelibdir
116
+ @sitelibdir ||= Ocra.Pathname(RbConfig::CONFIG['sitelibdir'])
117
+ end
118
+ def bindir
119
+ @bindir ||= Ocra.Pathname(RbConfig::CONFIG['bindir'])
120
+ end
121
+ def libruby_so
122
+ @libruby_so ||= Ocra.Pathname(RbConfig::CONFIG['LIBRUBY_SO'])
123
+ end
124
+ def exeext
125
+ RbConfig::CONFIG['EXEEXT'] || ".exe"
126
+ end
127
+ def rubyw_exe
128
+ @rubyw_exe ||= (RbConfig::CONFIG['rubyw_install_name'] || "rubyw") + exeext
129
+ end
130
+ def ruby_exe
131
+ @ruby_exe ||= (RbConfig::CONFIG['ruby_install_name'] || "ruby") + exeext
132
+ end
133
+ def tempdir
134
+ @tempdir ||= Ocra.Pathname(ENV['TEMP'])
135
+ end
136
+ end
137
+ end
138
+
139
+ # Sorts and returns an array without duplicates. Works with complex
140
+ # objects (such as Pathname), in contrast to Array#uniq.
141
+ def self.sort_uniq(a)
142
+ a.sort.inject([]) { |r, e| r.last == e ? r : r << e }
143
+ end
144
+
145
+ VERSION = "1.3.0.rc1"
15
146
 
16
147
  IGNORE_MODULES = /^enumerator.so$/
17
148
 
18
149
  IGNORE_GEMFILES = %r{(
19
150
  # Auxiliary files in the root of the gem
20
- ^(History|Install|Manifest|README|Licen[sc]e).(txt|rdoc)$ |
151
+ ^(\.\/)?(History|Install|Manifest|README|Licen[sc]e|Contributors|ChangeLog|BSD|GPL).*$ |
21
152
  # Installation files in the root of the gem
22
- ^(Rakefile|setup.rb|extconf.rb)$ |
153
+ ^(\.\/)?(Rakefile|setup.rb|extconf.rb)$ |
23
154
  # Documentation/test directories in the root of the gem
24
- ^(doc|ext|examples|test|tests|benchmarks)\/ |
155
+ ^(\.\/)?(doc|ext|examples|test|tests|benchmarks)\/ |
25
156
  # Directories anywhere
26
- (^|\/)(\.autotest|\.svn\.cvs)(\/|$) |
157
+ (^|\/)(\.autotest|\.svn|\.cvs|\.git)(\/|$) |
27
158
  # Unlikely extensions
28
159
  \.(rdoc)$/
29
160
  )}xi
30
161
 
31
- PATH_SEPARATOR = /[\/\\]/
32
-
33
- TEMPDIR_MARKER = "\xFF"
162
+ # Alias for the temporary directory where files are extracted.
163
+ TEMPDIR_ROOT = Pathname.new("\xFF")
164
+ # Directory for source files in temporary directory.
165
+ SRCDIR = Pathname.new('src')
166
+ # Directory for Ruby binaries in temporary directory.
167
+ BINDIR = Pathname.new('bin')
168
+ # Directory for GEMHOME files in temporary directory.
169
+ GEMHOMEDIR = Pathname.new('gemhome')
34
170
 
35
171
  @options = {
36
172
  :lzma_mode => true,
37
173
  :extra_dlls => [],
38
174
  :files => [],
175
+ :run_script => true,
176
+ :add_all_core => false,
177
+ :output_override => nil,
39
178
  :load_autoload => true,
40
179
  :force_windows => false,
41
180
  :force_console => false,
42
181
  :icon_filename => nil,
182
+ :gemfile => nil,
43
183
  :quiet => false,
184
+ :verbose => false,
44
185
  :autodll => true,
45
186
  :show_warnings => true,
187
+ :debug => false,
188
+ :debug_extract => false,
46
189
  :gem_filter => true,
47
190
  :arg => []
48
191
  }
@@ -56,17 +199,26 @@ module Ocra
56
199
  attr_reader :stubwimage
57
200
  end
58
201
 
59
- # Returns a binary blob store embedded in the current Ruby script.
60
- def Ocra.get_next_embedded_image
61
- DATA.read(DATA.readline.to_i).unpack("m")[0]
202
+ def Ocra.msg(s)
203
+ puts "=== #{s}" unless Ocra.quiet
204
+ end
205
+
206
+ def Ocra.verbose_msg(s)
207
+ puts s if Ocra.verbose and not Ocra.quiet
208
+ end
209
+
210
+ def Ocra.warn(s)
211
+ msg "WARNING: #{s}" if Ocra.show_warnings
62
212
  end
63
213
 
64
- def Ocra.dospath(path)
65
- path.tr('/','\\')
214
+ def Ocra.fatal_error(s)
215
+ puts "ERROR: #{s}"
216
+ exit 1
66
217
  end
67
218
 
68
- def Ocra.posixpath(path)
69
- path.tr('\\','/')
219
+ # Returns a binary blob store embedded in the current Ruby script.
220
+ def Ocra.get_next_embedded_image
221
+ DATA.read(DATA.readline.to_i).unpack("m")[0]
70
222
  end
71
223
 
72
224
  def Ocra.save_environment
@@ -86,20 +238,18 @@ module Ocra
86
238
  @stubimage = get_next_embedded_image
87
239
  @stubwimage = get_next_embedded_image
88
240
  lzmaimage = get_next_embedded_image
89
- @lzmapath = File.join(ENV['TEMP'], 'lzma.exe')
241
+ @lzmapath = Host.tempdir / 'lzma.exe'
90
242
  File.open(@lzmapath, "wb") { |file| file << lzmaimage }
91
243
  ediconimage = get_next_embedded_image
92
- @ediconpath = File.join(ENV['TEMP'], 'edicon.exe')
244
+ @ediconpath = Host.tempdir / 'edicon.exe'
93
245
  File.open(@ediconpath, "wb") { |file| file << ediconimage }
94
246
  else
95
- ocrapath = File.dirname(__FILE__)
96
- @stubimage = File.open(File.join(ocrapath, '../share/ocra/stub.exe'), "rb") { |file| file.read }
97
- @stubwimage = File.open(File.join(ocrapath, '../share/ocra/stubw.exe'), "rb") { |file| file.read }
98
- @lzmapath = File.expand_path('../share/ocra/lzma.exe', ocrapath)
99
- @ediconpath = File.expand_path('../share/ocra/edicon.exe', ocrapath)
100
- end
101
- @lzmapath = Ocra.dospath(@lzmapath)
102
- @ediconpath = Ocra.dospath(@ediconpath)
247
+ ocrapath = Pathname(File.dirname(__FILE__))
248
+ @stubimage = File.open(ocrapath / '../share/ocra/stub.exe', "rb") { |file| file.read }
249
+ @stubwimage = File.open(ocrapath / '../share/ocra/stubw.exe', "rb") { |file| file.read }
250
+ @lzmapath = (ocrapath / '../share/ocra/lzma.exe').expand
251
+ @ediconpath = (ocrapath / '../share/ocra/edicon.exe').expand
252
+ end
103
253
  end
104
254
 
105
255
  def Ocra.parseargs(argv)
@@ -108,24 +258,39 @@ ocra [options] script.rb
108
258
 
109
259
  --dll dllname Include additional DLLs from the Ruby bindir.
110
260
  --no-lzma Disable LZMA compression of the executable.
111
- --quiet Suppress output.
261
+ --no-dep-run Don't run script.rb to check for dependencies.
262
+ --add-all-core Add all core ruby libraries to the executable.
263
+ --output <file> Name the exe to generate. Defaults to ./<scriptname>.exe.
264
+ --gemfile <file> Add all gems and dependencies listed in a Bundler Gemfile.
265
+ --quiet Suppress output while building executable.
266
+ --verbose Show extra output while building executable.
267
+ --debug Executable will be verbose.
268
+ --debug-extract Executable will unpack to local dir and not delete after.
112
269
  --help Display this information.
113
270
  --windows Force Windows application (rubyw.exe)
114
271
  --console Force console application (ruby.exe)
115
- --no-autoload Don't load/include script.rb's autoloads
116
- --icon <ico> Replace icon with a custom one
117
- --version Display version number
118
- --no-gem-filter Don't filter readme's, doc, C-source, etc. from gems
272
+ --no-autoload Don't load/include script.rb's autoloads.
273
+ --icon <ico> Replace icon with a custom one.
274
+ --version Display version number and exit.
275
+ --no-gem-filter Don't filter readme's, doc, C-source, etc. from gems.
119
276
  EOF
120
277
 
121
278
  while arg = argv.shift
122
279
  case arg
123
280
  when /\A--(no-)?lzma\z/
124
281
  @options[:lzma_mode] = !$1
282
+ when /\A--no-dep-run\z/
283
+ @options[:run_script] = false
284
+ when /\A--add-all-core\z/
285
+ @options[:add_all_core] = true
286
+ when /\A--output\z/
287
+ @options[:output_override] = Pathname(argv.shift)
125
288
  when /\A--dll\z/
126
289
  @options[:extra_dlls] << argv.shift
127
290
  when /\A--quiet\z/
128
291
  @options[:quiet] = true
292
+ when /\A--verbose\z/
293
+ @options[:verbose] = true
129
294
  when /\A--windows\z/
130
295
  @options[:force_windows] = true
131
296
  when /\A--console\z/
@@ -133,15 +298,22 @@ EOF
133
298
  when /\A--no-autoload\z/
134
299
  @options[:load_autoload] = false
135
300
  when /\A--icon\z/
136
- @options[:icon_filename] = argv.shift
137
- raise "Icon file #{icon_filename} not found.\n" unless File.exist?(icon_filename)
301
+ @options[:icon_filename] = Pathname(argv.shift)
302
+ Ocra.fatal_error "Icon file #{icon_filename} not found.\n" unless icon_filename.exist?
303
+ when /\A--gemfile\z/
304
+ @options[:gemfile] = Pathname(argv.shift)
305
+ Ocra.fatal_error "Gemfile #{gemfile} not found.\n" unless gemfile.exist?
138
306
  when /\A--no-autodll\z/
139
307
  @options[:autodll] = false
140
308
  when /\A--version\z/
141
309
  puts "Ocra #{VERSION}"
142
- exit
310
+ exit 0
143
311
  when /\A--no-warnings\z/
144
312
  @options[:show_warnings] = false
313
+ when /\A--debug\z/
314
+ @options[:debug] = true
315
+ when /\A--debug-extract\z/
316
+ @options[:debug_extract] = true
145
317
  when /\A--no-gem-filter\z/
146
318
  @options[:gem_filter] = false
147
319
  when /\A--\z/
@@ -149,7 +321,7 @@ EOF
149
321
  ARGV.clear
150
322
  when /\A--help\z/, /\A--./
151
323
  puts usage
152
- exit
324
+ exit 0
153
325
  else
154
326
  @options[:files] << arg
155
327
  end
@@ -157,8 +329,19 @@ EOF
157
329
 
158
330
  if files.empty?
159
331
  puts usage
160
- exit
332
+ exit 1
161
333
  end
334
+
335
+ @options[:files].map! { |path|
336
+ path = path.tr('\\','/')
337
+ if File.directory?(path)
338
+ # If a directory is passed, we want all files under that directory
339
+ path = "#{path}/**/*"
340
+ end
341
+ files = Dir[path]
342
+ Ocra.fatal_error "#{path} not found!" if files.empty?
343
+ files.map { |path| Pathname(path).expand }
344
+ }.flatten!
162
345
  end
163
346
 
164
347
  def Ocra.init(argv)
@@ -171,7 +354,7 @@ EOF
171
354
  # (and hence classes), and checks their constants for autoloaded
172
355
  # ones, then attempts to load them.
173
356
  def Ocra.attempt_load_autoload
174
- modules_checked = []
357
+ modules_checked = {}
175
358
  loop do
176
359
  modules_to_check = []
177
360
  ObjectSpace.each_object(Module) do |mod|
@@ -179,15 +362,15 @@ EOF
179
362
  end
180
363
  break if modules_to_check.empty?
181
364
  modules_to_check.each do |mod|
182
- modules_checked << mod
365
+ modules_checked[mod] = true
183
366
  mod.constants.each do |const|
184
367
  if mod.autoload?(const)
185
368
  begin
186
369
  mod.const_get(const)
187
370
  rescue NameError
188
- puts "=== WARNING: #{mod}::#{const} was defined autoloadable, but caused NameError" if Ocra.show_warnings
371
+ Ocra.warn "#{mod}::#{const} was defined autoloadable, but caused NameError"
189
372
  rescue LoadError
190
- puts "=== WARNING: #{mod}::#{const} was not loadable" if Ocra.show_warnings
373
+ Ocra.warn "#{mod}::#{const} was not loadable"
191
374
  end
192
375
  end
193
376
  end
@@ -195,65 +378,43 @@ EOF
195
378
  end
196
379
  end
197
380
 
198
- # Compute the relative path from the 'src' path (directory) to 'tgt'
199
- # (directory or file). Return the absolute path to 'tgt' if it can't
200
- # be reached from 'src'.
201
- def Ocra.relative_path(src, tgt)
202
- a = posixpath(src).split('/')
203
- b = posixpath(tgt).split('/')
204
- while a.first && a.first.downcase == b.first.downcase
205
- a.shift
206
- b.shift
207
- end
208
- return tgt if abspath?(b.first)
209
- a.size.times { b.unshift '..' }
210
- return b.join('/')
211
- end
212
-
213
- # Determines if 'src' is contained in 'tgt' (i.e. it is a subpath of
214
- # 'tgt'). Both must be absolute paths and not contain '..'
215
- def Ocra.subpath?(src, tgt)
216
- src_normalized = Ocra.dospath(src).downcase
217
- tgt_normalized = Ocra.dospath(tgt).downcase
218
- src_normalized =~ /^#{Regexp.escape tgt_normalized}[\/\\]/i
219
- end
220
-
221
- # Returns true if 'path' is absolute, false otherwise.
222
- def Ocra.abspath?(path)
223
- path =~ /\A[A-Z]:/i
224
- end
225
-
226
381
  # Guess the load path (from 'paths') that was used to load
227
382
  # 'path'. This is primarily relevant on Ruby 1.8 which stores
228
383
  # "unqualified" paths in $LOADED_FEATURES.
229
- def Ocra.find_load_path(paths, path)
230
- if abspath?(path)
231
- # Find the shortest path in 'paths' that contain 'path'
232
- rps = paths.map {|p| relative_path(File.expand_path(p), path) }
233
- rps.zip(paths).sort_by {|x| x[0].size }.first[1]
384
+ def Ocra.find_load_path(loadpaths, feature)
385
+ if feature.absolute?
386
+ # Choose those loadpaths which contain the feature
387
+ candidate_loadpaths = loadpaths.select { |loadpath| feature.subpath?(loadpath.expand) }
388
+ # Guess the require'd feature
389
+ feature_pairs = candidate_loadpaths.map { |loadpath| [loadpath, feature.relative_path_from(loadpath.expand)] }
390
+ # Select the shortest possible require-path (longest load-path)
391
+ if feature_pairs.empty?
392
+ nil
393
+ else
394
+ feature_pairs.sort_by { |loadpath, feature| feature.path.size }.first[0]
395
+ end
234
396
  else
235
- # Select the paths that contain 'path' and select the shortest
236
- candidates = paths.select { |p| File.exist?(File.expand_path(path, p)) }
237
- candidates.sort_by {|p| p.size}.last
397
+ # Select the loadpaths that contain 'feature' and select the shortest
398
+ candidates = loadpaths.select { |loadpath| feature.expand(loadpath).exist? }
399
+ candidates.sort_by { |loadpath| loadpath.path.size }.last
238
400
  end
239
401
  end
240
-
402
+
241
403
  # Find the root of all files specified on the command line and use
242
404
  # it as the "src" of the output.
243
405
  def Ocra.find_src_root(files)
244
- src_files = files.map { |file| Dir[posixpath(file)] }.flatten
245
- src_files = src_files.map { |file| File.expand_path(file) }
246
- src_prefix = src_files.inject(File.dirname(src_files[0])) do |srcroot, path|
247
- if subpath?(path, @ruby_exec_prefix)
406
+ src_files = files.map { |file| file.expand }
407
+ src_prefix = src_files.inject(src_files.first.dirname) do |srcroot, path|
408
+ if path.subpath?(Host.exec_prefix)
248
409
  srcroot
249
410
  else
250
411
  loop do
251
- relpath = relative_path(srcroot, path)
252
- if abspath?(relpath)
253
- puts "ERROR: No common directory contains all specified files"
412
+ relpath = path.relative_path_from(srcroot)
413
+ if relpath.absolute?
414
+ Ocra.fatal_error "No common directory contains all specified files"
254
415
  end
255
- if relpath =~ /^\.\.\//
256
- srcroot = File.dirname(srcroot)
416
+ if relpath.to_s =~ /^\.\.\//
417
+ srcroot = srcroot.dirname
257
418
  else
258
419
  break
259
420
  end
@@ -261,7 +422,7 @@ EOF
261
422
  srcroot
262
423
  end
263
424
  end
264
- src_files = src_files.map { |file| relative_path(src_prefix, file) }
425
+ src_files = src_files.map { |file| file.relative_path_from(src_prefix) }
265
426
  return src_prefix, src_files
266
427
  end
267
428
 
@@ -273,43 +434,65 @@ EOF
273
434
  # file from a gem path.
274
435
  def Ocra.find_gem_files(features)
275
436
  features_from_gems = []
437
+ gems = []
438
+
439
+ # If a Bundler Gemfile was provided, add all gems it specifies
440
+ if Ocra.gemfile
441
+ Ocra.msg "Scanning Gemfile"
442
+ # Load Rubygems and Bundler so we can scan the Gemfile
443
+ ['rubygems', 'bundler'].each do |lib|
444
+ begin
445
+ require lib
446
+ rescue LoadError
447
+ Ocra.fatal_error "Couldn't scan Gemfile, unable to load #{lib}"
448
+ end
449
+ end
450
+
451
+ ENV['BUNDLE_GEMFILE'] = Ocra.gemfile
452
+ Bundler.load.specs.each do |spec|
453
+ gems << [Pathname(spec.installation_path), spec.full_name]
454
+ end
455
+ end
456
+
276
457
  if defined?(Gem)
277
- gems = []
278
- features.each do |filename|
279
- if filename !~ /^[a-z]:/i
280
- filename = find_load_path($:, filename)
281
- next if filename.nil? # Could be enumerator.so
458
+ features.each do |feature|
459
+ if not feature.absolute?
460
+ feature = find_load_path(Pathname($:), feature)
461
+ next if feature.nil? # Could be enumerator.so
282
462
  end
283
- Gem.path.each do |gempath|
284
- geminstallpath = File.join(gempath, "gems")
285
- if subpath?(filename, geminstallpath)
286
- gemlocalpath = relative_path(geminstallpath, filename)
287
- fullgemname = gemlocalpath.split('/')[0]
463
+ gempaths = Pathname(Gem.path)
464
+ gempaths.each do |gempath|
465
+ geminstallpath = Pathname(gempath) / "gems"
466
+ if feature.subpath?(geminstallpath)
467
+ gemlocalpath = feature.relative_path_from(geminstallpath)
468
+ fullgemname = gemlocalpath.path.split('/').first
288
469
  gems << [gempath, fullgemname]
289
- features_from_gems << filename
470
+ features_from_gems << feature
290
471
  end
291
472
  end
292
473
  end
293
- gems.sort!.uniq!
474
+
475
+ gems = sort_uniq(gems)
294
476
  gem_files = []
295
477
  gems.each do |gempath, fullgemname|
296
- gemspecpath = File.join(gempath, "specifications", fullgemname + ".gemspec")
478
+ gemspecpath = gempath / 'specifications' / "#{fullgemname}.gemspec"
297
479
  @gemspecs << gemspecpath
298
480
  spec = Gem::Specification.load(gemspecpath)
481
+ Ocra.msg "Will include gem #{spec.full_name}"
299
482
 
300
483
  # Get list of files
301
- files = spec.files
484
+ files = Pathname(spec.files)
302
485
  # Filter out some unlikely files (Readme, etc.)
303
486
  files = files.select { |filename| filename !~ IGNORE_GEMFILES } if Ocra.gem_filter
304
487
  # Find the full path
305
- files = files.map { |file| File.join(gempath, "gems", fullgemname, file) }
488
+ files = files.map { |file| (gempath / "gems" / fullgemname / file).expand }
306
489
  # Filter out non-files
307
- files = files.select { |file| File.file?(file) }
490
+ files = files.select { |file| file.file? }
308
491
 
309
492
  gem_files += files
310
493
  end
311
- gem_files.sort!.uniq!
312
- features -= features_from_gems
494
+ gem_files = sort_uniq(gem_files)
495
+ features -= features_from_gems # FIXME Isn't this duplicated by caller?
313
496
  else
314
497
  gem_files = []
315
498
  end
@@ -317,112 +500,158 @@ EOF
317
500
  end
318
501
 
319
502
  def Ocra.build_exe
320
- @added_load_paths = $LOAD_PATH - @load_path_before
503
+ all_load_paths = $LOAD_PATH.map { |loadpath| Pathname(loadpath).expand }
504
+ @added_load_paths = ($LOAD_PATH - @load_path_before).map { |loadpath| Pathname(loadpath).expand }
505
+ working_directory = Pathname.pwd.expand
321
506
 
322
507
  restore_environment
323
-
324
- # Attempt to autoload libraries before doing anything else.
325
- attempt_load_autoload if Ocra.load_autoload
326
-
327
- # Store the currently loaded files (before we require rbconfig for
328
- # our own use).
329
- features = $LOADED_FEATURES.dup
508
+
509
+ features = []
510
+ # If the script was ran, then detect the features it used
511
+ if Ocra.run_script
512
+ # Attempt to autoload libraries before doing anything else.
513
+ attempt_load_autoload if Ocra.load_autoload
514
+
515
+ # Store the currently loaded files (before we require rbconfig for
516
+ # our own use).
517
+ features = $LOADED_FEATURES.map { |feature| Pathname(feature) }
518
+ features.delete_if { |feature| feature =~ IGNORE_MODULES }
519
+ end
330
520
 
331
521
  # Find gemspecs to include
332
522
  if defined?(Gem)
333
- @gemspecs = Gem.loaded_specs.map { |name,info| info.loaded_from }
523
+ @gemspecs = Gem.loaded_specs.map { |name,info| Pathname(info.loaded_from) }
334
524
  else
335
525
  @gemspecs = []
336
526
  end
337
527
 
338
528
  require 'rbconfig'
339
- @ruby_exec_prefix = RbConfig::CONFIG['exec_prefix']
340
- src_prefix = Dir.pwd
341
- sitelibdir = RbConfig::CONFIG['sitelibdir']
342
- bindir = RbConfig::CONFIG['bindir']
343
- libruby_so = RbConfig::CONFIG['LIBRUBY_SO']
344
- instsitelibdir = relative_path(@ruby_exec_prefix, sitelibdir)
529
+ instsitelibdir = Host.sitelibdir.relative_path_from(Host.exec_prefix)
345
530
 
346
531
  load_path = []
347
-
348
- # Find the source root and adjust paths
349
- src_prefix, src_files = find_src_root(Ocra.files)
350
- Ocra.files.replace(src_files)
532
+ src_load_path = []
351
533
 
352
534
  # Find gems files and remove them from features
353
535
  gem_files, features_from_gems = find_gem_files(features)
354
536
  features -= features_from_gems
355
537
 
538
+ # Find the source root and adjust paths
539
+ src_prefix, src_files = find_src_root(Ocra.files)
540
+
356
541
  # Find features and decide where to put them in the temporary
357
542
  # directory layout.
358
543
  libs = []
359
- features.each do |filename|
360
- path = find_load_path($:, filename)
361
- if path
362
- if abspath?(filename)
363
- filename = relative_path(File.expand_path(path), filename)
364
- end
365
- if filename =~ /^\.\.\//
366
- puts "=== WARNING: Detected a relative require (#{filename}). This is not recommended." if Ocra.show_warnings
544
+ features.each do |feature|
545
+ path = find_load_path(all_load_paths, feature)
546
+ if path.nil? || path.expand == Pathname.pwd
547
+ Ocra.files << feature
548
+ else
549
+ if feature.absolute?
550
+ feature = feature.relative_path_from(path.expand)
367
551
  end
368
- fullpath = File.expand_path(filename, path)
369
- if subpath?(fullpath, @ruby_exec_prefix)
370
- libs << [ fullpath, relative_path(@ruby_exec_prefix, fullpath) ]
371
- elsif subpath?(fullpath, src_prefix)
372
- targetpath = File.join("src", relative_path(src_prefix, fullpath))
373
- libs << [ fullpath, targetpath ]
374
- if not @added_load_paths.include?(path) and not load_path.include?(path)
375
- load_path << File.join(TEMPDIR_MARKER, File.dirname(targetpath))
376
- end
377
- elsif defined?(Gem) and gemhome = Gem.path.find { |pth| subpath?(fullpath, pth) }
378
- targetpath = File.join("gemhome", relative_path(gemhome, fullpath))
552
+ fullpath = feature.expand(path)
553
+
554
+ if fullpath.subpath?(Host.exec_prefix)
555
+ # Features found in the Ruby installation are put in the
556
+ # temporary Ruby installation.
557
+ libs << [ fullpath, fullpath.relative_path_from(Host.exec_prefix) ]
558
+ elsif defined?(Gem) and gemhome = Gem.path.find { |pth| fullpath.subpath?(pth) }
559
+ # Features found in any other Gem path (e.g. ~/.gems) is put
560
+ # in a special 'gemhome' folder.
561
+ targetpath = GEMHOMEDIR / fullpath.relative_path_from(gemhome)
379
562
  libs << [ fullpath, targetpath ]
563
+ elsif fullpath.subpath?(src_prefix) || path == working_directory
564
+ # Any feature found inside the src_prefix automatically gets
565
+ # added as a source file (to go in 'src').
566
+ Ocra.files << fullpath
567
+ # Add the load path unless it was added by the script while
568
+ # running (or we assume that the script can also set it up
569
+ # correctly when running from the resulting executable).
570
+ src_load_path << path unless @added_load_paths.include?(path)
571
+ elsif @added_load_paths.include?(path)
572
+ # Any feature that exist in a load path added by the script
573
+ # itself is added as a file to go into the 'src' (src_prefix
574
+ # will be adjusted below to point to the common parent).
575
+ Ocra.files << fullpath
380
576
  else
381
- libs << [ fullpath, File.join(instsitelibdir, filename) ]
577
+ # All other feature that can not be resolved go in the the
578
+ # Ruby sitelibdir. This is automatically in the load path
579
+ # when Ruby starts.
580
+ libs << [ fullpath, instsitelibdir / feature ]
382
581
  end
383
- else
384
- puts "=== WARNING: Couldn't find #{filename}" unless filename =~ IGNORE_MODULES if Ocra.show_warnings
385
582
  end
386
583
  end
387
584
 
585
+ # Recompute the src_prefix. Files may have been added implicitly
586
+ # while scanning through features.
587
+ src_prefix, src_files = find_src_root(Ocra.files)
588
+ Ocra.files.replace(src_files)
589
+
590
+ # Add the load path that are required with the correct path after
591
+ # src_prefix was adjusted.
592
+ load_path += src_load_path.map { |loadpath| TEMPDIR_ROOT / SRCDIR / loadpath.relative_path_from(src_prefix) }
593
+
388
594
  # Decide where to put gem files, either the system gem folder, or
389
595
  # GEMHOME.
390
596
  gem_files.each do |gemfile|
391
- if subpath?(gemfile, @ruby_exec_prefix)
392
- libs << [ gemfile, relative_path(@ruby_exec_prefix, gemfile) ]
393
- elsif defined?(Gem) and gemhome = Gem.path.find { |pth| subpath?(gemfile, pth) }
394
- targetpath = File.join("gemhome", relative_path(gemhome, fullpath))
597
+ if gemfile.subpath?(Host.exec_prefix)
598
+ libs << [ gemfile, gemfile.relative_path_from(Host.exec_prefix) ]
599
+ elsif defined?(Gem) and gemhome = Gem.path.find { |pth| gemfile.subpath?(pth) }
600
+ targetpath = GEMHOMEDIR / fullpath.relative_path_from(gemhome)
395
601
  libs << [ gemfile, targetpath ]
396
602
  else
397
- raise "Don't know where to put #{gemfile}"
603
+ Ocra.fatal_error "Don't know where to put gemfile #{gemfile}"
604
+ end
605
+ end
606
+
607
+ # If requested, add all ruby standard libraries
608
+ if Ocra.add_all_core
609
+ Ocra.msg "Will include all ruby core libraries"
610
+ @load_path_before.each do |lp|
611
+ path = Pathname.new(lp)
612
+ next unless path.to_posix =~
613
+ /\/(ruby\/(?:site_ruby\/|vendor_ruby\/)?[0-9.]+)\/?$/i
614
+ subdir = $1
615
+ Dir["#{lp}/**/*"].each do |f|
616
+ fpath = Pathname.new(f)
617
+ next if fpath.directory?
618
+ tgt = "lib/#{subdir}/#{fpath.relative_path_from(path).to_posix}"
619
+ libs << [f, tgt]
620
+ end
398
621
  end
399
622
  end
400
623
 
401
624
  # Detect additional DLLs
402
625
  dlls = Ocra.autodll ? LibraryDetector.detect_dlls : []
403
626
 
404
- executable = File.basename(Ocra.files[0].sub(/(\.rbw?)?$/, '.exe'))
627
+ executable = nil
628
+ if Ocra.output_override
629
+ executable = Ocra.output_override
630
+ else
631
+ executable = Ocra.files.first.basename.ext('.exe')
632
+ executable.append_to_filename!("-debug") if Ocra.debug
633
+ end
405
634
 
406
- windowed = (Ocra.files[0] =~ /\.rbw$/ || Ocra.force_windows) && !Ocra.force_console
635
+ windowed = (Ocra.files.first.ext?('.rbw') || Ocra.force_windows) && !Ocra.force_console
407
636
 
408
- puts "=== Building #{executable}" unless Ocra.quiet
637
+ Ocra.msg "Building #{executable}"
409
638
  target_script = nil
410
639
  OcraBuilder.new(executable, windowed) do |sb|
411
640
  # Add explicitly mentioned files
641
+ Ocra.msg "Adding user-supplied source files"
412
642
  Ocra.files.each do |file|
413
- file = File.join(src_prefix, file)
414
-
415
- if subpath?(file, @ruby_exec_prefix)
416
- target = relative_path(@ruby_exec_prefix, file)
417
- elsif subpath?(file, src_prefix)
418
- target = File.join('src', relative_path(src_prefix, file))
643
+ file = src_prefix / file
644
+ if file.subpath?(Host.exec_prefix)
645
+ target = file.relative_path_from(Host.exec_prefix)
646
+ elsif file.subpath?(src_prefix)
647
+ target = SRCDIR / file.relative_path_from(src_prefix)
419
648
  else
420
- target = File.join('src', File.basename(file))
649
+ target = SRCDIR / file.basename
421
650
  end
422
651
 
423
652
  target_script ||= target
424
653
 
425
- if File.directory?(file)
654
+ if file.directory?
426
655
  sb.ensuremkdir(target)
427
656
  else
428
657
  sb.createfile(file, target)
@@ -431,61 +660,68 @@ EOF
431
660
 
432
661
  # Add the ruby executable and DLL
433
662
  if windowed
434
- rubyexe = (RbConfig::CONFIG['rubyw_install_name'] || "rubyw") + ".exe"
663
+ rubyexe = Host.rubyw_exe
435
664
  else
436
- rubyexe = (RbConfig::CONFIG['ruby_install_name'] || "ruby") + ".exe"
665
+ rubyexe = Host.ruby_exe
437
666
  end
438
- sb.createfile(File.join(bindir, rubyexe), File.join("bin", rubyexe))
439
- if libruby_so
440
- sb.createfile(File.join(bindir, libruby_so), File.join("bin", libruby_so))
667
+ Ocra.msg "Adding ruby executable #{rubyexe}"
668
+ sb.createfile(Host.bindir / rubyexe, BINDIR / rubyexe)
669
+ if Host.libruby_so
670
+ sb.createfile(Host.bindir / Host.libruby_so, BINDIR / Host.libruby_so)
441
671
  end
442
672
 
443
673
  # Add detected DLLs
444
674
  dlls.each do |dll|
445
- if subpath?(dll, @ruby_exec_prefix)
446
- target = relative_path(@ruby_exec_prefix, dll)
675
+ Ocra.msg "Adding detected DLL #{dll}"
676
+ if dll.subpath?(Host.exec_prefix)
677
+ target = dll.relative_path_from(Host.exec_prefix)
447
678
  else
448
- target = File.join('bin', File.basename(dll))
679
+ target = BINDIR / File.basename(dll)
449
680
  end
450
681
  sb.createfile(dll, target)
451
682
  end
452
683
 
453
684
  # Add extra DLLs specified on the command line
454
685
  Ocra.extra_dlls.each do |dll|
455
- sb.createfile(File.join(bindir, dll), File.join("bin", dll))
686
+ Ocra.msg "Adding supplied DLL #{dll}"
687
+ sb.createfile(Host.bindir / dll, BINDIR / dll)
456
688
  end
457
689
 
458
690
  # Add gemspec files
691
+ @gemspecs = sort_uniq(@gemspecs)
459
692
  @gemspecs.each do |gemspec|
460
- if subpath?(gemspec, @ruby_exec_prefix)
461
- path = relative_path(@ruby_exec_prefix, gemspec)
693
+ if gemspec.subpath?(Host.exec_prefix)
694
+ path = gemspec.relative_path_from(Host.exec_prefix)
462
695
  sb.createfile(gemspec, path)
463
- elsif defined?(Gem) and gemhome = Gem.path.find { |pth| subpath?(gemspec, pth) }
464
- path = File.join('gemhome', relative_path(gemhome, gemspec))
696
+ elsif defined?(Gem) and gemhome = Gem.path.find { |pth| gemspec.subpath?(pth) }
697
+ path = GEMHOMEDIR / gemspec.relative_path_from(gemhome)
465
698
  sb.createfile(gemspec, path)
466
699
  else
467
- raise "#{gemspec} does not exist in the Ruby installation. Don't know where to put it."
700
+ Ocra.fatal_error "Gem spec #{gemspec} does not exist in the Ruby installation. Don't know where to put it."
468
701
  end
469
702
  end
470
703
 
471
704
  # Add loaded libraries (features, gems)
705
+ Ocra.msg "Adding library files"
472
706
  libs.each do |path, target|
473
707
  sb.createfile(path, target)
474
708
  end
475
709
 
476
710
  # Set environment variable
477
711
  sb.setenv('RUBYOPT', ENV['RUBYOPT'] || '')
478
- sb.setenv('RUBYLIB', load_path.map{|path| dospath(path)}.uniq.join(';'))
479
- sb.setenv('GEM_PATH', "#{TEMPDIR_MARKER}\\gemhome")
480
-
481
- # Launch the script
482
- extra_arg = Ocra.arg.map { |a| ' "' + a.gsub(/\"/,'\\"') + '"' }.join
483
- sb.postcreateprocess(TEMPDIR_MARKER + "\\bin\\" + rubyexe,
484
- "#{rubyexe} \"\xff\\" + Ocra.dospath(target_script) + "\"#{extra_arg}")
712
+ sb.setenv('RUBYLIB', load_path.map{|path| path.to_native}.uniq.join(';'))
713
+ sb.setenv('GEM_PATH', (TEMPDIR_ROOT / GEMHOMEDIR).to_native)
714
+
715
+ # Add the opcode to launch the script
716
+ extra_arg = Ocra.arg.map { |arg| ' "' + arg.gsub(/\"/,'\\"') + '"' }.join
717
+ installed_ruby_exe = TEMPDIR_ROOT / BINDIR / rubyexe
718
+ launch_script = (TEMPDIR_ROOT / target_script).to_native
719
+ sb.postcreateprocess(installed_ruby_exe,
720
+ "#{rubyexe} \"#{launch_script}\"#{extra_arg}")
485
721
 
486
- puts "=== Compressing" unless Ocra.quiet or not Ocra.lzma_mode
722
+ Ocra.msg "Compressing" unless not Ocra.lzma_mode
487
723
  end
488
- puts "=== Finished building #{executable} (#{File.size(executable)} bytes)" unless Ocra.quiet
724
+ Ocra.msg "Finished building #{executable} (#{File.size(executable)} bytes)"
489
725
  end
490
726
 
491
727
  module LibraryDetector
@@ -508,21 +744,17 @@ EOF
508
744
  end
509
745
 
510
746
  handles = module_handle_buffer.unpack("I*")
511
- handles.select{|x|x>0}.map do |h|
747
+ handles.select { |handle| handle > 0 }.map do |handle|
512
748
  str = "\x00" * 256
513
- r = getmodulefilename.call(h, str, str.size)
514
- str[0,r]
749
+ modulefilename_length = getmodulefilename.call(handle, str, str.size)
750
+ Ocra.Pathname(str[0,modulefilename_length])
515
751
  end
516
752
  end
517
753
 
518
754
  def LibraryDetector.detect_dlls
519
755
  loaded = loaded_dlls
520
- exec_prefix = RbConfig::CONFIG['exec_prefix']
521
- loaded.select do |path|
522
- Ocra.subpath?(path, exec_prefix) and
523
- File.basename(path) =~ /\.dll$/i and
524
- File.basename(path).downcase != RbConfig::CONFIG['LIBRUBY_SO'].downcase
525
- end
756
+ exec_prefix = Host.exec_prefix
757
+ loaded.select { |path| path.subpath?(exec_prefix) && path.basename.ext?('.dll') && path.basename != Host.libruby_so }
526
758
  end
527
759
  end
528
760
 
@@ -530,15 +762,32 @@ EOF
530
762
  # (createfile, mkdir etc) are added by invoking methods on an
531
763
  # instance of OcraBuilder.
532
764
  class OcraBuilder
765
+ Signature = [0x41, 0xb6, 0xba, 0x4e]
766
+ OP_END = 0
767
+ OP_CREATE_DIRECTORY = 1
768
+ OP_CREATE_FILE = 2
769
+ OP_CREATE_PROCESS = 3
770
+ OP_DECOMPRESS_LZMA = 4
771
+ OP_SETENV = 5
772
+ OP_POST_CREATE_PROCESS = 6
773
+ OP_ENABLE_DEBUG_MODE = 7
774
+ OP_CREATE_INST_DIRECTORY = 8
775
+
533
776
  def initialize(path, windowed)
534
777
  @paths = {}
778
+ @files = {}
535
779
  File.open(path, "wb") do |ocrafile|
536
-
780
+ image = nil
537
781
  if windowed
538
- ocrafile.write(Ocra.stubwimage)
782
+ image = Ocra.stubwimage
539
783
  else
540
- ocrafile.write(Ocra.stubimage)
784
+ image = Ocra.stubimage
541
785
  end
786
+
787
+ unless image
788
+ Ocra.fatal_error "Stub image not available"
789
+ end
790
+ ocrafile.write(image)
542
791
  end
543
792
 
544
793
  if Ocra.icon_filename
@@ -555,6 +804,12 @@ EOF
555
804
  @of = ocrafile
556
805
  end
557
806
 
807
+ if Ocra.debug
808
+ ocrafile.write([OP_ENABLE_DEBUG_MODE].pack("V"))
809
+ end
810
+
811
+ createinstdir Ocra.debug_extract, !Ocra.debug_extract
812
+
558
813
  yield(self)
559
814
 
560
815
  if Ocra.lzma_mode
@@ -563,7 +818,6 @@ EOF
563
818
  system("\"#{Ocra.lzmapath}\" e tmpin tmpout 2>NUL") or fail
564
819
  compressed_data = File.open("tmpout", "rb") { |tmp| tmp.read }
565
820
  ocrafile.write([OP_DECOMPRESS_LZMA, compressed_data.size, compressed_data].pack("VVA*"))
566
- ocrafile.write([OP_END].pack("V"))
567
821
  ensure
568
822
  File.unlink("tmpin") if File.exist?("tmpin")
569
823
  File.unlink("tmpout") if File.exist?("tmpout")
@@ -579,38 +833,47 @@ EOF
579
833
  end
580
834
 
581
835
  def mkdir(path)
582
- @paths[path.downcase] = true
583
- puts "m #{showtempdir path}" unless Ocra.quiet
584
- @of << [OP_CREATE_DIRECTORY, Ocra.dospath(path)].pack("VZ*")
836
+ return if @paths[path.path.downcase]
837
+ @paths[path.path.downcase] = true
838
+ Ocra.verbose_msg "m #{showtempdir path}"
839
+ @of << [OP_CREATE_DIRECTORY, path.to_native].pack("VZ*")
585
840
  end
586
841
 
587
842
  def ensuremkdir(tgt)
588
- return if tgt == "."
589
- if not @paths[Ocra.posixpath(tgt.downcase)]
590
- ensuremkdir(File.dirname(tgt))
843
+ tgt = Ocra.Pathname(tgt)
844
+ return if tgt.path == "."
845
+ if not @paths[tgt.to_posix.downcase]
846
+ ensuremkdir(tgt.dirname)
591
847
  mkdir(tgt)
592
848
  end
593
849
  end
594
850
 
851
+ def createinstdir(next_to_exe = false, delete_after = false)
852
+ @of << [OP_CREATE_INST_DIRECTORY, next_to_exe ? 1 : 0, delete_after ? 1 : 0].pack("VVV")
853
+ end
854
+
595
855
  def createfile(src, tgt)
596
- ensuremkdir(File.dirname(tgt))
856
+ return if @files[tgt]
857
+ @files[tgt] = true
858
+ src, tgt = Ocra.Pathname(src), Ocra.Pathname(tgt)
859
+ ensuremkdir(tgt.dirname)
597
860
  str = File.open(src, "rb") { |file| file.read }
598
- puts "a #{showtempdir tgt}" unless Ocra.quiet
599
- @of << [OP_CREATE_FILE, Ocra.dospath(tgt), str.size, str].pack("VZ*VA*")
861
+ Ocra.verbose_msg "a #{showtempdir tgt}"
862
+ @of << [OP_CREATE_FILE, tgt.to_native, str.size, str].pack("VZ*VA*")
600
863
  end
601
864
 
602
865
  def createprocess(image, cmdline)
603
- puts "l #{showtempdir image} #{showtempdir cmdline}" unless Ocra.quiet
604
- @of << [OP_CREATE_PROCESS, Ocra.dospath(image), cmdline].pack("VZ*Z*")
866
+ Ocra.verbose_msg "l #{showtempdir image} #{showtempdir cmdline}"
867
+ @of << [OP_CREATE_PROCESS, image.to_native, cmdline].pack("VZ*Z*")
605
868
  end
606
869
 
607
870
  def postcreateprocess(image, cmdline)
608
- puts "p #{showtempdir image} #{showtempdir cmdline}" unless Ocra.quiet
609
- @of << [OP_POST_CREATE_PROCESS, Ocra.dospath(image), cmdline].pack("VZ*Z*")
871
+ Ocra.verbose_msg "p #{showtempdir image} #{showtempdir cmdline}"
872
+ @of << [OP_POST_CREATE_PROCESS, image.to_native, cmdline].pack("VZ*Z*")
610
873
  end
611
874
 
612
875
  def setenv(name, value)
613
- puts "e #{name} #{showtempdir value}" unless Ocra.quiet
876
+ Ocra.verbose_msg "e #{name} #{showtempdir value}"
614
877
  @of << [OP_SETENV, name, value].pack("VZ*Z*")
615
878
  end
616
879
 
@@ -619,9 +882,9 @@ EOF
619
882
  end
620
883
 
621
884
  def showtempdir(x)
622
- x.gsub(TEMPDIR_MARKER, "<tempdir>")
885
+ x.to_s.gsub(TEMPDIR_ROOT, "<tempdir>")
623
886
  end
624
-
887
+
625
888
  end # class OcraBuilder
626
889
 
627
890
  end # module Ocra
@@ -630,19 +893,20 @@ if File.basename(__FILE__) == File.basename($0)
630
893
  Ocra.init(ARGV)
631
894
  ARGV.replace(Ocra.arg)
632
895
 
633
- if !File.exist?(Ocra.files[0])
634
- puts "ERROR: #{Ocra.files[0]} was not found!"
635
- exit
896
+ if not Ocra.files.first.exist?
897
+ Ocra.fatal_error "#{Ocra.files[0]} was not found!"
636
898
  end
637
899
 
638
900
  at_exit do
639
901
  if $!.nil? or $!.kind_of?(SystemExit)
640
902
  Ocra.build_exe
641
- exit(0)
903
+ exit 0
642
904
  end
643
905
  end
644
906
 
645
- puts "=== Loading script to check dependencies" unless Ocra.quiet
646
- $0 = Ocra.files[0]
647
- load Ocra.files[0]
907
+ if Ocra.run_script
908
+ Ocra.msg "Loading script to check dependencies"
909
+ $0 = Ocra.files.first
910
+ load Ocra.files.first
911
+ end
648
912
  end