rpremote 0.5.0 → 0.7.0
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.md +13 -0
- data/README.ja.md +30 -46
- data/README.md +30 -47
- data/RELEASING.md +2 -2
- data/THIRD_PARTY_NOTICES.md +1 -1
- data/lib/rpremote/bootsel_command.rb +8 -6
- data/lib/rpremote/build_command.rb +3 -3
- data/lib/rpremote/builder.rb +15 -9
- data/lib/rpremote/cli/filesystem_commands.rb +105 -0
- data/lib/rpremote/cli.rb +20 -105
- data/lib/rpremote/config.rb +6 -5
- data/lib/rpremote/config_show.rb +13 -5
- data/lib/rpremote/deploy_command.rb +85 -55
- data/lib/rpremote/help/dfu_commands.rb +59 -0
- data/lib/rpremote/help/filesystem_commands.rb +36 -0
- data/lib/rpremote/help/project_commands.rb +89 -0
- data/lib/rpremote/help/remote_commands.rb +54 -0
- data/lib/rpremote/help/root_text.rb +63 -0
- data/lib/rpremote/help.rb +27 -300
- data/lib/rpremote/language_source.rb +19 -2
- data/lib/rpremote/mrbgems/definition.rb +116 -0
- data/lib/rpremote/mrbgems/file_writer.rb +26 -0
- data/lib/rpremote/mrbgems/lockfile.rb +131 -0
- data/lib/rpremote/mrbgems.rb +95 -155
- data/lib/rpremote/mrbgems_command.rb +19 -4
- data/lib/rpremote/picoruby_compiler.rb +28 -0
- data/lib/rpremote/picoruby_source_patch.rb +0 -4
- data/lib/rpremote/target.rb +1 -1
- data/lib/rpremote/terminal.rb +0 -2
- data/lib/rpremote/version.rb +1 -1
- data/lib/rpremote.rb +1 -0
- data/patches/picoruby-4.0.4-pwm-sleep-clock.patch +27 -0
- data/sig/rpremote.rbs +20 -4
- data/tasks/firmware_build.rb +10 -2
- metadata +12 -3
- data/patches/picoruby-3.4.5-bootsel.patch +0 -71
- data/patches/picoruby-4.0.3-bootsel.patch +0 -71
data/lib/rpremote/cli.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "optparse"
|
|
4
4
|
require_relative "build_command"
|
|
5
5
|
require_relative "bootsel_command"
|
|
6
|
+
require_relative "cli/filesystem_commands"
|
|
6
7
|
require_relative "config_show"
|
|
7
8
|
require_relative "dfu_command"
|
|
8
9
|
require_relative "deploy_command"
|
|
@@ -12,9 +13,10 @@ require_relative "mrbgems_command"
|
|
|
12
13
|
require_relative "recursive_copy"
|
|
13
14
|
require_relative "setup_command"
|
|
14
15
|
|
|
15
|
-
# rubocop:disable Metrics/ClassLength
|
|
16
16
|
module Rpremote
|
|
17
17
|
class CLI
|
|
18
|
+
include FilesystemCommands
|
|
19
|
+
|
|
18
20
|
def self.start(argv = ARGV, config: Config, **options)
|
|
19
21
|
new(**options).run(argv, config: config)
|
|
20
22
|
rescue OptionParser::ParseError, ArgumentError, SystemCallError, Rpremote::Error => e
|
|
@@ -136,44 +138,24 @@ module Rpremote
|
|
|
136
138
|
ConfigShow.run(args, defaults: config_options, output: stdout)
|
|
137
139
|
end
|
|
138
140
|
|
|
139
|
-
def fs(args)
|
|
140
|
-
subcommand = args.shift
|
|
141
|
-
recursive = subcommand == "cp" && (args.delete("--recursive") || args.delete("-r"))
|
|
142
|
-
options = parse_connection_options(args)
|
|
143
|
-
case subcommand
|
|
144
|
-
when "cp"
|
|
145
|
-
copy(args, options, recursive: recursive)
|
|
146
|
-
when "push"
|
|
147
|
-
copy(args, options, recursive: true, command: "push")
|
|
148
|
-
when "cat"
|
|
149
|
-
cat(args, options)
|
|
150
|
-
when "ls", "rm", "mkdir"
|
|
151
|
-
filesystem_shell_command(subcommand, args, options)
|
|
152
|
-
else
|
|
153
|
-
raise ArgumentError, "unknown fs command: #{subcommand || "(none)"}"
|
|
154
|
-
end
|
|
155
|
-
rescue Shell::TimeoutError => e
|
|
156
|
-
raise Shell::TimeoutError, "filesystem connection failed: #{e.message}"
|
|
157
|
-
end
|
|
158
|
-
|
|
159
141
|
def run_file(args)
|
|
160
142
|
reset_on_timeout = !args.delete("--reset-on-timeout").nil?
|
|
161
|
-
options = parse_connection_options(args)
|
|
143
|
+
options = parse_connection_options(args, mrbgems_lock: true)
|
|
162
144
|
raise ArgumentError, "usage: rpremote run FILE [options]" unless args.length == 1
|
|
163
145
|
|
|
164
146
|
source = args.first
|
|
165
147
|
source = File.join(source, "main.rb") if File.directory?(source)
|
|
166
|
-
data = prepend_mrbgem_requires(File.binread(source))
|
|
148
|
+
data = prepend_mrbgem_requires(File.binread(source), options[:mrbgems_lock])
|
|
167
149
|
execute_temporary(data, options, output: stdout, reset_on_timeout: reset_on_timeout)
|
|
168
150
|
rescue Errno::ENOENT => e
|
|
169
151
|
raise ArgumentError, e.message
|
|
170
152
|
end
|
|
171
153
|
|
|
172
154
|
def execute_code(args)
|
|
173
|
-
options = parse_connection_options(args)
|
|
155
|
+
options = parse_connection_options(args, mrbgems_lock: true)
|
|
174
156
|
raise ArgumentError, "usage: rpremote exec CODE [options]" unless args.length == 1
|
|
175
157
|
|
|
176
|
-
execute_temporary(prepend_mrbgem_requires(args.first), options, output: stdout)
|
|
158
|
+
execute_temporary(prepend_mrbgem_requires(args.first, options[:mrbgems_lock]), options, output: stdout)
|
|
177
159
|
end
|
|
178
160
|
|
|
179
161
|
def execute_temporary(data, options, output: nil, reset_on_timeout: false)
|
|
@@ -197,8 +179,13 @@ module Rpremote
|
|
|
197
179
|
"#{timeout_error.message}; automatic reset failed: #{e.message}"
|
|
198
180
|
end
|
|
199
181
|
|
|
200
|
-
def prepend_mrbgem_requires(data)
|
|
201
|
-
|
|
182
|
+
def prepend_mrbgem_requires(data, lock_file)
|
|
183
|
+
return data if lock_file == false
|
|
184
|
+
|
|
185
|
+
path = lock_file || Mrbgems::DEFAULT_LOCK_PATH
|
|
186
|
+
raise ArgumentError, "mrbgems lock file does not exist: #{path}" if lock_file && !File.file?(path)
|
|
187
|
+
|
|
188
|
+
Mrbgems.new(cwd: Dir.pwd, lock_path: path).prepend_requires(data)
|
|
202
189
|
end
|
|
203
190
|
|
|
204
191
|
def interactive(args, repl: false)
|
|
@@ -227,18 +214,23 @@ module Rpremote
|
|
|
227
214
|
stdout.puts("reset R2P2: #{port_path}")
|
|
228
215
|
end
|
|
229
216
|
|
|
230
|
-
def parse_connection_options(args, default_timeout: PicoModem::DEFAULT_TIMEOUT)
|
|
217
|
+
def parse_connection_options(args, default_timeout: PicoModem::DEFAULT_TIMEOUT, mrbgems_lock: false)
|
|
231
218
|
options = {
|
|
232
219
|
port: config_options[:port],
|
|
233
220
|
baud: config_options.fetch(:baud, Serial::BAUD_RATE),
|
|
234
221
|
timeout: config_options.fetch(:timeout, default_timeout),
|
|
235
222
|
language: config_options.fetch(:language, Target::DEFAULT_LANGUAGE)
|
|
236
223
|
}
|
|
224
|
+
options[:mrbgems_lock] = config_options[:mrbgems_lock] if mrbgems_lock
|
|
237
225
|
parser = OptionParser.new do |opts|
|
|
238
226
|
opts.on("--port PORT") { |value| options[:port] = value }
|
|
239
227
|
opts.on("--baud RATE", Integer) { |value| options[:baud] = value }
|
|
240
228
|
opts.on("--timeout SECONDS", Float) { |value| options[:timeout] = value }
|
|
241
229
|
opts.on("--language LANGUAGE") { |value| options[:language] = value }
|
|
230
|
+
if mrbgems_lock
|
|
231
|
+
opts.on("--lockfile FILE") { |value| options[:mrbgems_lock] = value }
|
|
232
|
+
opts.on("--no-mrbgems") { options[:mrbgems_lock] = false }
|
|
233
|
+
end
|
|
242
234
|
end
|
|
243
235
|
parser.parse!(args)
|
|
244
236
|
raise ArgumentError, "--baud must be positive" unless options[:baud].positive?
|
|
@@ -246,82 +238,5 @@ module Rpremote
|
|
|
246
238
|
|
|
247
239
|
options
|
|
248
240
|
end
|
|
249
|
-
|
|
250
|
-
def copy(args, options, recursive: false, command: "cp")
|
|
251
|
-
usage = command == "push" ? "rpremote fs push LOCAL_DIR :/REMOTE_DIR" : "rpremote fs cp SOURCE DESTINATION"
|
|
252
|
-
raise ArgumentError, "usage: #{usage} [options]" unless args.length == 2
|
|
253
|
-
|
|
254
|
-
source, destination = args
|
|
255
|
-
source_remote = RemotePath.remote?(source)
|
|
256
|
-
destination_remote = RemotePath.remote?(destination)
|
|
257
|
-
raise ArgumentError, "exactly one cp path must be remote (prefix remote paths with :)" if source_remote == destination_remote
|
|
258
|
-
return RecursiveCopy.new(output: stdout, serial: serial, device: device).call(source, destination, options) if recursive
|
|
259
|
-
|
|
260
|
-
ensure_filesystem_connection!(options)
|
|
261
|
-
if source_remote
|
|
262
|
-
data = with_modem(options) { |modem| modem.download(RemotePath.unwrap(source)) }
|
|
263
|
-
File.binwrite(local_destination(destination, source), data)
|
|
264
|
-
stdout.puts("downloaded #{data.bytesize} bytes: #{source} -> #{destination}")
|
|
265
|
-
else
|
|
266
|
-
data = File.binread(source)
|
|
267
|
-
with_modem(options) { |modem| modem.upload(RemotePath.unwrap(destination), data) }
|
|
268
|
-
stdout.puts("uploaded #{data.bytesize} bytes: #{source} -> #{destination}")
|
|
269
|
-
end
|
|
270
|
-
rescue Errno::ENOENT => e
|
|
271
|
-
raise ArgumentError, e.message
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
def cat(args, options)
|
|
275
|
-
raise ArgumentError, "usage: rpremote fs cat :/REMOTE/PATH [options]" unless args.length == 1
|
|
276
|
-
raise ArgumentError, "cat path must be remote (prefix it with :)" unless RemotePath.remote?(args.first)
|
|
277
|
-
|
|
278
|
-
stdout.write(with_modem(options) { |modem| modem.download(RemotePath.unwrap(args.first)) })
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
def filesystem_shell_command(command, args, options)
|
|
282
|
-
usage = "usage: rpremote fs #{command} :/REMOTE/PATH [options]"
|
|
283
|
-
raise ArgumentError, usage unless args.length == 1
|
|
284
|
-
|
|
285
|
-
path = RemotePath.validate(args.first)
|
|
286
|
-
stdout.puts("deleting remote path permanently: #{path}") if command == "rm"
|
|
287
|
-
output = with_shell(options) do |shell|
|
|
288
|
-
shell.execute("#{command} #{Shell.quote_argument(path)}")
|
|
289
|
-
end
|
|
290
|
-
ensure_filesystem_success!(command, output)
|
|
291
|
-
stdout.write(output)
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
def ensure_filesystem_success!(command, output)
|
|
295
|
-
failed = command == "ls" ? output.start_with?("ls:") : !output.empty?
|
|
296
|
-
raise Shell::CommandError, output.strip if failed
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
def ensure_filesystem_connection!(options)
|
|
300
|
-
output = with_shell(options) { |shell| shell.execute("ls '/'") }
|
|
301
|
-
ensure_filesystem_success!("ls", output)
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
def with_modem(options)
|
|
305
|
-
port_path = device.main_port(options[:port])
|
|
306
|
-
serial.open(port_path, baud: options[:baud]) do |port|
|
|
307
|
-
yield PicoModem.new(port, timeout: options[:timeout])
|
|
308
|
-
end
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
def with_shell(options)
|
|
312
|
-
port_path = device.main_port(options[:port])
|
|
313
|
-
serial.open(port_path, baud: options[:baud]) do |port|
|
|
314
|
-
shell = Shell.new(port, timeout: options[:timeout])
|
|
315
|
-
shell.synchronize!
|
|
316
|
-
yield shell
|
|
317
|
-
end
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
def local_destination(destination, source)
|
|
321
|
-
return destination unless File.directory?(destination)
|
|
322
|
-
|
|
323
|
-
File.join(destination, File.basename(RemotePath.unwrap(source)))
|
|
324
|
-
end
|
|
325
241
|
end
|
|
326
242
|
end
|
|
327
|
-
# rubocop:enable Metrics/ClassLength
|
data/lib/rpremote/config.rb
CHANGED
|
@@ -16,17 +16,17 @@ module Rpremote
|
|
|
16
16
|
baud: Integer,
|
|
17
17
|
timeout: Numeric,
|
|
18
18
|
language: String,
|
|
19
|
-
|
|
19
|
+
mrbgems_lock: [String, FalseClass].freeze
|
|
20
20
|
}.freeze
|
|
21
21
|
COMMAND_OPTIONS = {
|
|
22
22
|
"setup" => %i[cache language language_version],
|
|
23
|
-
"build" => %i[cache language language_version board firmware
|
|
23
|
+
"build" => %i[cache language language_version board firmware mrbgems_lock],
|
|
24
24
|
"bootsel" => %i[mount port baud timeout],
|
|
25
|
-
"deploy" => %i[cache language language_version board firmware
|
|
25
|
+
"deploy" => %i[cache language language_version board firmware mrbgems_lock mount port baud timeout],
|
|
26
26
|
"dfu" => %i[cache language language_version port baud timeout],
|
|
27
27
|
"flash" => %i[cache language language_version board firmware mount port timeout],
|
|
28
|
-
"run" => %i[port baud timeout language],
|
|
29
|
-
"exec" => %i[port baud timeout language],
|
|
28
|
+
"run" => %i[port baud timeout language mrbgems_lock],
|
|
29
|
+
"exec" => %i[port baud timeout language mrbgems_lock],
|
|
30
30
|
"reset" => %i[port baud timeout],
|
|
31
31
|
"monitor" => %i[port baud timeout],
|
|
32
32
|
"repl" => %i[port baud timeout],
|
|
@@ -106,6 +106,7 @@ module Rpremote
|
|
|
106
106
|
expected = OPTION_TYPES[key]
|
|
107
107
|
raise Error, "unknown config option: #{key}" unless expected
|
|
108
108
|
raise Error, "config option #{key} must be a #{type_name(expected)}" unless valid_value?(value, expected)
|
|
109
|
+
|
|
109
110
|
next unless %i[baud timeout].include?(key)
|
|
110
111
|
next if value.positive?
|
|
111
112
|
|
data/lib/rpremote/config_show.rb
CHANGED
|
@@ -38,7 +38,7 @@ module Rpremote
|
|
|
38
38
|
board: defaults.fetch(:board, Target::DEFAULT_BOARD),
|
|
39
39
|
cache: defaults.fetch(:cache, Target::DEFAULT_CACHE_DIR),
|
|
40
40
|
firmware: defaults[:firmware],
|
|
41
|
-
|
|
41
|
+
mrbgems_lock: defaults[:mrbgems_lock],
|
|
42
42
|
mount: defaults[:mount],
|
|
43
43
|
port: defaults[:port],
|
|
44
44
|
baud: defaults.fetch(:baud, Serial::BAUD_RATE),
|
|
@@ -53,8 +53,8 @@ module Rpremote
|
|
|
53
53
|
opts.on("--board BOARD") { |value| options[:board] = value }
|
|
54
54
|
opts.on("--cache DIR") { |value| options[:cache] = value }
|
|
55
55
|
opts.on("--firmware FILE") { |value| options[:firmware] = value }
|
|
56
|
-
opts.on("--
|
|
57
|
-
opts.on("--no-mrbgems") { options[:
|
|
56
|
+
opts.on("--lockfile FILE") { |value| options[:mrbgems_lock] = value }
|
|
57
|
+
opts.on("--no-mrbgems") { options[:mrbgems_lock] = false }
|
|
58
58
|
opts.on("--mount DIR") { |value| options[:mount] = value }
|
|
59
59
|
opts.on("--port PORT") { |value| options[:port] = value }
|
|
60
60
|
opts.on("--baud RATE", Integer) { |value| options[:baud] = value }
|
|
@@ -70,19 +70,27 @@ module Rpremote
|
|
|
70
70
|
cache_dir: options[:cache],
|
|
71
71
|
firmware: options[:firmware]
|
|
72
72
|
)
|
|
73
|
+
print_target_options(target)
|
|
74
|
+
print_runtime_options(options)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def print_target_options(target)
|
|
73
78
|
output.puts("language=#{target.language}")
|
|
74
79
|
output.puts("language_version=#{target.language_version}")
|
|
75
80
|
output.puts("board=#{target.board}")
|
|
76
81
|
output.puts("cache=#{target.cache_dir}")
|
|
77
82
|
output.puts("firmware=#{target.firmware_path}")
|
|
78
|
-
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def print_runtime_options(options)
|
|
86
|
+
output.puts("mrbgems_lock=#{mrbgems_lock_value(options[:mrbgems_lock])}")
|
|
79
87
|
output.puts("mount=#{options[:mount] || "auto"}")
|
|
80
88
|
output.puts("port=#{options[:port] || "auto"}")
|
|
81
89
|
output.puts("baud=#{options[:baud]}")
|
|
82
90
|
output.puts("timeout=#{options[:timeout]}")
|
|
83
91
|
end
|
|
84
92
|
|
|
85
|
-
def
|
|
93
|
+
def mrbgems_lock_value(value)
|
|
86
94
|
return "false" if value == false
|
|
87
95
|
|
|
88
96
|
value || "auto"
|
|
@@ -9,79 +9,106 @@ module Rpremote
|
|
|
9
9
|
SHELL_READY_TIMEOUT = 2.0
|
|
10
10
|
RETRY_INTERVAL = 0.25
|
|
11
11
|
|
|
12
|
+
Context = Data.define(:options, :output, :error, :services, :target_options, :firmware_path)
|
|
13
|
+
|
|
12
14
|
class Error < Rpremote::Error; end
|
|
13
15
|
|
|
14
|
-
# The command intentionally keeps the deployment stages visible in execution order.
|
|
15
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
16
16
|
def self.run(args, defaults:, output: $stdout, error: $stderr, services: {})
|
|
17
17
|
options = parse_options(args, defaults)
|
|
18
18
|
raise ArgumentError, "usage: rpremote deploy PATH [options]" unless args.length == 1
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
library, remote_library, source = project_files(project)
|
|
20
|
+
library, remote_library, source = project_files(File.expand_path(args.first))
|
|
22
21
|
Language.validate!(options[:language])
|
|
22
|
+
context = deployment_context(options, output, error, services)
|
|
23
|
+
build_firmware(context)
|
|
24
|
+
result = flash_firmware(context)
|
|
25
|
+
connect_to_shell(context, result)
|
|
26
|
+
push_library(context, result, library, remote_library)
|
|
27
|
+
run_source(context, result, source)
|
|
28
|
+
rescue Errno::ENOENT => e
|
|
29
|
+
raise ArgumentError, e.message
|
|
30
|
+
end
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
serial = services.fetch(:serial, Serial)
|
|
26
|
-
device = services.fetch(:device, Device)
|
|
27
|
-
runner = services.fetch(:runner, Runner)
|
|
32
|
+
def self.deployment_context(options, output, error, services)
|
|
28
33
|
target_options = options.slice(:language, :language_version, :board, :cache_dir, :firmware)
|
|
29
34
|
firmware_path = Target.new(**target_options).firmware_path(root: Dir.pwd)
|
|
35
|
+
Context.new(options:, output:, error:, services:, target_options:, firmware_path:)
|
|
36
|
+
end
|
|
37
|
+
private_class_method :deployment_context
|
|
38
|
+
|
|
39
|
+
def self.build_firmware(context)
|
|
40
|
+
return unless context.options[:build]
|
|
41
|
+
|
|
42
|
+
builder = context.services.fetch(:builder) { Builder.new }
|
|
43
|
+
context.output.puts("deploy build: #{context.firmware_path}")
|
|
44
|
+
builder.build(
|
|
45
|
+
**context.target_options,
|
|
46
|
+
mrbgems_lock: context.options[:mrbgems_lock],
|
|
47
|
+
output: context.output,
|
|
48
|
+
error: context.error
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
private_class_method :build_firmware
|
|
30
52
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
**target_options,
|
|
36
|
-
mrbgems: options[:mrbgems],
|
|
37
|
-
output: output,
|
|
38
|
-
error: error
|
|
39
|
-
)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
flash_service = flasher.new(timeout: options[:timeout])
|
|
43
|
-
bootsel_mount = resolve_bootsel_mount(options, flash_service, output, services)
|
|
44
|
-
|
|
53
|
+
def self.flash_firmware(context)
|
|
54
|
+
options = context.options
|
|
55
|
+
flash_service = context.services.fetch(:flasher, Flasher).new(timeout: options[:timeout])
|
|
56
|
+
bootsel_mount = resolve_bootsel_mount(options, flash_service, context.output, context.services)
|
|
45
57
|
mount = options[:mount] || "an automatically detected RP2350 BOOTSEL volume"
|
|
46
|
-
output.puts(
|
|
47
|
-
|
|
48
|
-
firmware_path,
|
|
49
|
-
mount: bootsel_mount,
|
|
50
|
-
port: options[:port]
|
|
58
|
+
context.output.puts(
|
|
59
|
+
"deploy flash: #{context.firmware_path} to #{mount}; this replaces persistent board firmware"
|
|
51
60
|
)
|
|
61
|
+
flash_service.flash(context.firmware_path, mount: bootsel_mount, port: options[:port])
|
|
62
|
+
end
|
|
63
|
+
private_class_method :flash_firmware
|
|
52
64
|
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
def self.connect_to_shell(context, result)
|
|
66
|
+
options = context.options
|
|
67
|
+
context.output.puts("deploy connect: waiting for R2P2 Shell on #{result.port}")
|
|
55
68
|
wait_for_shell(
|
|
56
|
-
port: result.port, baud: options[:baud], timeout: options[:timeout],
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
69
|
+
port: result.port, baud: options[:baud], timeout: options[:timeout],
|
|
70
|
+
serial: context.services.fetch(:serial, Serial),
|
|
71
|
+
shell: context.services.fetch(:shell, Shell),
|
|
72
|
+
sleeper: context.services.fetch(:sleeper) { ->(seconds) { sleep(seconds) } },
|
|
73
|
+
clock: context.services.fetch(:clock) { -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) } }
|
|
60
74
|
)
|
|
61
|
-
output.puts("deploy connect: ready on #{result.port}")
|
|
75
|
+
context.output.puts("deploy connect: ready on #{result.port}")
|
|
76
|
+
end
|
|
77
|
+
private_class_method :connect_to_shell
|
|
62
78
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
end
|
|
68
|
-
recursive_copy.call(library, ":#{remote_library}", connection_options)
|
|
69
|
-
else
|
|
70
|
-
output.puts("deploy push: skipped; directory not found: #{library}")
|
|
79
|
+
def self.push_library(context, result, library, remote_library)
|
|
80
|
+
unless File.directory?(library)
|
|
81
|
+
context.output.puts("deploy push: skipped; directory not found: #{library}")
|
|
82
|
+
return
|
|
71
83
|
end
|
|
72
84
|
|
|
73
|
-
output.puts("deploy
|
|
74
|
-
|
|
85
|
+
context.output.puts("deploy push: #{library} -> :#{remote_library}")
|
|
86
|
+
recursive_copy = context.services.fetch(:recursive_copy) do
|
|
87
|
+
RecursiveCopy.new(
|
|
88
|
+
output: context.output,
|
|
89
|
+
serial: context.services.fetch(:serial, Serial),
|
|
90
|
+
device: context.services.fetch(:device, Device)
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
connection_options = context.options.except(:build).merge(port: result.port)
|
|
94
|
+
recursive_copy.call(library, ":#{remote_library}", connection_options)
|
|
95
|
+
end
|
|
96
|
+
private_class_method :push_library
|
|
97
|
+
|
|
98
|
+
def self.run_source(context, result, source)
|
|
99
|
+
options = context.options
|
|
100
|
+
context.output.puts("deploy run: #{source} on #{result.port}")
|
|
101
|
+
source_data = prepend_mrbgem_requires(File.binread(source), options[:mrbgems_lock])
|
|
102
|
+
serial = context.services.fetch(:serial, Serial)
|
|
103
|
+
runner = context.services.fetch(:runner, Runner)
|
|
75
104
|
run_output = serial.open(result.port, baud: options[:baud]) do |port|
|
|
76
105
|
runner.new(port, timeout: options[:timeout]).run(
|
|
77
|
-
source_data, output: output, cleanup: false, remote_path: Runner::DEPLOY_REMOTE_PATH
|
|
106
|
+
source_data, output: context.output, cleanup: false, remote_path: Runner::DEPLOY_REMOTE_PATH
|
|
78
107
|
)
|
|
79
108
|
end
|
|
80
|
-
output.puts("deploy run: completed; output bytes: #{run_output.to_s.bytesize}")
|
|
81
|
-
rescue Errno::ENOENT => e
|
|
82
|
-
raise ArgumentError, e.message
|
|
109
|
+
context.output.puts("deploy run: completed; output bytes: #{run_output.to_s.bytesize}")
|
|
83
110
|
end
|
|
84
|
-
|
|
111
|
+
private_class_method :run_source
|
|
85
112
|
|
|
86
113
|
def self.project_files(project)
|
|
87
114
|
raise ArgumentError, "project directory not found: #{project}" unless File.directory?(project)
|
|
@@ -95,10 +122,13 @@ module Rpremote
|
|
|
95
122
|
end
|
|
96
123
|
private_class_method :project_files
|
|
97
124
|
|
|
98
|
-
def self.prepend_mrbgem_requires(source,
|
|
99
|
-
return source if
|
|
125
|
+
def self.prepend_mrbgem_requires(source, lock_file)
|
|
126
|
+
return source if lock_file == false
|
|
127
|
+
|
|
128
|
+
path = lock_file || Mrbgems::DEFAULT_LOCK_PATH
|
|
129
|
+
raise ArgumentError, "mrbgems lock file does not exist: #{path}" if lock_file && !File.file?(path)
|
|
100
130
|
|
|
101
|
-
Mrbgems.new(
|
|
131
|
+
Mrbgems.new(lock_path: path, cwd: Dir.pwd).prepend_requires(source)
|
|
102
132
|
end
|
|
103
133
|
private_class_method :prepend_mrbgem_requires
|
|
104
134
|
|
|
@@ -158,7 +188,7 @@ module Rpremote
|
|
|
158
188
|
board: defaults.fetch(:board, Target::DEFAULT_BOARD),
|
|
159
189
|
cache_dir: defaults.fetch(:cache, Target::DEFAULT_CACHE_DIR),
|
|
160
190
|
firmware: defaults[:firmware],
|
|
161
|
-
|
|
191
|
+
mrbgems_lock: defaults[:mrbgems_lock],
|
|
162
192
|
mount: defaults[:mount],
|
|
163
193
|
port: defaults[:port],
|
|
164
194
|
baud: defaults.fetch(:baud, Serial::BAUD_RATE),
|
|
@@ -176,8 +206,8 @@ module Rpremote
|
|
|
176
206
|
parser.on("--board BOARD") { |value| options[:board] = value }
|
|
177
207
|
parser.on("--cache DIR") { |value| options[:cache_dir] = value }
|
|
178
208
|
parser.on("--firmware FILE") { |value| options[:firmware] = value }
|
|
179
|
-
parser.on("--
|
|
180
|
-
parser.on("--no-mrbgems") { options[:
|
|
209
|
+
parser.on("--lockfile FILE") { |value| options[:mrbgems_lock] = value }
|
|
210
|
+
parser.on("--no-mrbgems") { options[:mrbgems_lock] = false }
|
|
181
211
|
parser.on("--mount DIR") { |value| options[:mount] = value }
|
|
182
212
|
parser.on("--port PORT") { |value| options[:port] = value }
|
|
183
213
|
parser.on("--baud RATE", Integer) { |value| options[:baud] = value }
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rpremote
|
|
4
|
+
module Help
|
|
5
|
+
def self.dfu_text(subcommand)
|
|
6
|
+
return dfu_app_text if subcommand == "app"
|
|
7
|
+
return dfu_compile_text if subcommand == "compile"
|
|
8
|
+
return dfu_status_text if subcommand == "status"
|
|
9
|
+
return dfu_remove_text if subcommand == "remove"
|
|
10
|
+
|
|
11
|
+
<<~HELP
|
|
12
|
+
Usage: #{COMMAND_USAGE.fetch(:dfu_app)}
|
|
13
|
+
#{COMMAND_USAGE.fetch(:dfu_compile)}
|
|
14
|
+
#{COMMAND_USAGE.fetch(:dfu_status)}
|
|
15
|
+
#{COMMAND_USAGE.fetch(:dfu_remove)}
|
|
16
|
+
|
|
17
|
+
Stages and inspects PicoModem DFU applications. Run `rpremote dfu SUBCOMMAND --help` for details.
|
|
18
|
+
HELP
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.dfu_app_text
|
|
22
|
+
<<~HELP
|
|
23
|
+
Usage: #{COMMAND_USAGE.fetch(:dfu_app)}
|
|
24
|
+
|
|
25
|
+
Transfers a Ruby source or matching RITE bytecode application to the inactive DFU slot. The next R2P2 restart tries it.
|
|
26
|
+
Options default to the configured or automatically selected CDC 0 port, 115200 baud, and 20 seconds.
|
|
27
|
+
The transfer changes the staged boot application; use `rpremote reset` to restart and require DFU.confirm after a successful boot.
|
|
28
|
+
HELP
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.dfu_compile_text
|
|
32
|
+
<<~HELP
|
|
33
|
+
Usage: #{COMMAND_USAGE.fetch(:dfu_compile)}
|
|
34
|
+
|
|
35
|
+
Compiles Ruby source to bytecode that matches the selected PicoRuby version. The output defaults beside FILE.
|
|
36
|
+
It requires prepared PicoRuby source and does not connect to a board.
|
|
37
|
+
HELP
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.dfu_status_text
|
|
41
|
+
<<~HELP
|
|
42
|
+
Usage: #{COMMAND_USAGE.fetch(:dfu_status)}
|
|
43
|
+
|
|
44
|
+
Prints the active and candidate DFU A/B slots. Defaults are the configured or automatically selected CDC 0 port,
|
|
45
|
+
115200 baud, and 20 seconds. This command reads board state without changing it.
|
|
46
|
+
HELP
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.dfu_remove_text
|
|
50
|
+
<<~HELP
|
|
51
|
+
Usage: #{COMMAND_USAGE.fetch(:dfu_remove)}
|
|
52
|
+
|
|
53
|
+
Permanently removes the Ruby source and bytecode applications from both DFU A/B slots and resets their metadata.
|
|
54
|
+
Run `rpremote reset` afterward to stop the application already running in RAM. It does not remove /home/app.rb,
|
|
55
|
+
/home/app.mrb, other files, embedded mrbgems, or R2P2 firmware.
|
|
56
|
+
HELP
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rpremote
|
|
4
|
+
module Help
|
|
5
|
+
def self.fs_text(subcommand)
|
|
6
|
+
return fs_subcommand_text(subcommand) if %w[cp push cat ls rm mkdir].include?(subcommand)
|
|
7
|
+
|
|
8
|
+
<<~HELP
|
|
9
|
+
Usage: #{COMMAND_USAGE.fetch(:fs_cp)}
|
|
10
|
+
#{COMMAND_USAGE.fetch(:fs_push)}
|
|
11
|
+
#{COMMAND_USAGE.fetch(:fs_cat)}
|
|
12
|
+
#{COMMAND_USAGE.fetch(:fs_ls)}
|
|
13
|
+
#{COMMAND_USAGE.fetch(:fs_rm)}
|
|
14
|
+
#{COMMAND_USAGE.fetch(:fs_mkdir)}
|
|
15
|
+
|
|
16
|
+
Uses :/REMOTE/PATH for R2P2 paths. Run `rpremote fs SUBCOMMAND --help` for details.
|
|
17
|
+
HELP
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.fs_subcommand_text(subcommand)
|
|
21
|
+
usage = COMMAND_USAGE.fetch(:"fs_#{subcommand}")
|
|
22
|
+
effect = case subcommand
|
|
23
|
+
when "rm" then "Deletes the remote path permanently."
|
|
24
|
+
when "mkdir" then "Creates a remote directory."
|
|
25
|
+
when "cp" then "Transfers one file, or recursively uploads a local directory with --recursive."
|
|
26
|
+
when "push" then "Creates missing remote directories and recursively uploads a local directory."
|
|
27
|
+
else "Reads remote files."
|
|
28
|
+
end
|
|
29
|
+
<<~HELP
|
|
30
|
+
Usage: #{usage}
|
|
31
|
+
|
|
32
|
+
#{effect} Defaults are the automatic CDC 0 port, 115200 baud, and 20 seconds.
|
|
33
|
+
HELP
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rpremote
|
|
4
|
+
module Help
|
|
5
|
+
def self.setup_text
|
|
6
|
+
<<~HELP
|
|
7
|
+
Usage: #{COMMAND_USAGE.fetch(:setup)}
|
|
8
|
+
|
|
9
|
+
Creates config/setting.json when it does not exist, then downloads and prepares PicoRuby source and the official Raspberry Pi nuke_universal.uf2 firmware.
|
|
10
|
+
Options: --language LANGUAGE (picoruby), --language-version VERSION (latest), --cache DIR (firmware), --force.
|
|
11
|
+
This changes the project configuration and source cache but does not connect to a board.
|
|
12
|
+
HELP
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.build_text
|
|
16
|
+
<<~HELP
|
|
17
|
+
Usage: #{COMMAND_USAGE.fetch(:build)}
|
|
18
|
+
|
|
19
|
+
Builds a custom UF2 for the selected target. It writes generated files under build/ and the selected firmware path.
|
|
20
|
+
Options: --language LANGUAGE, --language-version VERSION, --board BOARD (pico2), --cache DIR (firmware),
|
|
21
|
+
--firmware FILE, --lockfile FILE, --no-mrbgems. A project Mrbgems.lock file is used automatically by default.
|
|
22
|
+
HELP
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.build_clean_text
|
|
26
|
+
<<~HELP
|
|
27
|
+
Usage: #{COMMAND_USAGE.fetch(:build_clean)}
|
|
28
|
+
|
|
29
|
+
Removes only the project's generated build/ directory. It does not remove firmware/, Mrbgems, or Mrbgems.lock.
|
|
30
|
+
HELP
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.deploy_text
|
|
34
|
+
<<~HELP
|
|
35
|
+
Usage: #{COMMAND_USAGE.fetch(:deploy)}
|
|
36
|
+
|
|
37
|
+
Enters BOOTSEL when needed, flashes the existing selected UF2, and waits for the R2P2 Shell to become ready.
|
|
38
|
+
With --build, builds the selected custom UF2 before entering BOOTSEL and flashing it.
|
|
39
|
+
If PATH/lib/NAME exists, it copies it to :/lib/NAME, where NAME is the final component of PATH. It then runs PATH/main.rb and preserves its Shell job, so hardware output remains active until the next command.
|
|
40
|
+
The stages run in order and stop at the first failure. Flashing replaces persistent board firmware.
|
|
41
|
+
deploy requires current PicoRuby 4.x firmware.
|
|
42
|
+
Options combine build, BOOTSEL, flash, library-copy, and run settings. The first install of firmware with automatic BOOTSEL still requires holding BOOTSEL.
|
|
43
|
+
HELP
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.bootsel_text
|
|
47
|
+
<<~HELP
|
|
48
|
+
Usage: #{COMMAND_USAGE.fetch(:bootsel)}
|
|
49
|
+
|
|
50
|
+
Asks the running R2P2 firmware to restart as an RP2350 BOOTSEL USB volume, then waits for that volume to appear.
|
|
51
|
+
By default it does not write firmware. This requires firmware built by a current rpremote; use physical BOOTSEL once to install it first.
|
|
52
|
+
--reset-flash-memory copies the official Raspberry Pi nuke_universal.uf2 in BOOTSEL mode and erases all external flash memory.
|
|
53
|
+
After resetting flash memory, wait for BOOTSEL to reappear and run `rpremote flash` to install R2P2 again.
|
|
54
|
+
HELP
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.mrbgems_text(subcommand)
|
|
58
|
+
action = subcommand || "check|list|lock|update"
|
|
59
|
+
<<~HELP
|
|
60
|
+
Usage: #{COMMAND_USAGE.fetch(:mrbgems).sub("SUBCOMMAND", action)}
|
|
61
|
+
|
|
62
|
+
Checks or lists build dependencies, writes a reproducible lock file, or updates locked GitHub commits.
|
|
63
|
+
`lock --with production --without test` includes common and production gems. Exclusions take precedence.
|
|
64
|
+
`update` resolves new commits using the same group options.
|
|
65
|
+
HELP
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.flash_text
|
|
69
|
+
<<~HELP
|
|
70
|
+
Usage: #{COMMAND_USAGE.fetch(:flash)}
|
|
71
|
+
|
|
72
|
+
Copies the selected UF2 to an RP2350 BOOTSEL volume and waits for R2P2 to reconnect. It replaces persistent board firmware.
|
|
73
|
+
Defaults select pico2, firmware/picoruby-latest-pico2.uf2, an automatic BOOTSEL mount and CDC 0 port, and 20 seconds.
|
|
74
|
+
HELP
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.config_text(subcommand)
|
|
78
|
+
unless subcommand == "show"
|
|
79
|
+
return "Usage: #{COMMAND_USAGE.fetch(:config_show)}\n\nUse `rpremote config show --help` for the supported overrides.\n"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
<<~HELP
|
|
83
|
+
Usage: #{COMMAND_USAGE.fetch(:config_show)}
|
|
84
|
+
|
|
85
|
+
Prints the configuration file, defaults, and command-line overrides after resolution. It does not connect to a board or change state.
|
|
86
|
+
HELP
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|