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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/README.ja.md +30 -46
  4. data/README.md +30 -47
  5. data/RELEASING.md +2 -2
  6. data/THIRD_PARTY_NOTICES.md +1 -1
  7. data/lib/rpremote/bootsel_command.rb +8 -6
  8. data/lib/rpremote/build_command.rb +3 -3
  9. data/lib/rpremote/builder.rb +15 -9
  10. data/lib/rpremote/cli/filesystem_commands.rb +105 -0
  11. data/lib/rpremote/cli.rb +20 -105
  12. data/lib/rpremote/config.rb +6 -5
  13. data/lib/rpremote/config_show.rb +13 -5
  14. data/lib/rpremote/deploy_command.rb +85 -55
  15. data/lib/rpremote/help/dfu_commands.rb +59 -0
  16. data/lib/rpremote/help/filesystem_commands.rb +36 -0
  17. data/lib/rpremote/help/project_commands.rb +89 -0
  18. data/lib/rpremote/help/remote_commands.rb +54 -0
  19. data/lib/rpremote/help/root_text.rb +63 -0
  20. data/lib/rpremote/help.rb +27 -300
  21. data/lib/rpremote/language_source.rb +19 -2
  22. data/lib/rpremote/mrbgems/definition.rb +116 -0
  23. data/lib/rpremote/mrbgems/file_writer.rb +26 -0
  24. data/lib/rpremote/mrbgems/lockfile.rb +131 -0
  25. data/lib/rpremote/mrbgems.rb +95 -155
  26. data/lib/rpremote/mrbgems_command.rb +19 -4
  27. data/lib/rpremote/picoruby_compiler.rb +28 -0
  28. data/lib/rpremote/picoruby_source_patch.rb +0 -4
  29. data/lib/rpremote/target.rb +1 -1
  30. data/lib/rpremote/terminal.rb +0 -2
  31. data/lib/rpremote/version.rb +1 -1
  32. data/lib/rpremote.rb +1 -0
  33. data/patches/picoruby-4.0.4-pwm-sleep-clock.patch +27 -0
  34. data/sig/rpremote.rbs +20 -4
  35. data/tasks/firmware_build.rb +10 -2
  36. metadata +12 -3
  37. data/patches/picoruby-3.4.5-bootsel.patch +0 -71
  38. data/patches/picoruby-4.0.3-bootsel.patch +0 -71
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rpremote
4
+ class Mrbgems
5
+ module Lockfile
6
+ def read_lock(required: true)
7
+ contents = JSON.parse(File.read(lock_path))
8
+ validate_lock!(contents)
9
+ contents
10
+ rescue Errno::ENOENT
11
+ raise LockError, "mrbgems lock file does not exist: #{lock_path}" if required
12
+
13
+ nil
14
+ rescue JSON::ParserError => e
15
+ raise LockError, "invalid mrbgems lock file #{lock_path}: #{e.message}"
16
+ end
17
+
18
+ def require_names
19
+ lock_data = read_lock(required: false)
20
+ return [] unless lock_data
21
+
22
+ lock_data.fetch("gems").filter_map do |gem|
23
+ gem["require_name"] if gem.fetch("auto_require", true)
24
+ end.uniq
25
+ end
26
+
27
+ def prepend_requires(source)
28
+ names = require_names
29
+ return source if names.empty?
30
+
31
+ names.map { |name| "require #{name.inspect}\n" }.join.b + source.b
32
+ end
33
+
34
+ def locked_vm
35
+ read_lock.fetch("vm", nil)&.to_sym
36
+ end
37
+
38
+ private
39
+
40
+ def validate_lock!(contents)
41
+ validate_lock_header!(contents)
42
+ validate_lock_entries!(contents)
43
+ end
44
+
45
+ def validate_lock_header!(contents)
46
+ unless contents.is_a?(Hash) && contents["version"] == LOCK_VERSION
47
+ raise LockError,
48
+ "unsupported mrbgems lock format: #{lock_path}; " \
49
+ "run `rpremote mrbgems lock` to regenerate it"
50
+ end
51
+ excluded = contents.fetch("without", [])
52
+ unless contents["gems"].is_a?(Array) && contents["with"].is_a?(Array) && excluded.is_a?(Array) &&
53
+ valid_groups?(contents["with"]) && valid_groups?(excluded)
54
+ raise LockError, "invalid mrbgems lock file: #{lock_path}"
55
+ end
56
+ return if contents["vm"].nil? || VMS.map(&:to_s).include?(contents["vm"])
57
+
58
+ raise LockError, "invalid mrbgems VM in lock file: #{contents["vm"].inspect}"
59
+ end
60
+
61
+ def validate_lock_entries!(contents)
62
+ contents["gems"].each do |entry|
63
+ type = entry["type"] if entry.is_a?(Hash)
64
+ valid = entry.is_a?(Hash) && (type == "github" ? valid_github_lock?(entry) : valid_path_lock?(entry))
65
+ valid &&= valid_groups?(entry["groups"])
66
+ valid &&= entry_groups_selected?(entry, contents["with"], contents.fetch("without", []))
67
+ raise LockError, "invalid mrbgems lock entry: #{entry.inspect}" unless valid
68
+ end
69
+ keys = contents["gems"].map { |entry| [entry["type"], entry["source"]] }
70
+ raise LockError, "duplicate mrbgems lock entries: #{lock_path}" unless keys.uniq.length == keys.length
71
+ end
72
+
73
+ def valid_github_lock?(entry)
74
+ entry["source"].is_a?(String) && GITHUB_PATTERN.match?(entry["source"]) &&
75
+ entry["branch"].is_a?(String) && !entry["branch"].empty? &&
76
+ !entry["branch"].match?(/[\x00-\x1f\x7f]/) && entry["commit"].is_a?(String) &&
77
+ COMMIT_PATTERN.match?(entry["commit"]) &&
78
+ valid_require_name?(entry["require_name"]) &&
79
+ valid_auto_require?(entry)
80
+ end
81
+
82
+ def valid_path_lock?(entry)
83
+ entry["type"] == "path" && entry["source"].is_a?(String) && !entry["source"].empty? &&
84
+ !entry["source"].start_with?(File::SEPARATOR) && !entry["source"].match?(/[\x00-\x1f\x7f]/) &&
85
+ entry["sha256"].is_a?(String) && /\A[0-9a-f]{64}\z/.match?(entry["sha256"]) &&
86
+ valid_require_name?(entry["require_name"]) &&
87
+ valid_auto_require?(entry)
88
+ end
89
+
90
+ def valid_require_name?(name)
91
+ name.nil? || (name.is_a?(String) && !name.empty? && !name.match?(/[\x00-\x1f\x7f]/))
92
+ end
93
+
94
+ def valid_auto_require?(entry)
95
+ !entry.key?("auto_require") || entry["auto_require"] == true || entry["auto_require"] == false
96
+ end
97
+
98
+ def valid_groups?(groups)
99
+ groups.nil? || (groups.is_a?(Array) && groups.uniq.length == groups.length &&
100
+ groups.all? { |name| name.is_a?(String) && GROUP_PATTERN.match?(name) })
101
+ end
102
+
103
+ def entry_groups_selected?(entry, selected, excluded)
104
+ groups = Array(entry["groups"])
105
+ groups.empty? || (groups.intersect?(selected) && !groups.intersect?(excluded))
106
+ end
107
+
108
+ def validate_locked_paths!(entries)
109
+ entries.select { |entry| entry["type"] == "path" }.each do |entry|
110
+ directory = resolved_locked_path(entry)
111
+ raise LockError, "locked local mrbgem does not exist: #{directory}" unless mrbgem_directory?(directory)
112
+ next if digest_directory(directory) == entry.fetch("sha256")
113
+
114
+ raise LockError,
115
+ "locked local mrbgem has changed: #{directory}; " \
116
+ "run `rpremote mrbgems lock` to update #{lock_path}"
117
+ end
118
+ end
119
+
120
+ def mrbgem_directory?(directory)
121
+ File.file?(File.join(directory, "mrbgem.rake"))
122
+ end
123
+
124
+ def resolved_locked_path(entry)
125
+ File.expand_path(entry.fetch("source"), File.dirname(lock_path))
126
+ end
127
+ end
128
+
129
+ include Lockfile
130
+ end
131
+ end
@@ -9,12 +9,13 @@ module Rpremote
9
9
  class Mrbgems
