rpremote 0.2.0 → 0.4.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.
@@ -14,7 +14,7 @@ module Rpremote
14
14
  COMMIT_PATTERN = /\A[0-9a-f]{40,64}\z/i
15
15
  VMS = %i[mruby mrubyc].freeze
16
16
 
17
- Dependency = Data.define(:type, :source, :branch, :commit, :path)
17
+ Dependency = Data.define(:type, :source, :branch, :commit, :path, :require_name)
18
18
  Overlay = Data.define(:path, :fingerprint)
19
19
 
20
20
  class Error < Rpremote::Error; end
@@ -68,6 +68,20 @@ module Rpremote
68
68
  raise LockError, "invalid mrbgems lock file #{lock_path}: #{e.message}"
69
69
  end
70
70
 
71
+ def require_names
72
+ lock_data = read_lock(required: false)
73
+ return [] unless lock_data
74
+
75
+ lock_data.fetch("gems").filter_map { |gem| gem["require_name"] }.uniq
76
+ end
77
+
78
+ def prepend_requires(source)
79
+ names = require_names
80
+ return source if names.empty?
81
+
82
+ names.map { |name| "require #{name.inspect}\n" }.join.b + source.b
83
+ end
84
+
71
85
  def generate_overlay(base_config:, target:, directory:, update: false)
72
86
  lock_data = lock(update: update)
73
87
  fingerprint = build_fingerprint(base_config, lock_data)
@@ -109,10 +123,12 @@ module Rpremote
109
123
  dependency.source, dependency.branch
110
124
  )
111
125
  { "type" => "github", "source" => dependency.source,
112
- "branch" => dependency.branch, "commit" => validate_commit!(commit) }
126
+ "branch" => dependency.branch, "commit" => validate_commit!(commit),
127
+ "require_name" => dependency.require_name }.compact
113
128
  else
114
129
  { "type" => "path", "source" => dependency.source,
115
- "sha256" => digest_directory(dependency.path) }
130
+ "sha256" => digest_directory(dependency.path),
131
+ "require_name" => dependency.require_name || local_require_name(dependency.path) }.compact
116
132
  end
117
133
  end
118
134
 
@@ -184,12 +200,22 @@ module Rpremote
184
200
 
185
201
  def valid_github_lock?(entry)
186
202
  GITHUB_PATTERN.match?(entry["source"].to_s) && !entry["branch"].to_s.empty? &&
187
- COMMIT_PATTERN.match?(entry["commit"].to_s)
203
+ COMMIT_PATTERN.match?(entry["commit"].to_s) && valid_require_name?(entry["require_name"])
188
204
  end
189
205
 
190
206
  def valid_path_lock?(entry)
191
207
  entry["type"] == "path" && !entry["source"].to_s.empty? &&
192
- /\A[0-9a-f]{64}\z/.match?(entry["sha256"].to_s)
208
+ /\A[0-9a-f]{64}\z/.match?(entry["sha256"].to_s) && valid_require_name?(entry["require_name"])
209
+ end
210
+
211
+ def valid_require_name?(name)
212
+ name.nil? || (name.is_a?(String) && !name.empty? && !name.match?(/[\x00-\x1f\x7f]/))
213
+ end
214
+
215
+ def local_require_name(directory)
216
+ source = File.read(File.join(directory, "mrbgem.rake"))
217
+ match = source.match(/^\s*spec\.require_name\s*=\s*["']([^"']+)["']\s*$/)
218
+ match && match[1]
193
219
  end
194
220
 
195
221
  def build_fingerprint(base_config, lock_data)
@@ -246,14 +272,17 @@ module Rpremote
246
272
  @vm_name = value
247
273
  end
248
274
 
249
- def gem(github: nil, path: nil, branch: "main", commit: nil)
275
+ def gem(github: nil, path: nil, branch: "main", commit: nil, require: nil)
250
276
  sources = [github, path].compact
251
277
  raise DefinitionError, "gem requires exactly one of github or path" unless sources.length == 1
278
+ unless require.nil? || (require.is_a?(String) && !require.empty? && !require.match?(/[\x00-\x1f\x7f]/))
279
+ raise DefinitionError, "invalid mrbgem require name: #{require.inspect}"
280
+ end
252
281
 
