Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions lib/syskit/cli/main.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,91 @@ def orogen_test(*args)
"commands related to monitoring and commanding a running Syskit system"
subcommand "telemetry", Telemetry::CLI

desc "bundle_dir NAME", "print the directory of a bundle"
def bundle_dir(name)
require "rock/bundle" unless defined?(Rock::Bundles)

bundle = Rock::Bundles.each_bundle.find { |b| b.name == name }
if bundle
puts bundle.path
else
$stderr.puts "No bundle named '#{name}' found."
$stderr.puts "Available bundles are: #{Rock::Bundles.each_bundle.map(&:name).sort.join(', ')}"
exit 1
end
end
Comment on lines 43 to +56

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This possibly exists in autoproj, or at least it is possible to get this information from an autoproj command (ie autoproj show bundles/bla)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you can find packages with autoproj locate PACKAGE_NAME or with autoproj show PACKAGE_NAME as you said. But IMO it can be helpful to have this extra command, it is simple, easy to remember, and doesn't require autoproj. On the other hand, it doesn't bring any disadvantages


desc "config ROBOT", "print the configuration of a given robot"
option :key, type: :string, repeatable: true, default: []
option :from_bundle, type: :string, default: nil
def config(robot_name)
Comment on lines +58 to +61

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically, syskit is ran from the bundle itself. Besides that, I think a more useful command would to extract the variables inside the Conf variable from a syskit run, but that is way more complex (and might encounter friction if Conf is used as something dynamic).

I dont think that what you aimed for here should be packed with syskit, it can mostly be resolved by using a sequence of bash commands, and it is outside of syskit's scope

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a more useful command would to extract the variables inside the Conf variable from a syskit run, but that is way more complex (and might encounter friction if Conf is used as something dynamic).

I could also that, but I believe it should be a distinct command from this one. The current proposed syskit config command is designed to work without requiring the system to be running, which is nice to have. Otherwise, one would need to start the system to inspect the config files.
A part from the templated sdf generation use case we have in mind, this command could be useful for a quick inspection on the configuration of the system without needing to open all the files manually and read them, which is convenient.
Again, it doesn't bring any disadvantages adding support for this command.

syskit is ran from the bundle itself.

Yes, but for the templated SDF generation use case we dont run rock-gazebo necessarily from the robot bundle dir, we could run it from an arbitrary directory.

require "yaml"

if (from_bundle = options[:from_bundle])
require "rock/bundle" unless defined?(Rock::Bundles)

bundle = Rock::Bundles.each_bundle.find { |b| b.name == from_bundle }
if bundle
Dir.chdir(bundle.path)
else
$stderr.puts "No bundle named '#{from_bundle}' found."
$stderr.puts "Available bundles are: #{Rock::Bundles.each_bundle.map(&:name).sort.join(', ')}"
exit 1
end
end

app = Roby.app
app.require_app_dir
app.load_config_yaml
app.setup_robot_names_from_config_dir
app.robot(robot_name)

merger = proc do |_, old_val, new_val|
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
old_val.merge(new_val, &merger)
elsif old_val.is_a?(Array) && new_val.is_a?(Array)
old_val + new_val
else
new_val
end
end

config_path = ["config", "robots", "ROBOT.yml"]
merged_hash = app.find_files(
*config_path, all: true, order: :specific_last
).each_with_object({}) do |path, config_hash|
values = YAML.safe_load(File.read(path)) || {}
config_hash.merge!(values, &merger)
end

if options[:key].empty?
result = merged_hash
else
resolved_queries = options[:key].map do |key_query|
keys = key_query.split(".")
parent = keys.size > 1 ? merged_hash.dig(*keys[0...-1]) : merged_hash

unless parent.is_a?(Hash) && parent.has_key?(keys.last)
$stderr.puts "Error: Key '#{key_query}' not found."
exit 1
end

[keys, merged_hash.dig(*keys)]
end

if resolved_queries.size == 1
result = resolved_queries.first.last
else
result = resolved_queries.reduce({}) do |accum, (keys, value)|
nested_path_hash = keys.reverse.reduce(value) { |v, k| { k => v } }
accum.merge(nested_path_hash, &merger)
end
end
end

puts YAML.dump(result)
end

no_commands do
def setup_interface(*, **)
interface_version = options[:interface_version] || 1
Expand Down
157 changes: 157 additions & 0 deletions test/cli/test_main.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,163 @@ module CLI
assert_command_stops run_cmd
end
end

describe "bundle_dir" do
it "returns the absolute path of an existing bundle" do
require "tmpdir"
Dir.mktmpdir do |dir|
bundle_dir = File.join(dir, "my_temp_bundle")
FileUtils.mkdir_p(File.join(bundle_dir, "config"))
FileUtils.touch(File.join(bundle_dir, "config", "bundle.yml"))