10
10
  DEFAULT_PATH = "Mrbgems"
11
11
  DEFAULT_LOCK_PATH = "Mrbgems.lock"
12
- LOCK_VERSION = 1
12
+ LOCK_VERSION = 2
13
13
  GITHUB_PATTERN = %r{\A[\w.-]+/[\w.-]+\z}
14
14
  COMMIT_PATTERN = /\A[0-9a-f]{40,64}\z/i
15
+ GROUP_PATTERN = /\A[a-zA-Z0-9][a-zA-Z0-9_-]*\z/
15
16
  VMS = %i[mruby mrubyc].freeze
16
17
 
17
- Dependency = Data.define(:type, :source, :branch, :commit, :path, :require_name, :auto_require)
18
+ Dependency = Data.define(:type, :source, :branch, :commit, :path, :require_name, :auto_require, :groups)
18
19
  Overlay = Data.define(:path, :fingerprint)
19
20
 
20
21
  class Error < Rpremote::Error; end
@@ -25,10 +26,30 @@ module Rpremote
25
26
 
26
27
  def initialize(path: DEFAULT_PATH, lock_path: nil, cwd: Dir.pwd, resolver: nil)
27
28
  @path = File.expand_path(path, cwd)
28
- @lock_path = File.expand_path(lock_path || DEFAULT_LOCK_PATH, File.dirname(@path))
29
+ @lock_path = if lock_path
30
+ File.expand_path(lock_path, cwd)
31
+ else
32
+ File.expand_path(DEFAULT_LOCK_PATH, File.dirname(@path))
33
+ end
29
34
  @resolver = resolver || method(:resolve_github)