253
282
  dependency = if github
254
- github_dependency(github, branch, commit)
283
+ github_dependency(github, branch, commit, require)
255
284
  else
256
- path_dependency(path, branch, commit)
285
+ path_dependency(path, branch, commit, require)
257
286
  end
258
287
  key = [dependency.type, dependency.source]
259
288
  raise DefinitionError, "duplicate mrbgem: #{dependency.source}" if dependencies.any? do |item|
@@ -267,21 +296,21 @@ module Rpremote
267
296
 
268
297
  attr_reader :directory
269
298
 
270
- def github_dependency(source, branch, commit)
299
+ def github_dependency(source, branch, commit, require_name)
271
300
  raise DefinitionError, "invalid GitHub mrbgem: #{source.inspect}" unless GITHUB_PATTERN.match?(source.to_s)
272
301
  raise DefinitionError, "GitHub mrbgem branch must not be empty" if branch.to_s.empty?
273
302
  raise DefinitionError, "invalid Git commit: #{commit.inspect}" if commit && !COMMIT_PATTERN.match?(commit.to_s)
274
303
 
275
304
  Dependency.new(type: :github, source: source, branch: branch,
276
- commit: commit&.downcase, path: nil)
305
+ commit: commit&.downcase, path: nil, require_name: require_name)
277
306
  end
278
307
 
279
- def path_dependency(source, branch, commit)
308
+ def path_dependency(source, branch, commit, require_name)
280
309
  raise DefinitionError, "local mrbgem path must not be empty" if source.to_s.empty?
281
310
  raise DefinitionError, "local mrbgem does not accept branch or commit" if branch != "main" || commit
282
311
 
283
312
  Dependency.new(type: :path, source: source, branch: nil, commit: nil,
284
- path: File.expand_path(source, directory))
313
+ path: File.expand_path(source, directory), require_name: require_name)
285
314
  end
286
315
  end
287
316
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "net/http"
5
+ require "timeout"
6
+ require "uri"
7
+
8
+ module Rpremote
9
+ class NukeFirmware
10
+ URL = "https://github.com/raspberrypi/pico-sdk-prebuilts/releases/latest/download/nuke_universal.uf2"
11
+ FILENAME = "nuke_universal.uf2"
12
+ MAX_REDIRECTS = 5
13
+
14
+ class DownloadError < Rpremote::Error; end
15
+
16
+ def initialize(directory: Target::DEFAULT_CACHE_DIR, fetcher: nil)
17
+ @directory = File.expand_path(directory)
18
+ @fetcher = fetcher || method(:download)
19
+ end
20
+
21
+ def setup(force: false)
22
+ FileUtils.mkdir_p(directory)
23
+ return path if File.size?(path) && !force
24
+
25
+ temporary = "#{path}.part"
26
+ File.binwrite(temporary, fetcher.call(URL))
27
+ raise DownloadError, "downloaded nuke_universal.uf2 is empty" unless File.size?(temporary)
28
+
29
+ File.rename(temporary, path)
30
+ path
31
+ rescue SystemCallError => e
32
+ raise DownloadError, "cannot store nuke_universal.uf2: #{e.message}"
33
+ ensure
34
+ FileUtils.rm_f(temporary) if temporary
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :directory, :fetcher
40
+
41
+ def path
42
+ File.join(directory, FILENAME)
43
+ end
44
+
45
+ def download(url, redirects = MAX_REDIRECTS)
46
+ raise DownloadError, "too many redirects while downloading nuke_universal.uf2" if redirects.negative?
47
+
48
+ uri = URI(url)
49
+ raise DownloadError, "nuke_universal.uf2 download requires HTTPS" unless uri.is_a?(URI::HTTPS)
50
+
51
+ request = Net::HTTP::Get.new(uri)
52
+ request["User-Agent"] = "rpremote/#{Rpremote::VERSION}"
53
+ response = Net::HTTP.start(
54
+ uri.host, uri.port, use_ssl: true, open_timeout: 10, read_timeout: 60
55
+ ) { |http| http.request(request) }
56
+
57
+ case response
58
+ when Net::HTTPSuccess then response.body
59
+ when Net::HTTPRedirection
60
+ location = response["location"]
61
+ raise DownloadError, "nuke_universal.uf2 download redirect has no location" unless location
62
+
63
+ download(URI.join(url, location).to_s, redirects - 1)
64
+ else
65
+ raise DownloadError, "nuke_universal.uf2 download failed: HTTP #{response.code} #{response.message}"
66
+ end
67
+ rescue SocketError, SystemCallError, Timeout::Error => e
68
+ raise DownloadError, "nuke_universal.uf2 download failed: #{e.message}"
69
+ end
70
+ end
71
+ end
@@ -3,6 +3,7 @@
3
3
  module Rpremote