set_environment_variable("ROCK_BUNDLE_PATH", dir)
cmd = run_command_and_stop "syskit bundle_dir my_temp_bundle"
assert_equal File.realdirpath(bundle_dir), File.realdirpath(cmd.stdout.strip)
assert_equal 0, cmd.exit_status
end
end

it "returns an error for a non-existent bundle" do
require "tmpdir"
Dir.mktmpdir do |dir|
set_environment_variable("ROCK_BUNDLE_PATH", dir)
cmd = run_command_and_stop "syskit bundle_dir non_existent_bundle_test_cli",
fail_on_error: false
assert_match /No bundle named 'non_existent_bundle_test_cli' found/, cmd.stderr
assert_equal 1, cmd.exit_status
end
end
end

describe "config" do
before do
run_command_and_stop "roby gen app"
write_file "config/robots/gazebo.yml", <<~YAML
system_definitions:
sdf_model_parameters:
param1: value1
param2: value2
array_param: [1, 2]
enable_something: false
nil_param: null
other_config:
something: value3
YAML
write_file "config/robots/gazebo_test.yml", <<~YAML
system_definitions:
sdf_model_parameters:
param2: overriden_value2
array_param: [3]
YAML
# Declare the robot and alias in app.yml so Roby knows about it
write_file "config/app.yml", <<~YAML
robots:
aliases:
test_alias: gazebo_test
robots:
gazebo_test: gazebo
YAML
end

it "prints the full configuration of a given robot when no keys are specified" do
cmd = run_command_and_stop "syskit config gazebo_test"
expected = {
"system_definitions" => {
"sdf_model_parameters" => {
"param1" => "value1",
"param2" => "overriden_value2",
"array_param" => [1, 2, 3]
},
"enable_something" => false,
"nil_param" => nil
},
"other_config" => {
"something" => "value3"
}
}
assert_equal expected, YAML.safe_load(cmd.stdout)
end

it "prints the value of a single key when --key is specified" do
cmd = run_command_and_stop "syskit config gazebo_test --key system_definitions.sdf_model_parameters.param1"
assert_equal "value1", YAML.safe_load(cmd.stdout)
end

it "properly preserves boolean false values when --key is specified" do
cmd = run_command_and_stop "syskit config gazebo_test --key system_definitions.enable_something"
assert_equal false, YAML.safe_load(cmd.stdout)
end

it "properly preserves explicit nil values when --key is specified" do
cmd = run_command_and_stop "syskit config gazebo_test --key system_definitions.nil_param"
assert_nil YAML.safe_load(cmd.stdout)
end

it "returns an error when querying a nested key inside a non-hash value" do
cmd = run_command_and_stop "syskit config gazebo_test --key system_definitions.enable_something.nested", fail_on_error: false
assert_match /Key 'system_definitions.enable_something.nested' not found/, cmd.stderr
assert_equal 1, cmd.exit_status
end

it "returns an error for a non-existent key" do
cmd = run_command_and_stop "syskit config gazebo_test --key non_existent", fail_on_error: false
assert_match /Key 'non_existent' not found/, cmd.stderr
assert_equal 1, cmd.exit_status
end

it "supports cumulative key filtering when multiple --key options are specified" do
cmd = run_command_and_stop "syskit config gazebo_test --key system_definitions.sdf_model_parameters.param1 --key other_config.something"
expected = {
"system_definitions" => {
"sdf_model_parameters" => {
"param1" => "value1"
}
},
"other_config" => {
"something" => "value3"
}
}
assert_equal expected, YAML.safe_load(cmd.stdout)
end

it "runs the command from a specific bundle when --from-bundle is specified" do
require "tmpdir"
Dir.mktmpdir do |dir|
bundle_dir = File.join(dir, "my_temp_bundle")
FileUtils.mkdir_p(File.join(bundle_dir, "config", "robots"))
FileUtils.touch(File.join(bundle_dir, "config", "bundle.yml"))
File.write(File.join(bundle_dir, "config", "robots", "gazebo.yml"), <<~YAML)
system_definitions:
enable_something: true
YAML
File.write(File.join(bundle_dir, "config", "app.yml"), <<~YAML)
robots:
robots:
gazebo: gazebo
YAML

set_environment_variable("ROCK_BUNDLE_PATH", dir)
cmd = run_command_and_stop "syskit config gazebo --from-bundle my_temp_bundle"
expected = {
"system_definitions" => {
"enable_something" => true
}
}
assert_equal expected, YAML.safe_load(cmd.stdout)
assert_equal 0, cmd.exit_status
end
end

it "returns an error for a non-existent bundle with --from-bundle" do
require "tmpdir"
Dir.mktmpdir do |dir|
set_environment_variable("ROCK_BUNDLE_PATH", dir)
cmd = run_command_and_stop "syskit config gazebo --from-bundle non_existent_bundle_test_cli", fail_on_error: false
assert_match /No bundle named 'non_existent_bundle_test_cli' found/, cmd.stderr
assert_equal 1, cmd.exit_status
end
end
end
end
end
end