30
35
  end
31
36
 
37
+ def self.parse_groups(value)
38
+ groups = value.to_s.split(",", -1).map(&:strip)
39
+ raise DefinitionError, "invalid mrbgem group: #{value.inspect}" if groups.empty?
40
+
41
+ normalize_groups(groups)
42
+ end
43
+
44
+ def self.normalize_groups(values)
45
+ Array(values).map do |name|
46
+ value = name.to_s
47
+ raise DefinitionError, "invalid mrbgem group: #{name.inspect}" unless GROUP_PATTERN.match?(value)
48
+
49
+ value.to_sym
50
+ end.uniq.freeze
51
+ end
52
+
32
53
  def exist?
33
54
  File.file?(path)
34
55
  end
@@ -37,6 +58,18 @@ module Rpremote
37
58
  @dependencies ||= load_definition
38
59
  end
39
60
 
61
+ def selected_dependencies(with: [], without: [])
62
+ selected_groups = self.class.normalize_groups(with)
63
+ excluded_groups = self.class.normalize_groups(without)
64
+ dependencies
65
+ available = @definition.groups
66
+ validate_selected_groups!(selected_groups + excluded_groups, available)
67
+ dependencies.select do |dependency|
68
+ dependency.groups.empty? ||
69
+ (dependency.groups.intersect?(selected_groups) && !dependency.groups.intersect?(excluded_groups))
70
+ end
71
+ end
72
+
40
73
  def vm
41
74
  dependencies
42
75
  @definition.vm_name
@@ -47,50 +80,36 @@ module Rpremote
47
80
  dependencies
48
81
  end
49
82
 