4
4
  class PicoRubySourcePatch
5
5
  JOB_PATH = "mrbgems/picoruby-shell/mrblib/job.rb"
6
+ BOOTSEL_PATH = "mrbgems/picoruby-machine/include/machine.h"
6
7
  PATCH_ALIASES = { "3.4.2" => "3.4.5" }.freeze
7
8
 
8
9
  class Error < Rpremote::Error; end
@@ -14,19 +15,35 @@ module Rpremote
14
15
  end
15
16
 
16
17
  def apply(source)
18
+ return unless Dir.exist?(source)
19
+
17
20
  patch_version = PATCH_ALIASES.fetch(version, version)
18
- patch = File.expand_path("../../patches/picoruby-#{patch_version}-ruby-exception-status.patch", __dir__)
19
- return unless File.file?(patch) && File.file?(File.join(source, JOB_PATH))
20
- return if git_apply?(source, patch, "--reverse", "--check")
21
+ patches(patch_version).each do |patch, prerequisite|
22
+ next unless File.file?(patch) && File.file?(File.join(source, prerequisite))
23
+ next if prerequisite == BOOTSEL_PATH && File.read(File.join(source, prerequisite)).include?("Machine_bootsel")
24
+
25
+ next if git_apply?(source, patch, "--reverse", "--check")
21
26
 
22
- raise Error, "cannot apply rpremote patch to PicoRuby #{version}" unless git_apply?(source, patch, "--check")
23
- raise Error, "cannot apply rpremote patch to PicoRuby #{version}" unless git_apply?(source, patch)
27
+ raise Error, "cannot apply rpremote patch to PicoRuby #{version}" unless git_apply?(source, patch, "--check")
28
+ raise Error, "cannot apply rpremote patch to PicoRuby #{version}" unless git_apply?(source, patch)
29
+ end
30
+ nil
24
31
  end
25
32
 
26
33
  private
27
34
 
35
+ def patches(patch_version)
36
+ definitions = {
37
+ "ruby-exception-status" => JOB_PATH,
38
+ "bootsel" => BOOTSEL_PATH
39
+ }
40
+ definitions.map do |name, prerequisite|
41
+ [File.expand_path("../../patches/picoruby-#{patch_version}-#{name}.patch", __dir__), prerequisite]
42
+ end
43
+ end
44
+
28
45
  def git_apply?(source, patch, *options)
29
- system("git", "apply", *options, patch, chdir: source, out: File::NULL, err: File::NULL)
46
+ system("git", "apply", "--unidiff-zero", *options, patch, chdir: source, out: File::NULL, err: File::NULL)
30
47
  end
31
48
  end
