ocran 1.3.15 → 1.3.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.txt +272 -0
- data/LICENSE.txt +23 -0
- data/README.md +531 -0
- data/exe/ocran +5 -0
- data/lib/ocran/build_constants.rb +16 -0
- data/lib/ocran/build_facade.rb +17 -0
- data/lib/ocran/build_helper.rb +105 -0
- data/lib/ocran/command_output.rb +22 -0
- data/lib/ocran/direction.rb +386 -0
- data/lib/ocran/file_path_set.rb +69 -0
- data/lib/ocran/gem_spec_queryable.rb +172 -0
- data/lib/ocran/host_config_helper.rb +37 -0
- data/lib/ocran/inno_setup_script_builder.rb +111 -0
- data/lib/ocran/launcher_batch_builder.rb +85 -0
- data/lib/ocran/library_detector.rb +61 -0
- data/lib/ocran/option.rb +273 -0
- data/lib/ocran/refine_pathname.rb +104 -0
- data/lib/ocran/runner.rb +105 -0
- data/lib/ocran/runtime_environment.rb +46 -0
- data/lib/ocran/stub_builder.rb +224 -0
- data/lib/ocran/version.rb +1 -1
- data/lib/ocran/windows_command_escaping.rb +15 -0
- data/lib/ocran.rb +3 -2
- data/share/ocran/edicon.exe +0 -0
- data/share/ocran/stub.exe +0 -0
- data/share/ocran/stubw.exe +0 -0
- metadata +43 -9
- data/bin/ocran +0 -1317
- /data/{share/ocran/empty-msys-2.0.dll → lib/ocran/empty_source} +0 -0
data/lib/ocran/runner.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
load File.expand_path("../ocran.rb", __dir__)
|
3
|
+
|
4
|
+
module Ocran
|
5
|
+
class Runner
|
6
|
+
load File.expand_path("command_output.rb", __dir__)
|
7
|
+
include CommandOutput
|
8
|
+
|
9
|
+
def fatal_error(statement)
|
10
|
+
error statement
|
11
|
+
exit false
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
load File.expand_path("runtime_environment.rb", __dir__)
|
16
|
+
@pre_env = RuntimeEnvironment.save
|
17
|
+
|
18
|
+
load File.expand_path("option.rb", __dir__)
|
19
|
+
@option = Option.new.tap do |opt|
|
20
|
+
opt.parse(ARGV)
|
21
|
+
rescue RuntimeError => e
|
22
|
+
# Capture RuntimeError during parsing and display an appropriate
|
23
|
+
# error message to the user. This error usually occurs from invalid
|
24
|
+
# option arguments.
|
25
|
+
fatal_error e.message
|
26
|
+
else
|
27
|
+
# Update ARGV with the parsed command line arguments to pass to
|
28
|
+
# the user's script. This ensures the script executes based on
|
29
|
+
# the user-specified arguments.
|
30
|
+
ARGV.replace(opt.argv)
|
31
|
+
end
|
32
|
+
|
33
|
+
Ocran.option = @option
|
34
|
+
|
35
|
+
@ignore_modules = ObjectSpace.each_object(Module).to_a
|
36
|
+
end
|
37
|
+
|
38
|
+
def run
|
39
|
+
at_exit do
|
40
|
+
if $!.nil? or $!.kind_of?(SystemExit)
|
41
|
+
build
|
42
|
+
exit
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
exit unless @option.run_script?
|
47
|
+
say "Loading script to check dependencies"
|
48
|
+
$PROGRAM_NAME = @option.script.to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
# Force loading autoloaded constants. Searches through all modules
|
52
|
+
# (and hence classes), and checks their constants for autoloaded
|
53
|
+
# ones, then attempts to load them.
|
54
|
+
def attempt_load_autoload(ignore_modules = [])
|
55
|
+
checked_modules = ignore_modules.inject({}) { |h, mod| h[mod] = true; h }
|
56
|
+
while ObjectSpace.each_object(Module).count { |mod|
|
57
|
+
next if checked_modules.include?(mod)
|
58
|
+
mod.constants.each do |const|
|
59
|
+
next unless mod.autoload?(const)
|
60
|
+
say "Attempting to trigger autoload of #{mod}::#{const}"
|
61
|
+
begin
|
62
|
+
mod.const_get(const)
|
63
|
+
rescue ScriptError, StandardError => e
|
64
|
+
# Some autoload constants may throw exceptions beyond the expected
|
65
|
+
# errors. This includes issues dependent on the system or execution
|
66
|
+
# environment, so it is preferable to ignore exceptions other than
|
67
|
+
# critical errors.
|
68
|
+
warning "#{mod}::#{const} loading failed: #{e.message}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
checked_modules[mod] = true
|
72
|
+
}.nonzero?
|
73
|
+
# Loops until all constants have been checked.
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def build
|
78
|
+
# If the script was run and autoload is enabled, attempt to autoload libraries.
|
79
|
+
if @option.force_autoload?
|
80
|
+
attempt_load_autoload(@ignore_modules)
|
81
|
+
end
|
82
|
+
|
83
|
+
@post_env = RuntimeEnvironment.save
|
84
|
+
# NOTE: From this point, $LOADED_FEATURES has been captured, so it is now
|
85
|
+
# safe to call require_relative.
|
86
|
+
|
87
|
+
ENV.replace(@pre_env.env)
|
88
|
+
|
89
|
+
# It might be useful to reset the current directory to the point where the
|
90
|
+
# command was launched, especially when implementing the builder object.
|
91
|
+
Dir.chdir(@pre_env.pwd)
|
92
|
+
|
93
|
+
require_relative "direction"
|
94
|
+
direction = Direction.new(@post_env, @pre_env, @option)
|
95
|
+
|
96
|
+
if @option.use_inno_setup?
|
97
|
+
direction.build_inno_setup_installer
|
98
|
+
else
|
99
|
+
direction.build_stab_exe
|
100
|
+
end
|
101
|
+
rescue RuntimeError => e
|
102
|
+
fatal_error e.message
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "pathname"
|
3
|
+
|
4
|
+
module Ocran
|
5
|
+
load File.expand_path("refine_pathname.rb", __dir__) unless defined? RefinePathname
|
6
|
+
using RefinePathname
|
7
|
+
|
8
|
+
class RuntimeEnvironment
|
9
|
+
class << self
|
10
|
+
alias save new
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :env, :load_path, :loaded_features, :pwd
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@env = ENV.to_hash.freeze
|
17
|
+
@load_path = $LOAD_PATH.dup.freeze
|
18
|
+
@loaded_features = $LOADED_FEATURES.dup.freeze
|
19
|
+
@pwd = Dir.pwd.freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
# Expands the given path using the working directory stored in this
|
23
|
+
# instance as the base. This method resolves relative paths to
|
24
|
+
# absolute paths, ensuring they are fully qualified based on the
|
25
|
+
# working directory stored within this instance.
|
26
|
+
def expand_path(path)
|
27
|
+
File.expand_path(path, @pwd)
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_load_path(path)
|
31
|
+
path = Pathname.new(path) unless path.is_a?(Pathname)
|
32
|
+
|
33
|
+
if path.absolute?
|
34
|
+
# For an absolute path feature, find the load path that contains the feature
|
35
|
+
# and determine the longest matching path (most specific path).
|
36
|
+
@load_path.select { |load_path| path.subpath?(expand_path(load_path)) }
|
37
|
+
.max_by { |load_path| expand_path(load_path).length }
|
38
|
+
else
|
39
|
+
# For a relative path feature, find the load path where the expanded feature exists
|
40
|
+
# and select the longest load path (most specific path).
|
41
|
+
@load_path.select { |load_path| path.expand_path(load_path).exist? }
|
42
|
+
.max_by { |load_path| load_path.length }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "tempfile"
|
3
|
+
require_relative "file_path_set"
|
4
|
+
|
5
|
+
module Ocran
|
6
|
+
# Utility class that produces the actual executable. Opcodes
|
7
|
+
# (create_file, mkdir etc) are added by invoking methods on an
|
8
|
+
# instance of OcranBuilder.
|
9
|
+
class StubBuilder
|
10
|
+
Signature = [0x41, 0xb6, 0xba, 0x4e].freeze
|
11
|
+
OP_END = 0
|
12
|
+
OP_CREATE_DIRECTORY = 1
|
13
|
+
OP_CREATE_FILE = 2
|
14
|
+
OP_SETENV = 3
|
15
|
+
OP_SET_SCRIPT = 4
|
16
|
+
|
17
|
+
DEBUG_MODE = 0x01
|
18
|
+
EXTRACT_TO_EXE_DIR = 0x02
|
19
|
+
AUTO_CLEAN_INST_DIR = 0x04
|
20
|
+
CHDIR_BEFORE_SCRIPT = 0x08
|
21
|
+
DATA_COMPRESSED = 0x10
|
22
|
+
|
23
|
+
base_dir = File.expand_path("../../share/ocran", File.dirname(__FILE__))
|
24
|
+
STUB_PATH = File.expand_path("stub.exe", base_dir)
|
25
|
+
STUBW_PATH = File.expand_path("stubw.exe", base_dir)
|
26
|
+
LZMA_PATH = File.expand_path("lzma.exe", base_dir)
|
27
|
+
EDICON_PATH = File.expand_path("edicon.exe", base_dir)
|
28
|
+
|
29
|
+
attr_reader :data_size
|
30
|
+
|
31
|
+
# chdir_before:
|
32
|
+
# When set to true, the working directory is changed to the application's
|
33
|
+
# deployment location at runtime.
|
34
|
+
#
|
35
|
+
# debug_mode:
|
36
|
+
# When the debug_mode option is set to true, the stub will output debug information
|
37
|
+
# when the exe file is executed. Debug mode can also be enabled within the directive
|
38
|
+
# code using the enable_debug_mode method. This option is provided to transition to
|
39
|
+
# debug mode from the initialization point of the stub.
|
40
|
+
#
|
41
|
+
# debug_extract:
|
42
|
+
# When set to true, the runtime file is extracted to the directory where the executable resides,
|
43
|
+
# and the extracted files remain even after the application exits.
|
44
|
+
# When set to false, the runtime file is extracted to the system's temporary directory,
|
45
|
+
# and the extracted files are deleted after the application exits.
|
46
|
+
#
|
47
|
+
# gui_mode:
|
48
|
+
# When set to true, the stub does not display a console window at startup. Errors are shown in a dialog window.
|
49
|
+
# When set to false, the stub reports errors through the console window.
|
50
|
+
#
|
51
|
+
# icon_path:
|
52
|
+
# Specifies the path to the icon file to be embedded in the stub's resources.
|
53
|
+
#
|
54
|
+
def initialize(path, chdir_before: nil, debug_extract: nil, debug_mode: nil,
|
55
|
+
enable_compression: nil, gui_mode: nil, icon_path: nil)
|
56
|
+
@dirs = FilePathSet.new
|
57
|
+
@files = FilePathSet.new
|
58
|
+
@data_size = 0
|
59
|
+
|
60
|
+
if icon_path && !File.exist?(icon_path)
|
61
|
+
raise "Icon file #{icon_path} not found"
|
62
|
+
end
|
63
|
+
|
64
|
+
stub = Tempfile.new("", File.dirname(path))
|
65
|
+
IO.copy_stream(gui_mode ? STUBW_PATH : STUB_PATH, stub)
|
66
|
+
stub.close
|
67
|
+
|
68
|
+
if icon_path
|
69
|
+
system(EDICON_PATH, stub.path, icon_path.to_s, exception: true)
|
70
|
+
end
|
71
|
+
|
72
|
+
File.open(stub, "ab") do |of|
|
73
|
+
@of = of
|
74
|
+
@opcode_offset = @of.size
|
75
|
+
|
76
|
+
write_header(debug_mode, debug_extract, chdir_before, enable_compression)
|
77
|
+
|
78
|
+
b = proc {
|
79
|
+
yield(self)
|
80
|
+
write_opcode(OP_END)
|
81
|
+
}
|
82
|
+
|
83
|
+
if enable_compression
|
84
|
+
compress(&b)
|
85
|
+
else
|
86
|
+
b.yield
|
87
|
+
end
|
88
|
+
|
89
|
+
write_footer
|
90
|
+
end
|
91
|
+
|
92
|
+
File.rename(stub, path)
|
93
|
+
end
|
94
|
+
|
95
|
+
def mkdir(target)
|
96
|
+
return unless @dirs.add?("/", target)
|
97
|
+
|
98
|
+
write_opcode(OP_CREATE_DIRECTORY)
|
99
|
+
write_path(target)
|
100
|
+
end
|
101
|
+
|
102
|
+
def cp(source, target)
|
103
|
+
unless File.exist?(source)
|
104
|
+
raise "The file does not exist (#{source})"
|
105
|
+
end
|
106
|
+
|
107
|
+
return unless @files.add?(source, target)
|
108
|
+
|
109
|
+
write_opcode(OP_CREATE_FILE)
|
110
|
+
write_path(target)
|
111
|
+
write_file(source)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Specifies the final application script to be launched, which can be called
|
115
|
+
# from any position in the data stream. It cannot be specified more than once.
|
116
|
+
#
|
117
|
+
# You can omit setting OP_SET_SCRIPT without issues, in which case
|
118
|
+
# the stub terminates without launching anything after performing other
|
119
|
+
# runtime operations.
|
120
|
+
def exec(image, script, *argv)
|
121
|
+
if @script_set
|
122
|
+
raise "Script is already set"
|
123
|
+
end
|
124
|
+
@script_set = true
|
125
|
+
|
126
|
+
write_opcode(OP_SET_SCRIPT)
|
127
|
+
write_string_array(convert_to_native(image), convert_to_native(script), *argv)
|
128
|
+
end
|
129
|
+
|
130
|
+
def export(name, value)
|
131
|
+
write_opcode(OP_SETENV)
|
132
|
+
write_string(name.to_s)
|
133
|
+
write_string(value.to_s)
|
134
|
+
end
|
135
|
+
|
136
|
+
def compress
|
137
|
+
IO.popen([LZMA_PATH, "e", "-si", "-so"], "r+b") do |lzma|
|
138
|
+
_of, @of = @of, lzma
|
139
|
+
Thread.new { yield(self); lzma.close_write }
|
140
|
+
IO.copy_stream(lzma, _of)
|
141
|
+
@of = _of
|
142
|
+
end
|
143
|
+
|
144
|
+
# Calculate the position to write the LZMA decompressed size (64-bit unsigned integer)
|
145
|
+
# @opcode_offset: start position of the data section
|
146
|
+
# 1: size of the header byte
|
147
|
+
# 5: size of the LZMA header in bytes
|
148
|
+
File.binwrite(@of.path, [@data_size].pack("Q<"), @opcode_offset + 1 + 5)
|
149
|
+
end
|
150
|
+
private :compress
|
151
|
+
|
152
|
+
def write_header(debug_mode, debug_extract, chdir_before, compressed)
|
153
|
+
next_to_exe, delete_after = debug_extract, !debug_extract
|
154
|
+
@of << [0 |
|
155
|
+
(debug_mode ? DEBUG_MODE : 0) |
|
156
|
+
(next_to_exe ? EXTRACT_TO_EXE_DIR : 0) |
|
157
|
+
(delete_after ? AUTO_CLEAN_INST_DIR : 0) |
|
158
|
+
(chdir_before ? CHDIR_BEFORE_SCRIPT : 0) |
|
159
|
+
(compressed ? DATA_COMPRESSED : 0)
|
160
|
+
].pack("C")
|
161
|
+
end
|
162
|
+
private :write_header
|
163
|
+
|
164
|
+
def write_opcode(op)
|
165
|
+
@of << [op].pack("C")
|
166
|
+
@data_size += 1
|
167
|
+
end
|
168
|
+
private :write_opcode
|
169
|
+
|
170
|
+
def write_size(i)
|
171
|
+
if i > 0xFFFF_FFFF
|
172
|
+
raise ArgumentError, "Size #{i} is too large: must be 32-bit unsigned integer (0 to 4294967295)"
|
173
|
+
end
|
174
|
+
|
175
|
+
@of << [i].pack("V")
|
176
|
+
@data_size += 4
|
177
|
+
end
|
178
|
+
private :write_size
|
179
|
+
|
180
|
+
def write_string(str)
|
181
|
+
len = str.bytesize + 1 # +1 to account for the null terminator
|
182
|
+
|
183
|
+
if len > 0xFFFF
|
184
|
+
raise ArgumentError, "String length #{len} is too large: must be less than or equal to 65535 bytes including null terminator"
|
185
|
+
end
|
186
|
+
|
187
|
+
@of << [len, str].pack("vZ*")
|
188
|
+
@data_size += 2 + len
|
189
|
+
end
|
190
|
+
private :write_string
|
191
|
+
|
192
|
+
def write_string_array(*str_array)
|
193
|
+
ary = str_array.map(&:to_s)
|
194
|
+
size = ary.sum(0) { |s| s.bytesize + 1 }
|
195
|
+
write_size(size)
|
196
|
+
ary.each_slice(1) { |a| @of << a.pack("Z*") }
|
197
|
+
@data_size += size
|
198
|
+
end
|
199
|
+
private :write_string_array
|
200
|
+
|
201
|
+
def write_file(src)
|
202
|
+
size = File.size(src)
|
203
|
+
write_size(size)
|
204
|
+
IO.copy_stream(src, @of)
|
205
|
+
@data_size += size
|
206
|
+
end
|
207
|
+
private :write_file
|
208
|
+
|
209
|
+
def write_path(path)
|
210
|
+
write_string(convert_to_native(path))
|
211
|
+
end
|
212
|
+
private :write_path
|
213
|
+
|
214
|
+
def write_footer
|
215
|
+
@of << ([@opcode_offset] + Signature).pack("VC*")
|
216
|
+
end
|
217
|
+
private :write_footer
|
218
|
+
|
219
|
+
def convert_to_native(path)
|
220
|
+
path.to_s.tr(File::SEPARATOR, "\\")
|
221
|
+
end
|
222
|
+
private :convert_to_native
|
223
|
+
end
|
224
|
+
end
|
data/lib/ocran/version.rb
CHANGED
data/lib/ocran.rb
CHANGED
data/share/ocran/edicon.exe
CHANGED
Binary file
|
data/share/ocran/stub.exe
CHANGED
Binary file
|
data/share/ocran/stubw.exe
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,21 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ocran
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andi Idogawa
|
8
8
|
- Lars Christensen
|
9
9
|
autorequire:
|
10
|
-
bindir:
|
10
|
+
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
12
|
+
date: 2025-05-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: fiddle
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.0'
|
14
28
|
description: "OCRAN (One-Click Ruby Application Next) builds Windows executables from
|
15
29
|
Ruby source code. \n The executable is a self-extracting, self-running executable
|
16
30
|
that contains the Ruby interpreter, your source code and any additionally needed
|
17
31
|
ruby libraries or DLL.\n \n This is a fork of OCRA that is compatible with ruby
|
18
|
-
version after
|
32
|
+
version after 3.0.\n Migration guide: make sure to write ocran instead of ocra
|
19
33
|
in your code. For instance: OCRAN_EXECUTABLE\n\n usage: \n ocra helloworld.rb\n
|
20
34
|
\ helloworld.exe\n\n See readme at https://github.com/largo/ocran\n Report problems
|
21
35
|
in the github issues. Contributions welcome.\n This gem contains executables. We
|
@@ -27,11 +41,31 @@ executables:
|
|
27
41
|
extensions: []
|
28
42
|
extra_rdoc_files: []
|
29
43
|
files:
|
30
|
-
-
|
44
|
+
- CHANGELOG.txt
|
45
|
+
- LICENSE.txt
|
46
|
+
- README.md
|
47
|
+
- exe/ocran
|
31
48
|
- lib/ocran.rb
|
49
|
+
- lib/ocran/build_constants.rb
|
50
|
+
- lib/ocran/build_facade.rb
|
51
|
+
- lib/ocran/build_helper.rb
|
52
|
+
- lib/ocran/command_output.rb
|
53
|
+
- lib/ocran/direction.rb
|
54
|
+
- lib/ocran/empty_source
|
55
|
+
- lib/ocran/file_path_set.rb
|
56
|
+
- lib/ocran/gem_spec_queryable.rb
|
57
|
+
- lib/ocran/host_config_helper.rb
|
58
|
+
- lib/ocran/inno_setup_script_builder.rb
|
59
|
+
- lib/ocran/launcher_batch_builder.rb
|
60
|
+
- lib/ocran/library_detector.rb
|
61
|
+
- lib/ocran/option.rb
|
62
|
+
- lib/ocran/refine_pathname.rb
|
63
|
+
- lib/ocran/runner.rb
|
64
|
+
- lib/ocran/runtime_environment.rb
|
65
|
+
- lib/ocran/stub_builder.rb
|
32
66
|
- lib/ocran/version.rb
|
67
|
+
- lib/ocran/windows_command_escaping.rb
|
33
68
|
- share/ocran/edicon.exe
|
34
|
-
- share/ocran/empty-msys-2.0.dll
|
35
69
|
- share/ocran/lzma.exe
|
36
70
|
- share/ocran/stub.exe
|
37
71
|
- share/ocran/stubw.exe
|
@@ -50,14 +84,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
84
|
requirements:
|
51
85
|
- - ">="
|
52
86
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
87
|
+
version: 3.0.0
|
54
88
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
89
|
requirements:
|
56
90
|
- - ">="
|
57
91
|
- !ruby/object:Gem::Version
|
58
92
|
version: '0'
|
59
93
|
requirements: []
|
60
|
-
rubygems_version: 3.
|
94
|
+
rubygems_version: 3.5.3
|
61
95
|
signing_key:
|
62
96
|
specification_version: 4
|
63
97
|
summary: OCRAN (One-Click Ruby Application Next) builds Windows executables from Ruby
|