50
- def lock(update: false)
51
- check
52
- previous = update ? nil : read_lock(required: false)
53
- entries = dependencies.map { |dependency| lock_entry(dependency, previous) }
54
- contents = { "version" => LOCK_VERSION, "vm" => vm&.to_s, "gems" => entries }.compact
55
- write_json(lock_path, contents)
56
- contents
57
- end
58
-
59
- def read_lock(required: true)
60
- contents = JSON.parse(File.read(lock_path))
83
+ def lock(update: false, with: [], without: [])
84
+ selected_groups = self.class.normalize_groups(with)
85
+ excluded_groups = self.class.normalize_groups(without)
86
+ previous = update ? nil : read_previous_lock
87
+ selected = selected_dependencies(with: selected_groups, without: excluded_groups)
88
+ selected.each { |dependency| validate_local!(dependency) if dependency.type == :path }
89
+ entries = selected.map do |dependency|
90
+ lock_entry(dependency, previous)
91
+ end
92
+ contents = {
93
+ "version" => LOCK_VERSION,
94
+ "vm" => vm&.to_s,
95
+ "with" => selected_groups.map(&:to_s),
96
+ "without" => excluded_groups.map(&:to_s),
97
+ "gems" => entries
98
+ }.compact
61
99
  validate_lock!(contents)
100
+ write_json(lock_path, contents)
62
101
  contents
63
- rescue Errno::ENOENT
64
- raise LockError, "mrbgems lock file does not exist: #{lock_path}" if required
65
-
66
- nil
67
- rescue JSON::ParserError => e
68
- raise LockError, "invalid mrbgems lock file #{lock_path}: #{e.message}"
69
- end
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 do |gem|
76
- gem["require_name"] if gem.fetch("auto_require", true)
77
- end.uniq
78
- end
79
-
80
- def prepend_requires(source)
81
- names = require_names
82
- return source if names.empty?
83
-
84
- names.map { |name| "require #{name.inspect}\n" }.join.b + source.b
85
102
  end
86
103
 
87
- def generate_overlay(base_config:, target:, directory:, update: false)
88
- lock_data = lock(update: update)
104
+ def generate_overlay(base_config:, target:, directory:)
105
+ lock_data = read_lock
106
+ entries = lock_data.fetch("gems")
107
+ validate_locked_paths!(entries)
89
108
  fingerprint = build_fingerprint(base_config, lock_data)
90
109
  output_dir = File.join(File.expand_path(directory), fingerprint)
91
110
  output_path = File.join(output_dir, "build_config.rb")
92
111
  FileUtils.mkdir_p(output_dir)
93
- write_file(output_path, overlay_source(base_config, target, lock_data.fetch("gems")))
112
+ write_file(output_path, overlay_source(base_config, target, entries))
94
113
  Overlay.new(path: output_path, fingerprint: fingerprint)
95
114
  end
96
115
 
@@ -128,7 +147,7 @@ module Rpremote
128
147
  "branch" => dependency.branch, "commit" => validate_commit!(commit),
129
148
  "require_name" => dependency.require_name }.compact
130
149
  else