32
49
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rpremote
4
+ class RecursiveCopy
5
+ def initialize(output:, serial: Serial, device: Device)
6
+ @output = output
7
+ @serial = serial
8
+ @device = device
9
+ end
10
+
11
+ def call(source, destination, options)
12
+ validate!(source, destination)
13
+ remote_root = RemotePath.validate(destination)
14
+ directories, files = collect(source, remote_root)
15
+ create_directories(directories, options)
16
+ upload_files(files, options)
17
+ output.puts("uploaded #{files.length} files: #{source} -> #{destination}")
18
+ rescue Errno::ENOENT => e
19
+ raise ArgumentError, e.message
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :output, :serial, :device
25
+
26
+ def validate!(source, destination)
27
+ raise ArgumentError, "recursive cp only supports a local source and remote destination" if RemotePath.remote?(source)
28
+ raise ArgumentError, "recursive cp source must be a directory: #{source}" unless File.directory?(source)
29
+
30
+ RemotePath.validate(destination)
31
+ end
32
+
33
+ def collect(source, remote_root)
34
+ directories = [remote_root]
35
+ files = []
36
+ local_entries(source).each do |path|
37
+ remote_path = File.join(remote_root, path.delete_prefix("#{source}/"))
38
+ File.directory?(path) ? directories << remote_path : files << [path, remote_path]
39
+ end
40
+ [directories.sort_by { |path| [path.count("/"), path] }.uniq, files.sort]
41
+ end
42
+
43
+ def local_entries(source)
44
+ entries = Dir.glob(File.join(source, "**", "*"), File::FNM_DOTMATCH).reject do |path|
45
+ %w[. ..].include?(File.basename(path))
46
+ end
47
+ symbolic_link = entries.find { |path| File.symlink?(path) }
48
+ raise ArgumentError, "recursive cp does not support symbolic links: #{symbolic_link}" if symbolic_link
49
+
50
+ entries
51
+ end
52
+
53
+ def create_directories(directories, options)
54
+ with_port(options) do |port|
55
+ shell = Shell.new(port, timeout: options[:timeout])
56
+ shell.synchronize!
57
+ directories.each do |path|
58
+ quoted_path = Shell.quote_argument(path)
59
+ next unless shell.execute("ls #{quoted_path}").start_with?("ls:")
60
+
61
+ result = shell.execute("mkdir #{quoted_path}")
62
+ raise Shell::CommandError, result.strip unless result.empty?
63
+
64
+ output.puts("created remote directory: :#{path}")
65
+ end
66
+ end
67
+ end
68
+
69
+ def upload_files(files, options)
70
+ with_port(options) do |port|
71
+ modem = PicoModem.new(port, timeout: options[:timeout])
72
+ files.each do |local_path, remote_path|
73
+ size = modem.upload(remote_path, File.binread(local_path))
74
+ output.puts("uploaded #{size} bytes: #{local_path} -> :#{remote_path}")
75
+ end
76
+ end
77
+ end
78
+
79
+ def with_port(options, &)
80
+ port_path = device.main_port(options[:port])
81
+ serial.open(port_path, baud: options[:baud], &)
82
+ end
83
+ end
84
+ end
@@ -3,6 +3,8 @@
3
3
  module Rpremote
4
4
  class Runner
5
5
  REMOTE_DIRECTORY = "/home"
6
+ RUN_REMOTE_PATH = "#{REMOTE_DIRECTORY}/.rpremote-run.rb".freeze
7
+ DEPLOY_REMOTE_PATH = "#{REMOTE_DIRECTORY}/.rpremote-deploy.rb".freeze
6
8
 
7
9
  attr_reader :io, :timeout
8
10
 
@@ -20,16 +22,33 @@ module Rpremote
20
22
  @path_factory = path_factory || method(:temporary_path)
21
23
  end
22
24
 
23
- def run(data, output: nil)
24
- remote_path = @path_factory.call
25
+ def run(data, output: nil, cleanup: true, remote_path: nil, diagnostics: nil)
26
+ remote_path ||= @path_factory.call
25
27
  shell = @shell_class.new(io, timeout: timeout)
28
+ trace(diagnostics, "SYNC_BEFORE_UPLOAD_START")
26
29
  shell.synchronize!
30
+ trace(diagnostics, "SYNC_BEFORE_UPLOAD_DONE")
31
+ trace(diagnostics, "UPLOAD_START", "bytes=#{data.bytesize},path=#{remote_path}")
27
32
  @modem_class.new(io, timeout: timeout).upload(remote_path, data)
33
+ trace(diagnostics, "UPLOAD_DONE")
34
+ trace(diagnostics, "SYNC_BEFORE_RUN_START")
28
35
  shell.synchronize!
36
+ trace(diagnostics, "SYNC_BEFORE_RUN_DONE")
29
37
  shell_ready = true
