Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rostypegen within arbitrary modules (default: Main) for __precompile__ compatibility #47

Merged
merged 3 commits into from
Aug 10, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,31 @@ 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 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:

# MyROSPackage.jl
__precompile__()
module MyROSPackage

using RobotOS

@rosimport geometry_msgs.msg: Pose
rostypegen(current_module())
import .geometry_msgs.msg: Pose
# ...

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`.

## Usage: ROS API

In general, the API functions provided directly match those provided in rospy,
Expand Down
85 changes: 47 additions & 38 deletions src/gentypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -277,38 +277,47 @@ 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)
@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)
pkginitcode = :(function __init__() 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)
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)
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)
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)
function modulecode(mod::ROSModule, rosrootmod::Module)
@debug("submodule: ", _fullname(mod))
modcode = Expr[]

Expand All @@ -325,7 +334,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))

Expand All @@ -340,20 +349,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)
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])
Copy link
Owner

Choose a reason for hiding this comment

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

Would it be better to use relative path syntax here? Could rosrootmod be something that using won't see?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually it looks like I ended up going with absolute path in #48 (the 0.7 PR), i.e.,

Expr(:using, Expr(:., fullname(rosrootmod)..., Symbol(m), :msg))

for which the 0.6 equivalent here would be

Expr(:using, fullname(rosrootmod)..., Symbol(m), :msg)

I feel like since rosrootmod is the actual module (i.e., not a String or something) the absolute path approach should be infallible (knock on wood). I'll make the appropriate change to this PR (and to the commit history contained in #48).

Copy link
Owner

Choose a reason for hiding this comment

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

Cool, that works.

imports
end
function _importexprs(mod::ROSSrvModule)
function _importexprs(mod::ROSSrvModule, rosrootmod::Module)
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

Expand Down Expand Up @@ -519,7 +528,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)
Expand Down
2 changes: 1 addition & 1 deletion test/rospy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
10 changes: 10 additions & 0 deletions test/typegeneration.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down