From 8d04836e329141f2b1a6d5283edc613f625a207d Mon Sep 17 00:00:00 2001 From: Ed Schmerling Date: Wed, 11 Jul 2018 16:35:55 -0700 Subject: [PATCH 1/3] rostypegen within arbitrary modules (default: Main) for __precompile__ compatibility --- docs/src/index.md | 33 ++++++++++++++++++ src/gentypes.jl | 76 +++++++++++++++++++++--------------------- test/rospy.jl | 2 +- test/typegeneration.jl | 10 ++++++ 4 files changed, 82 insertions(+), 39 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index c9d8b20..36c9618 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -69,6 +69,39 @@ generated modules will be overwritten after `rostypegen()` is called again. Kee in mind that names cannot be cleared once defined so if a module is not regenerated, the first version will remain. +### Compatibility with Package Precompilation +As described above, by default `rostypegen` creates modules in `Main` -- however, +this behavior is incompatible with Julia package precompilation. If you are using +`RobotOS` in the context of a module, as opposed to a script, you may reduce +load-time latency (useful for real-life applications!) by generating the ROS type +modules inside your package module using an approach similar to the example below: + + # MyROSPackage.jl + __precompile__() + module MyROSPackage + + using RobotOS + + @rosimport geometry_msgs.msg: Pose + rostypegen(current_module()) + import .geometry_msgs.msg: Pose + # ... + + function __init__() + @rosimport geometry_msgs.msg: Pose + # ... + end + + end + +In this case, we have provided `rostypegen` with a root module (`MyROSPackage`) +for type generation. The Julia type corresponding to `geometry_msgs/Pose` now +lives at `MyROSPackage.geometry_msgs.msg.Pose`; note the extra dot in +`import .geometry_msgs.msg: Pose`. A precompiled package using `RobotOS` must +also duplicate any `@rosimport` statements within the `__init__` function in +its module so that necessary memory/objects in the Python runtime can be +allocated each time the package is used. + ## Usage: ROS API In general, the API functions provided directly match those provided in rospy, diff --git a/src/gentypes.jl b/src/gentypes.jl index f040f76..3c74527 100644 --- a/src/gentypes.jl +++ b/src/gentypes.jl @@ -39,24 +39,24 @@ const _rospy_imports = Dict{String,ROSPackage}() const _rospy_objects = Dict{String,PyObject}() const _rospy_modules = Dict{String,PyObject}() -const _ros_builtin_types = Dict{String, Symbol}( - "bool" => :Bool, - "int8" => :Int8, - "int16" => :Int16, - "int32" => :Int32, - "int64" => :Int64, - "uint8" => :UInt8, - "uint16" => :UInt16, - "uint32" => :UInt32, - "uint64" => :UInt64, - "float32" => :Float32, - "float64" => :Float64, - "string" => :String, - "time" => :Time, - "duration"=> :Duration, +const _ros_builtin_types = Dict{String,DataType}( + "bool" => Bool, + "int8" => Int8, + "int16" => Int16, + "int32" => Int32, + "int64" => Int64, + "uint8" => UInt8, + "uint16" => UInt16, + "uint32" => UInt32, + "uint64" => UInt64, + "float32" => Float32, + "float64" => Float64, + "string" => String, + "time" => Time, + "duration"=> Duration, #Deprecated by ROS but supported here - "char" => :UInt8, - "byte" => :Int8, + "char" => UInt8, + "byte" => Int8, ) #Abstract supertypes of all generated types @@ -143,18 +143,18 @@ function _rosimport(package::String, ismsg::Bool, names::String...) end """ - rostypegen() + rostypegen(rosrootmod::Module=Main) -Initiate the Julia type generation process after importing some ROS types. Creates modules in `Main` -with the same behavior as imported ROS modules in python. Should only be called once, after all -`@rosimport` statements are done. +Initiate the Julia type generation process after importing some ROS types. Creates modules in +rootrosmod (default is `Main`) with the same behavior as imported ROS modules in python. +Should only be called once, after all `@rosimport` statements are done. """ -function rostypegen() +function rostypegen(rosrootmod::Module=Main) global _rospy_imports pkgdeps = _collectdeps(_rospy_imports) pkglist = _order(pkgdeps) for pkg in pkglist - buildpackage(_rospy_imports[pkg]) + buildpackage(_rospy_imports[pkg], rosrootmod) end end @@ -277,38 +277,38 @@ function _import_rospy_pkg(package::String) end #The function that creates and fills the generated top-level modules -function buildpackage(pkg::ROSPackage) +function buildpackage(pkg::ROSPackage, rosrootmod::Module=Main) @debug("Building package: ", _name(pkg)) #Create the top-level module for the package in Main pkgsym = Symbol(_name(pkg)) - pkgcode = Expr(:toplevel, :(module ($pkgsym) end)) - Main.eval(pkgcode) - pkgmod = Main.eval(pkgsym) + pkgcode = :(module ($pkgsym) end) #Add msg and srv submodules if needed @debug_addindent if length(pkg.msg.members) > 0 msgmod = :(module msg end) - msgcode = modulecode(pkg.msg) + msgcode = modulecode(pkg.msg, rosrootmod) for expr in msgcode push!(msgmod.args[3].args, expr) end - eval(pkgmod, msgmod) + push!(pkgcode.args[3].args, msgmod) end if length(pkg.srv.members) > 0 srvmod = :(module srv end) - srvcode = modulecode(pkg.srv) + srvcode = modulecode(pkg.srv, rosrootmod) for expr in srvcode push!(srvmod.args[3].args, expr) end - eval(pkgmod, srvmod) + push!(pkgcode.args[3].args, srvmod) end + pkgcode = Expr(:toplevel, pkgcode) + rosrootmod.eval(pkgcode) @debug_subindent end #Generate all code for a .msg or .srv module -function modulecode(mod::ROSModule) +function modulecode(mod::ROSModule, rosrootmod::Module=Main) @debug("submodule: ", _fullname(mod)) modcode = Expr[] @@ -325,7 +325,7 @@ function modulecode(mod::ROSModule) end ) #Import statement specific to the module - append!(modcode, _importexprs(mod)) + append!(modcode, _importexprs(mod, rosrootmod)) #The exported names push!(modcode, _exportexpr(mod)) @@ -340,20 +340,20 @@ function modulecode(mod::ROSModule) end #The imports specific to each module, including dependant packages -function _importexprs(mod::ROSMsgModule) +function _importexprs(mod::ROSMsgModule, rosrootmod::Module=Main) imports = Expr[Expr(:import, :RobotOS, :AbstractMsg)] othermods = filter(d -> d != _name(mod), mod.deps) - append!(imports, [Expr(:using,:Main,Symbol(m),:msg) for m in othermods]) + append!(imports, [Expr(:using,Symbol(rosrootmod),Symbol(m),:msg) for m in othermods]) imports end -function _importexprs(mod::ROSSrvModule) +function _importexprs(mod::ROSSrvModule, rosrootmod::Module=Main) imports = Expr[ Expr(:import, :RobotOS, :AbstractSrv), Expr(:import, :RobotOS, :AbstractService), Expr(:import, :RobotOS, :_srv_reqtype), Expr(:import, :RobotOS, :_srv_resptype), ] - append!(imports, [Expr(:using,:Main,Symbol(m),:msg) for m in mod.deps]) + append!(imports, [Expr(:using,Symbol(rosrootmod),Symbol(m),:msg) for m in mod.deps]) imports end @@ -519,7 +519,7 @@ function _addtypemember!(exprs, namestr, typestr) end j_typ = _ros_builtin_types[typestr] #Compute the default value now - j_def = @eval _typedefault($j_typ) + j_def = _typedefault(j_typ) end namesym = Symbol(namestr) diff --git a/test/rospy.jl b/test/rospy.jl index 00b1425..fe60290 100644 --- a/test/rospy.jl +++ b/test/rospy.jl @@ -4,7 +4,7 @@ init_node("jltest", anonymous=true) #Parameters @test length(RobotOS.get_param_names()) > 0 @test has_param("rosdistro") -@test chomp(get_param("rosdistro")) in ["hydro", "indigo", "jade", "kinetic", "lunar"] +@test chomp(get_param("rosdistro")) in ["hydro", "indigo", "jade", "kinetic", "lunar", "melodic"] @test ! has_param("some_param") @test_throws KeyError get_param("some_param") @test_throws KeyError delete_param("some_param") diff --git a/test/typegeneration.jl b/test/typegeneration.jl index 2524fe5..77a35ec 100644 --- a/test/typegeneration.jl +++ b/test/typegeneration.jl @@ -28,6 +28,16 @@ rostypegen() @test isdefined(nav_msgs.srv, :GetPlanRequest) @test isdefined(nav_msgs.srv, :GetPlanResponse) +#type generation in a non-Main module +module TestModule + using RobotOS + @rosimport std_msgs.msg: Float32 + rostypegen(current_module()) +end +@test !isdefined(std_msgs.msg, :Float32Msg) +@test isdefined(TestModule, :std_msgs) +@test isdefined(TestModule.std_msgs.msg, :Float32Msg) + #message creation posestamp = geometry_msgs.msg.PoseStamped() @test typeof(posestamp.pose) == geometry_msgs.msg.Pose From ea29bdadcddda4eed46ec61b2ebd08f3f6713a47 Mon Sep 17 00:00:00 2001 From: Ed Schmerling Date: Fri, 3 Aug 2018 23:20:46 -0700 Subject: [PATCH 2/3] Add __init__() method to generated modules to avoid double @rosimport when precompiling results of rostypegen --- docs/src/index.md | 12 ++---------- src/gentypes.jl | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 36c9618..c752d1b 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -72,7 +72,7 @@ regenerated, the first version will remain. ### Compatibility with Package Precompilation As described above, by default `rostypegen` creates modules in `Main` -- however, this behavior is incompatible with Julia package precompilation. If you are using -`RobotOS` in the context of a module, as opposed to a script, you may reduce +`RobotOS` in your own module or package, as opposed to a script, you may reduce load-time latency (useful for real-life applications!) by generating the ROS type modules inside your package module using an approach similar to the example below: @@ -87,20 +87,12 @@ modules inside your package module using an approach similar to the example belo import .geometry_msgs.msg: Pose # ... - function __init__() - @rosimport geometry_msgs.msg: Pose - # ... - end - end In this case, we have provided `rostypegen` with a root module (`MyROSPackage`) for type generation. The Julia type corresponding to `geometry_msgs/Pose` now lives at `MyROSPackage.geometry_msgs.msg.Pose`; note the extra dot in -`import .geometry_msgs.msg: Pose`. A precompiled package using `RobotOS` must -also duplicate any `@rosimport` statements within the `__init__` function in -its module so that necessary memory/objects in the Python runtime can be -allocated each time the package is used. +`import .geometry_msgs.msg: Pose`. ## Usage: ROS API diff --git a/src/gentypes.jl b/src/gentypes.jl index 3c74527..3a92b3c 100644 --- a/src/gentypes.jl +++ b/src/gentypes.jl @@ -277,12 +277,13 @@ function _import_rospy_pkg(package::String) end #The function that creates and fills the generated top-level modules -function buildpackage(pkg::ROSPackage, rosrootmod::Module=Main) +function buildpackage(pkg::ROSPackage, rosrootmod::Module) @debug("Building package: ", _name(pkg)) #Create the top-level module for the package in Main pkgsym = Symbol(_name(pkg)) pkgcode = :(module ($pkgsym) end) + pkginitcode = :(function __init__() end) #Add msg and srv submodules if needed @debug_addindent @@ -293,6 +294,9 @@ function buildpackage(pkg::ROSPackage, rosrootmod::Module=Main) push!(msgmod.args[3].args, expr) end push!(pkgcode.args[3].args, msgmod) + for typ in pkg.msg.members + push!(pkginitcode.args[2].args, :(@rosimport $(pkgsym).msg: $(Symbol(typ)))) + end end if length(pkg.srv.members) > 0 srvmod = :(module srv end) @@ -301,14 +305,19 @@ function buildpackage(pkg::ROSPackage, rosrootmod::Module=Main) push!(srvmod.args[3].args, expr) end push!(pkgcode.args[3].args, srvmod) + for typ in pkg.srv.members + push!(pkginitcode.args[2].args, :(@rosimport $(pkgsym).srv: $(Symbol(typ)))) + end end + push!(pkgcode.args[3].args, :(import RobotOS.@rosimport)) + push!(pkgcode.args[3].args, pkginitcode) pkgcode = Expr(:toplevel, pkgcode) rosrootmod.eval(pkgcode) @debug_subindent end #Generate all code for a .msg or .srv module -function modulecode(mod::ROSModule, rosrootmod::Module=Main) +function modulecode(mod::ROSModule, rosrootmod::Module) @debug("submodule: ", _fullname(mod)) modcode = Expr[] @@ -340,13 +349,13 @@ function modulecode(mod::ROSModule, rosrootmod::Module=Main) end #The imports specific to each module, including dependant packages -function _importexprs(mod::ROSMsgModule, rosrootmod::Module=Main) +function _importexprs(mod::ROSMsgModule, rosrootmod::Module) imports = Expr[Expr(:import, :RobotOS, :AbstractMsg)] othermods = filter(d -> d != _name(mod), mod.deps) append!(imports, [Expr(:using,Symbol(rosrootmod),Symbol(m),:msg) for m in othermods]) imports end -function _importexprs(mod::ROSSrvModule, rosrootmod::Module=Main) +function _importexprs(mod::ROSSrvModule, rosrootmod::Module) imports = Expr[ Expr(:import, :RobotOS, :AbstractSrv), Expr(:import, :RobotOS, :AbstractService), From 6620b797a29121b9977869af5e82bdb802375eb7 Mon Sep 17 00:00:00 2001 From: Ed Schmerling Date: Thu, 9 Aug 2018 13:35:05 -0700 Subject: [PATCH 3/3] Import other ROS dependencies using the fullname of the rosrootmod to ensure visibility --- src/gentypes.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gentypes.jl b/src/gentypes.jl index 3a92b3c..469c174 100644 --- a/src/gentypes.jl +++ b/src/gentypes.jl @@ -352,7 +352,7 @@ end function _importexprs(mod::ROSMsgModule, rosrootmod::Module) imports = Expr[Expr(:import, :RobotOS, :AbstractMsg)] othermods = filter(d -> d != _name(mod), mod.deps) - append!(imports, [Expr(:using,Symbol(rosrootmod),Symbol(m),:msg) for m in othermods]) + append!(imports, [Expr(:using,fullname(rosrootmod)...,Symbol(m),:msg) for m in othermods]) imports end function _importexprs(mod::ROSSrvModule, rosrootmod::Module) @@ -362,7 +362,7 @@ function _importexprs(mod::ROSSrvModule, rosrootmod::Module) Expr(:import, :RobotOS, :_srv_reqtype), Expr(:import, :RobotOS, :_srv_resptype), ] - append!(imports, [Expr(:using,Symbol(rosrootmod),Symbol(m),:msg) for m in mod.deps]) + append!(imports, [Expr(:using,fullname(rosrootmod)...,Symbol(m),:msg) for m in mod.deps]) imports end