131
- entry = { "type" => "path", "source" => dependency.source,
150
+ entry = { "type" => "path", "source" => locked_path(dependency.path),
132
151
  "sha256" => digest_directory(dependency.path),
133
152
  "require_name" => dependency.require_name || local_require_name(dependency.path) }.compact
134
153
  end
@@ -136,6 +155,7 @@ module Rpremote
136
155
  entry.delete("require_name")
137
156
  entry["auto_require"] = false
138
157
  end
158
+ entry["groups"] = dependency.groups.map(&:to_s) unless dependency.groups.empty?
139
159
  entry
140
160
  end
141
161
 
@@ -149,6 +169,26 @@ module Rpremote
149
169
  entry&.fetch("commit", nil)
150
170
  end
151
171
 
172
+ def read_previous_lock
173
+ contents = JSON.parse(File.read(lock_path))
174
+ raise LockError, "invalid mrbgems lock file: #{lock_path}" unless contents.is_a?(Hash)
175
+
176
+ version = contents["version"]
177
+ supported = [1, LOCK_VERSION].include?(version) && contents["gems"].is_a?(Array)
178
+ raise LockError, "unsupported mrbgems lock format: #{lock_path}" unless supported
179
+
180
+ if version == LOCK_VERSION
181
+ validate_lock!(contents)
182
+ elsif contents["gems"].any? { |entry| !entry.is_a?(Hash) }
183
+ raise LockError, "invalid mrbgems lock file: #{lock_path}"
184
+ end
185
+ contents
186
+ rescue Errno::ENOENT
187
+ nil
188
+ rescue JSON::ParserError => e
189
+ raise LockError, "invalid mrbgems lock file #{lock_path}: #{e.message}"
190
+ end
191
+
152
192
  def validate_commit!(commit)
153
193
  value = commit.to_s.downcase
154
194
  raise LockError, "invalid Git commit: #{commit.inspect}" unless COMMIT_PATTERN.match?(value)
@@ -190,41 +230,6 @@ module Rpremote
190
230
  relative.split(File::SEPARATOR).intersect?(%w[.git build tmp])
191
231
  end
192
232
 
193
- def validate_lock!(contents)
194
- unless contents.is_a?(Hash) && contents["version"] == LOCK_VERSION && contents["gems"].is_a?(Array)
195
- raise LockError, "unsupported mrbgems lock format: #{lock_path}"
196
- end
197
- unless contents["vm"].nil? || VMS.map(&:to_s).include?(contents["vm"])
198
- raise LockError, "invalid mrbgems VM in lock file: #{contents["vm"].inspect}"
199
- end
200
-
201
- contents["gems"].each do |entry|
202
- type = entry["type"]
203
- valid = type == "github" ? valid_github_lock?(entry) : valid_path_lock?(entry)
204
- raise LockError, "invalid mrbgems lock entry: #{entry.inspect}" unless valid
205
- end
206
- end
207
-
208
- def valid_github_lock?(entry)
209
- GITHUB_PATTERN.match?(entry["source"].to_s) && !entry["branch"].to_s.empty? &&
210
- COMMIT_PATTERN.match?(entry["commit"].to_s) && valid_require_name?(entry["require_name"]) &&
211
- valid_auto_require?(entry)
212
- end
213
-
214
- def valid_path_lock?(entry)
215
- entry["type"] == "path" && !entry["source"].to_s.empty? &&
216
- /\A[0-9a-f]{64}\z/.match?(entry["sha256"].to_s) && valid_require_name?(entry["require_name"]) &&
217
- valid_auto_require?(entry)
218
- end
219
-
220
- def valid_require_name?(name)
221
- name.nil? || (name.is_a?(String) && !name.empty? && !name.match?(/[\x00-\x1f\x7f]/))
222
- end
223
-
224
- def valid_auto_require?(entry)
225
- !entry.key?("auto_require") || entry["auto_require"] == true || entry["auto_require"] == false
226
- end
227
-
228
233
  def local_require_name(directory)
229
234
  source = File.read(File.join(directory, "mrbgem.rake"))
230
235
  match = source.match(/^\s*spec\.require_name\s*=\s*["']([^"']+)["']\s*$/)
@@ -233,7 +238,7 @@ module Rpremote
233
238
 
234
239
  def build_fingerprint(base_config, lock_data)
235
240
  Digest::SHA256.hexdigest(
236
- [File.binread(path), File.binread(base_config), JSON.generate(lock_data)].join("\0")
241
+ [File.binread(base_config), JSON.generate(lock_data)].join("\0")
237
242
  ).slice(0, 12)
238
243
  end
239
244
 
@@ -245,93 +250,28 @@ module Rpremote
245
250
  "load #{File.expand_path(base_config).inspect}",
246
251
  "conf = MRuby.targets.fetch(#{target.inspect})"
247
252
  ]
248
- dependencies.zip(locked_entries).each do |dependency, locked|
249
- lines << if dependency.type == :github
250
- "conf.gem github: #{dependency.source.inspect}, " \
251
- "branch: #{dependency.branch.inspect}, checksum_hash: #{locked.fetch("commit").inspect}"
253
+ locked_entries.each do |locked|
254
+ lines << if locked.fetch("type") == "github"
255
+ "conf.gem github: #{locked.fetch("source").inspect}, " \
256
+ "branch: #{locked.fetch("branch").inspect}, checksum_hash: #{locked.fetch("commit").inspect}"
252
257
  else
253
- "conf.gem #{dependency.path.inspect}"
258
+ "conf.gem #{resolved_locked_path(locked).inspect}"
254
259
  end