30
- output ? shell.execute("./#{File.basename(remote_path)}", output: output) : shell.execute("./#{File.basename(remote_path)}")
38
+ command = "./#{File.basename(remote_path)}"
39
+ trace(diagnostics, "EXECUTE_START", "command=#{command}")
40
+ result = output ? shell.execute(command, output: output, idle_timeout: true) : shell.execute(command, idle_timeout: true)
41
+ trace(diagnostics, "EXECUTE_DONE", "bytes=#{result.to_s.bytesize}")
42
+ result
43
+ rescue StandardError => e
44
+ trace(diagnostics, "ERROR", "class=#{e.class},message=#{e.message.to_s.gsub("\n", " ")}")
45
+ raise
31
46
  ensure
32
- cleanup(shell, remote_path) if shell_ready && remote_path
47
+ if cleanup && shell_ready && remote_path
48
+ trace(diagnostics, "CLEANUP_START", "path=#{remote_path}")
49
+ cleanup(shell, remote_path)
50
+ trace(diagnostics, "CLEANUP_DONE")
51
+ end
33
52
  end
34
53
 
35
54
  private
@@ -40,9 +59,17 @@ module Rpremote
40
59
  nil
41
60
  end
42
61
 
62
+ def trace(diagnostics, event, details = nil)
63
+ return unless diagnostics
64
+
65
+ line = "rpremote: run event=#{event}"
66
+ line += ",#{details}" if details
67
+ diagnostics.puts(line)
68
+ diagnostics.flush if diagnostics.respond_to?(:flush)
69
+ end
70
+
43
71
  def temporary_path
44
- stamp = (Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1_000_000).to_i
45
- "#{REMOTE_DIRECTORY}/.rpremote-run-#{Process.pid}-#{stamp}.rb"
72
+ RUN_REMOTE_PATH
46
73
  end
47
74
  end
48
75
  end
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "optparse"
4
+ require_relative "nuke_firmware"
4
5
 
5
6
  module Rpremote
6
7
  class SetupCommand
7
- def self.run(args, defaults:, config:, config_filename:, output: $stdout)
8
+ def self.run(args, defaults:, config:, config_filename:, output: $stdout, nuke_firmware: NukeFirmware)
8
9
  options = {
9
10
  force: false,
10
11
  cache_dir: defaults.fetch(:cache, Target::DEFAULT_CACHE_DIR),
@@ -29,6 +30,8 @@ module Rpremote
29
30
  cache_dir: target.cache_dir
30
31
  ).setup(force: options[:force])
31
32
  output.puts("installed #{target.language} #{target.language_version}: #{path}")
33
+ nuke_path = nuke_firmware.new.setup(force: options[:force])
34
+ output.puts("installed Raspberry Pi nuke firmware: #{nuke_path}")
32
35
  end
33
36
  end
34
37
  end
@@ -29,11 +29,12 @@ module Rpremote
29
29
 
30
30
  attr_reader :io, :timeout
31
31
 
32
- def initialize(io, timeout: DEFAULT_TIMEOUT)
32
+ def initialize(io, timeout: DEFAULT_TIMEOUT, clock: nil)
33
33
  raise ArgumentError, "timeout must be positive" unless timeout.positive?
34
34
 
35
35
  @io = io
36
36
  @timeout = timeout
37
+ @clock = clock || -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
37
38
  end
38
39
 
39
40
  def synchronize!
@@ -43,12 +44,14 @@ module Rpremote
43
44
  nil
44
45
  end
45
46
 
46
- def execute(command, output: nil)
47
+ def execute(command, output: nil, idle_timeout: false)
47
48
  command = validate_command(command)
48
49
  send_command(command)
49
50
  echo = ECHO_START + command.b + ERASE_LINE
50
51
  stream_state = {}
51
- response = read_until_prompt(after: echo) { |buffer| stream_output(output, buffer, echo, stream_state) }
52
+ response = read_until_prompt(after: echo, idle_timeout: idle_timeout) do |buffer|
53
+ stream_output(output, buffer, echo, stream_state)
54
+ end
52
55
  command_output = extract_output(response, command)