255
260
  end
256
261
  "#{lines.join("\n")}\n"
257
262
  end
258
263
 
259
- def write_json(filename, contents)
260
- write_file(filename, "#{JSON.pretty_generate(contents)}\n")
261
- end
262
-
263
- def write_file(filename, contents)
264
- return if File.file?(filename) && File.binread(filename) == contents
265
-
266
- FileUtils.mkdir_p(File.dirname(filename))
267
- temporary = "#{filename}.tmp-#{Process.pid}"
268
- File.binwrite(temporary, contents)
269
- File.rename(temporary, filename)
270
- ensure
271
- FileUtils.rm_f(temporary) if temporary
264
+ def locked_path(directory)
265
+ Pathname.new(directory).relative_path_from(Pathname.new(File.dirname(lock_path))).to_s
272
266
  end
273
267
 
274
- class Definition
275
- attr_reader :dependencies, :vm_name
276
-
277
- def initialize(filename)
278
- @directory = File.dirname(filename)
279
- @dependencies = []
280
- end
281
-
282
- def vm(name)
283
- value = name.to_sym
284
- raise DefinitionError, "unsupported mrbgems VM: #{name.inspect}" unless VMS.include?(value)
285
- raise DefinitionError, "mrbgems VM specified more than once" if vm_name
286
-
287
- @vm_name = value
288
- end
289
-
290
- def gem(github: nil, path: nil, branch: "main", commit: nil, require: nil, auto_require: true)
291
- sources = [github, path].compact
292
- raise DefinitionError, "gem requires exactly one of github or path" unless sources.length == 1
293
- unless require.nil? || (require.is_a?(String) && !require.empty? && !require.match?(/[\x00-\x1f\x7f]/))
294
- raise DefinitionError, "invalid mrbgem require name: #{require.inspect}"
295
- end
296
-
297
- valid_auto_require = [true, false].include?(auto_require)
298
- raise DefinitionError, "mrbgem auto_require must be true or false: #{auto_require.inspect}" unless valid_auto_require
299
-
300
- dependency = if github
301
- github_dependency(github, branch, commit, require, auto_require)
302
- else
303
- path_dependency(path, branch, commit, require, auto_require)
304
- end
305
- key = [dependency.type, dependency.source]
306
- raise DefinitionError, "duplicate mrbgem: #{dependency.source}" if dependencies.any? do |item|
307
- [item.type, item.source] == key
308
- end
309
-
310
- dependencies << dependency
311
- end
312
-
313
- private
314
-
315
- attr_reader :directory
316
-
317
- def github_dependency(source, branch, commit, require_name, auto_require)
318
- raise DefinitionError, "invalid GitHub mrbgem: #{source.inspect}" unless GITHUB_PATTERN.match?(source.to_s)
319
- raise DefinitionError, "GitHub mrbgem branch must not be empty" if branch.to_s.empty?
320
- raise DefinitionError, "invalid Git commit: #{commit.inspect}" if commit && !COMMIT_PATTERN.match?(commit.to_s)
321
-
322
- Dependency.new(type: :github, source: source, branch: branch,
323
- commit: commit&.downcase, path: nil, require_name: require_name,
324
- auto_require: auto_require)
325
- end
326
-
327
- def path_dependency(source, branch, commit, require_name, auto_require)
328
- raise DefinitionError, "local mrbgem path must not be empty" if source.to_s.empty?
329
- raise DefinitionError, "local mrbgem does not accept branch or commit" if branch != "main" || commit
330
-
331
- Dependency.new(type: :path, source: source, branch: nil, commit: nil,
332
- path: File.expand_path(source, directory), require_name: require_name,
333
- auto_require: auto_require)
334
- end
268
+ def validate_selected_groups!(selected, available)
269
+ unknown = selected - available
270
+ raise DefinitionError, "unknown mrbgem group: #{unknown.join(", ")}" unless unknown.empty?
335
271
  end
336
272
  end
337
273
  end
274
+
275
+ require_relative "mrbgems/file_writer"
276
+ require_relative "mrbgems/lockfile"
277
+ require_relative "mrbgems/definition"
@@ -10,12 +10,21 @@ module Rpremote
10
10
  def self.run(args, output: $stdout, mrbgems: Mrbgems)