53
56
  ruby_exception = command_output.include?(RUBY_EXCEPTION_STATUS)
54
57
  command_output = remove_ruby_exception_status(command_output)
@@ -76,7 +79,7 @@ module Rpremote
76
79
  command
77
80
  end
78
81
 
79
- def read_until_prompt(wait: timeout, after: nil)
82
+ def read_until_prompt(wait: timeout, after: nil, idle_timeout: false)
80
83
  deadline = monotonic_time + wait
81
84
  buffer = +"".b
82
85
  loop do
@@ -85,6 +88,7 @@ module Rpremote
85
88
  return buffer if prompt_at && buffer.index(PROMPT_END, prompt_at + PROMPT_START.bytesize)
86
89
 
87
90
  buffer << read_available(deadline)
91
+ deadline = monotonic_time + wait if idle_timeout
88
92
  answer_terminal_queries(buffer)
89
93
  yield buffer if block_given?
90
94
  end
@@ -186,7 +190,7 @@ module Rpremote
186
190
  end
187
191
 
188
192
  def monotonic_time
189
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
193
+ @clock.call
190
194
  end
191
195
  end
192
196
  end
@@ -49,7 +49,7 @@ module Rpremote
49
49
  data = serial_io.read_nonblock(BUFFER_SIZE)
50
50
  return false if data.nil? || data.empty?
51
51
 
52
- output.write(data)
52
+ output.write(normalize_line_endings(data))
53
53
  output.flush if output.respond_to?(:flush)
54
54
  true
55
55
  else
@@ -75,6 +75,18 @@ module Rpremote
75
75
  true
76
76
  end
77
77
 
78
+ # input.raw disables the terminal's usual LF-to-CRLF output conversion.
79
+ # Preserve device CRLF while making a bare LF start at the left margin.
80
+ def normalize_line_endings(data)
81
+ normalized = +"".b
82
+ data.each_byte do |byte|
83
+ normalized << "\r" if byte == 0x0A && !@previous_output_was_cr
84
+ normalized << byte
85
+ @previous_output_was_cr = byte == 0x0D
86
+ end
87
+ normalized
88
+ end
89
+
78
90
  def selectable_serial
79
91
  @selectable_serial ||= serial_io.respond_to?(:to_io) ? serial_io.to_io : serial_io
80
92
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rpremote
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/rpremote.rb CHANGED
@@ -19,8 +19,10 @@ require_relative "rpremote/terminal"
19
19
  require_relative "rpremote/shell"
20
20
  require_relative "rpremote/runner"
21
21
  require_relative "rpremote/resetter"
22
+ require_relative "rpremote/recursive_copy"
22
23
  require_relative "rpremote/picoruby_source_patch"
23
24
  require_relative "rpremote/language_source"
25
+ require_relative "rpremote/nuke_firmware"
24
26
  require_relative "rpremote/mrbgems"
25
27
  require_relative "rpremote/mrbgems_command"
26
28
  require_relative "rpremote/flasher"
@@ -29,3 +31,5 @@ require_relative "rpremote/dfu_command"
29
31
  require_relative "rpremote/setup_command"
30
32
  require_relative "rpremote/build_command"
31
33
  require_relative "rpremote/flash_command"