11
11
  command = args.shift
12
12
  options = { path: DEFAULT_PATH, lock_path: nil }
13
+ with = nil
14
+ without = nil
13
15
  OptionParser.new do |opts|
14
16
  opts.on("--file FILE") { |value| options[:path] = value }
15
17
  opts.on("--lockfile FILE") { |value| options[:lock_path] = value }
18
+ opts.on("--with GROUPS") { |value| with = Mrbgems.parse_groups(value) }
19
+ opts.on("--without GROUPS") { |value| without = Mrbgems.parse_groups(value) }
16
20
  end.parse!(args)
17
21
  raise ArgumentError, "mrbgems does not accept arguments" unless args.empty?
18
22
 
23
+ unless %w[lock update].include?(command)
24
+ raise ArgumentError, "--with is only available for mrbgems lock and update" if with
25
+ raise ArgumentError, "--without is only available for mrbgems lock and update" if without
26
+ end
27
+
19
28
  manager = mrbgems.new(**options)
20
29
  case command
21
30
  when "check"
@@ -24,7 +33,7 @@ module Rpremote
24
33
  when "list"
25
34
  list(manager, output)
26
35
  when "lock", "update"
27
- result = manager.lock(update: command == "update")
36
+ result = manager.lock(update: command == "update", with: with || [], without: without || [])
28
37
  output.puts("locked #{result.fetch("gems").length} mrbgems: #{manager.lock_path}")
29
38
  else
30
39
  raise ArgumentError, "unknown mrbgems command: #{command || "(none)"}"
@@ -36,13 +45,19 @@ module Rpremote
36
45
  lock = manager.read_lock(required: false)
37
46
  entries = lock&.fetch("gems", []) || []
38
47
  dependencies.each do |dependency|
39
- entry = entries.find do |item|
40
- item["type"] == dependency.type.to_s && item["source"] == dependency.source
41
- end
48
+ entry = entries.find { |item| matching_entry?(manager, dependency, item) }
42
49
  suffix = entry && (entry["commit"] || entry["sha256"])
43
50
  output.puts([dependency.type, dependency.source, suffix&.slice(0, 12)].compact.join(" "))
44
51
  end
45
52
  end
46
53
  private_class_method :list
54
+
55
+ def self.matching_entry?(manager, dependency, entry)
56
+ return false unless entry["type"] == dependency.type.to_s
57
+ return entry["source"] == dependency.source if dependency.type == :github
58
+
59
+ File.expand_path(entry["source"], File.dirname(manager.lock_path)) == dependency.path
60
+ end
61
+ private_class_method :matching_entry?
47
62
  end
48
63
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Rpremote
6
+ class PicoRubyCompiler
7
+ class Error < StandardError; end
8
+
9
+ CANDIDATES = %w[
10
+ build/mrbc/default/bin/mrbc
11
+ build/host/bin/mrbc
12
+ ].freeze
13
+
14
+ def self.ensure_legacy_path!(root)
15
+ root = File.expand_path(root)
16
+ legacy_path = File.join(root, "bin/mrbc")
17
+ return legacy_path if File.executable?(legacy_path)
18
+
19
+ compiler = CANDIDATES.map { |path| File.join(root, path) }.find { |path| File.executable?(path) }
20
+ raise Error, "PicoRuby mrbc was not built under #{root}" unless compiler
21
+
22
+ FileUtils.mkdir_p(File.dirname(legacy_path))
23
+ relative = File.join("..", compiler.delete_prefix("#{root}/"))
24
+ FileUtils.ln_sf(relative, legacy_path)
25
+ legacy_path
26
+ end
27
+ end
28
+ end
@@ -3,7 +3,6 @@
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"
7
6
  PWM_PATH = "mrbgems/picoruby-pwm/ports/rp2040/pwm.c"
8
7
  PATCH_ALIASES = { "3.4.2" => "3.4.5" }.freeze
9
8
 
@@ -21,8 +20,6 @@ module Rpremote
21
20
  patch_version = PATCH_ALIASES.fetch(version, version)
22
21
  patches(patch_version).each do |patch, prerequisite|
23
22
  next unless File.file?(patch) && File.file?(File.join(source, prerequisite))
24
- next if prerequisite == BOOTSEL_PATH && File.read(File.join(source, prerequisite)).include?("Machine_bootsel")
25
-
26
23
  next if git_apply?(source, patch, "--reverse", "--check")
27
24
 
28
25
  raise Error, "cannot apply rpremote patch to PicoRuby #{version}" unless git_apply?(source, patch, "--check")
@@ -36,7 +33,6 @@ module Rpremote
36
33
  def patches(patch_version)
37
34
  definitions = {
38
35
  "ruby-exception-status" => JOB_PATH,
39
- "bootsel" => BOOTSEL_PATH,
40
36
  "pwm-sleep-clock" => PWM_PATH
41
37
  }
42
38
  definitions.map do |name, prerequisite|
@@ -3,7 +3,7 @@
3
3
  module Rpremote
4
4
  class Target
5
5
  DEFAULT_LANGUAGE = "picoruby"
6
- DEFAULT_LANGUAGE_VERSION = "4.0.3"
6
+ DEFAULT_LANGUAGE_VERSION = "latest"
7
7
  DEFAULT_BOARD = "pico2"
8
8
  DEFAULT_CACHE_DIR = "firmware"
9
9
  IDENTIFIER_PATTERN = /\A[a-zA-Z0-9][a-zA-Z0-9._-]*\z/
@@ -75,8 +75,6 @@ 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
78
  def normalize_line_endings(data)
81
79
  normalized = +"".b
82
80
  data.each_byte do |byte|
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rpremote
4
- VERSION = "0.5.0"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/rpremote.rb CHANGED
@@ -21,6 +21,7 @@ require_relative "rpremote/runner"
21
21
  require_relative "rpremote/resetter"
22
22
  require_relative "rpremote/recursive_copy"
23
23
  require_relative "rpremote/picoruby_source_patch"
24
+ require_relative "rpremote/picoruby_compiler"
24
25
  require_relative "rpremote/language_source"
25
26
  require_relative "rpremote/nuke_firmware"
26
27
  require_relative "rpremote/mrbgems"
@@ -0,0 +1,27 @@
1
+ diff --git a/mrbgems/picoruby-pwm/ports/rp2040/pwm.c b/mrbgems/picoruby-pwm/ports/rp2040/pwm.c
2
+ --- a/mrbgems/picoruby-pwm/ports/rp2040/pwm.c
3
+ +++ b/mrbgems/picoruby-pwm/ports/rp2040/pwm.c
4
+ @@ -1,3 +1,4 @@
5
+ #include "pico/stdlib.h"
6
+ #include "hardware/clocks.h"
7
+ #include "hardware/pwm.h"
8
+ +#include "hardware/structs/clocks.h"
9
+ @@ -61,4 +62,18 @@
10
+ {
11
+ uint slice_num = pwm_gpio_to_slice_num(pin);
12
+ + /* The scheduler uses WFI when no task is runnable, and the HAL normally
13
+ + * gates clk_sys_pwm while a core sleeps. Ungate it before starting a slice
14
+ + * so autonomous PWM output continues between scheduler ticks. */
15
+ + if (enabled) {
16
+ + hw_set_bits(&clocks_hw->sleep_en0,
17
+ + CLOCKS_SLEEP_EN0_CLK_SYS_PWM_BITS);
18
+ + }
19
+ pwm_set_enabled(slice_num, enabled);
20
+ + /* Preserve the clock for every active slice. Once the final slice stops,
21
+ + * restore the HAL's low-power policy instead of keeping PWM clocked merely
22
+ + * because a PWM object was initialized earlier. */
23
+ + if (!enabled && pwm_hw->en == 0) {
24
+ + hw_clear_bits(&clocks_hw->sleep_en0,
25
+ + CLOCKS_SLEEP_EN0_CLK_SYS_PWM_BITS);
26
+ + }
27
+ }