34
+ require_relative "rpremote/bootsel_command"
35
+ require_relative "rpremote/deploy_command"
@@ -0,0 +1,71 @@
1
+ diff --git a/mrbgems/picoruby-machine/include/machine.h b/mrbgems/picoruby-machine/include/machine.h
2
+ --- a/mrbgems/picoruby-machine/include/machine.h
3
+ +++ b/mrbgems/picoruby-machine/include/machine.h
4
+ @@ -48,1 +48,2 @@
5
+ void Machine_reboot(void);
6
+ +void Machine_bootsel(void);
7
+ diff --git a/mrbgems/picoruby-machine/mrblib/machine.rb b/mrbgems/picoruby-machine/mrblib/machine.rb
8
+ --- a/mrbgems/picoruby-machine/mrblib/machine.rb
9
+ +++ b/mrbgems/picoruby-machine/mrblib/machine.rb
10
+ @@ -22,0 +23,4 @@
11
+ + def self.bootsel
12
+ + self._bootsel
13
+ + end
14
+ +
15
+ diff --git a/mrbgems/picoruby-machine/ports/rp2040/machine.c b/mrbgems/picoruby-machine/ports/rp2040/machine.c
16
+ --- a/mrbgems/picoruby-machine/ports/rp2040/machine.c
17
+ +++ b/mrbgems/picoruby-machine/ports/rp2040/machine.c
18
+ @@ -12,0 +13,1 @@
19
+ +#include "pico/bootrom.h"
20
+ @@ -32,0 +34,6 @@
21
+ +void
22
+ +Machine_bootsel(void)
23
+ +{
24
+ + rom_reset_usb_boot(0, 0);
25
+ +}
26
+ +
27
+ diff --git a/mrbgems/picoruby-machine/src/mruby/machine.c b/mrbgems/picoruby-machine/src/mruby/machine.c
28
+ --- a/mrbgems/picoruby-machine/src/mruby/machine.c
29
+ +++ b/mrbgems/picoruby-machine/src/mruby/machine.c
30
+ @@ -417,0 +418,11 @@
31
+ +static mrb_value
32
+ +mrb_s__bootsel(mrb_state *mrb, mrb_value self)
33
+ +{
34
+ +#if !defined(PICORB_PLATFORM_POSIX)
35
+ + Machine_bootsel();
36
+ +#else
37
+ + mrb_raise(mrb, E_NOTIMP_ERROR, "Machine._bootsel is not available on this platform");
38
+ +#endif
39
+ + return mrb_nil_value();
40
+ +}
41
+ +
42
+ @@ -443,0 +455,1 @@
43
+ + mrb_define_class_method_id(mrb, module_Machine, MRB_SYM(_bootsel), mrb_s__bootsel, MRB_ARGS_NONE());
44
+ diff --git a/mrbgems/picoruby-machine/src/mrubyc/machine.c b/mrbgems/picoruby-machine/src/mrubyc/machine.c
45
+ --- a/mrbgems/picoruby-machine/src/mrubyc/machine.c
46
+ +++ b/mrbgems/picoruby-machine/src/mrubyc/machine.c
47
+ @@ -252,0 +253,11 @@
48
+ +static void
49
+ +c_Machine__bootsel(mrbc_vm *vm, mrbc_value *v, int argc)
50
+ +{
51
+ +#if !defined(PICORB_PLATFORM_POSIX)
52
+ + Machine_bootsel();
53
+ +#else
54
+ + mrbc_raise(vm, MRBC_CLASS(RuntimeError), "Machine.bootsel is not available on this platform.");
55
+ +#endif
56
+ + SET_NIL_RETURN();
57
+ +}
58
+ +
59
+ @@ -439,0 +451,1 @@
60
+ + mrbc_define_method(vm, module_Machine, "_bootsel", c_Machine__bootsel);
61
+ diff --git a/mrbgems/picoruby-shell/shell_executables/_path.txt b/mrbgems/picoruby-shell/shell_executables/_path.txt
62
+ --- a/mrbgems/picoruby-shell/shell_executables/_path.txt
63
+ +++ b/mrbgems/picoruby-shell/shell_executables/_path.txt
64
+ @@ -16,0 +17,1 @@
65
+ +/bin/bootsel
66
+ diff --git a/mrbgems/picoruby-shell/shell_executables/bootsel.rb b/mrbgems/picoruby-shell/shell_executables/bootsel.rb
67
+ new file mode 100644
68
+ --- /dev/null
69
+ +++ b/mrbgems/picoruby-shell/shell_executables/bootsel.rb
70
+ @@ -0,0 +1 @@
71
+ +Machine.bootsel
@@ -0,0 +1,71 @@
1
+ diff --git a/mrbgems/picoruby-machine/include/machine.h b/mrbgems/picoruby-machine/include/machine.h
2
+ --- a/mrbgems/picoruby-machine/include/machine.h
3
+ +++ b/mrbgems/picoruby-machine/include/machine.h
4
+ @@ -68,1 +68,2 @@
5
+ void Machine_reboot(void);
6
+ +void Machine_bootsel(void);
7
+ diff --git a/mrbgems/picoruby-machine/mrblib/machine.rb b/mrbgems/picoruby-machine/mrblib/machine.rb
8
+ --- a/mrbgems/picoruby-machine/mrblib/machine.rb
9
+ +++ b/mrbgems/picoruby-machine/mrblib/machine.rb
10
+ @@ -22,0 +23,4 @@
11
+ + def self.bootsel
12
+ + self._bootsel
13
+ + end
14
+ +
15
+ diff --git a/mrbgems/picoruby-machine/ports/rp2040/machine.c b/mrbgems/picoruby-machine/ports/rp2040/machine.c
16
+ --- a/mrbgems/picoruby-machine/ports/rp2040/machine.c
17
+ +++ b/mrbgems/picoruby-machine/ports/rp2040/machine.c
18
+ @@ -10,0 +11,1 @@
19
+ +#include "pico/bootrom.h"
20
+ @@ -42,0 +44,6 @@
21
+ +void
22
+ +Machine_bootsel(void)
23
+ +{
24
+ + rom_reset_usb_boot(0, 0);
25
+ +}
26
+ +
27
+ diff --git a/mrbgems/picoruby-machine/src/mruby/machine.c b/mrbgems/picoruby-machine/src/mruby/machine.c
28
+ --- a/mrbgems/picoruby-machine/src/mruby/machine.c
29
+ +++ b/mrbgems/picoruby-machine/src/mruby/machine.c
30
+ @@ -496,0 +497,11 @@
31
+ +static mrb_value
32
+ +mrb_s__bootsel(mrb_state *mrb, mrb_value self)
33
+ +{
34
+ +#if !defined(PICORB_PLATFORM_POSIX)
35
+ + Machine_bootsel();
36
+ +#else
37
+ + mrb_raise(mrb, E_NOTIMP_ERROR, "Machine._bootsel is not available on this platform");
38
+ +#endif
39
+ + return mrb_nil_value();
40
+ +}
41
+ +
42
+ @@ -529,0 +541,1 @@
43
+ + mrb_define_class_method_id(mrb, module_Machine, MRB_SYM(_bootsel), mrb_s__bootsel, MRB_ARGS_NONE());
44
+ diff --git a/mrbgems/picoruby-machine/src/mrubyc/machine.c b/mrbgems/picoruby-machine/src/mrubyc/machine.c
45
+ --- a/mrbgems/picoruby-machine/src/mrubyc/machine.c
46
+ +++ b/mrbgems/picoruby-machine/src/mrubyc/machine.c
47
+ @@ -316,0 +317,11 @@
48
+ +static void
49
+ +c_Machine__bootsel(mrbc_vm *vm, mrbc_value *v, int argc)
50
+ +{
51
+ +#if !defined(PICORB_PLATFORM_POSIX)
52
+ + Machine_bootsel();
53
+ +#else
54
+ + mrbc_raise(vm, MRBC_CLASS(RuntimeError), "Machine.bootsel is not available on this platform.");
55
+ +#endif
56
+ + SET_NIL_RETURN();
57
+ +}
58
+ +
59
+ @@ -546,0 +558,1 @@
60
+ + mrbc_define_method(vm, module_Machine, "_bootsel", c_Machine__bootsel);
61
+ diff --git a/mrbgems/picoruby-shell/shell_executables/_path.txt b/mrbgems/picoruby-shell/shell_executables/_path.txt
62
+ --- a/mrbgems/picoruby-shell/shell_executables/_path.txt
63
+ +++ b/mrbgems/picoruby-shell/shell_executables/_path.txt
64
+ @@ -17,0 +18,1 @@
65
+ +/bin/bootsel
66
+ diff --git a/mrbgems/picoruby-shell/shell_executables/bootsel.rb b/mrbgems/picoruby-shell/shell_executables/bootsel.rb
67
+ new file mode 100644
68
+ --- /dev/null
69
+ +++ b/mrbgems/picoruby-shell/shell_executables/bootsel.rb
70
+ @@ -0,0 +1 @@
71
+ +Machine.bootsel