diff --git a/.travis.yml b/.travis.yml index 93c10e8..9d1f570 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,13 @@ language: julia julia: - - 0.6 + - 0.7 + - 1.0 - nightly sudo: required dist: trusty +env: + global: + - PYTHON=python before_install: - sudo apt-add-repository -y "deb http://packages.ros.org/ros/ubuntu trusty main" - wget https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -O - | sudo apt-key add - @@ -19,10 +23,18 @@ before_script: - python test/echonode.py & - sleep 5 after_success: - - julia -e 'cd(Pkg.dir("RobotOS")); Pkg.add("Coverage"); using Coverage; Coveralls.submit(Coveralls.process_folder())' - - julia -e 'Pkg.add("Documenter");' - - julia -e 'cd(Pkg.dir("RobotOS")); ENV["DOCUMENTER_DEBUG"]="true"; include(joinpath("docs", "make.jl"))' + - julia --project -e 'import Pkg; Pkg.add("Coverage"); using Coverage; Coveralls.submit(Coveralls.process_folder())' after_script: - killall roscore - killall python - sleep 5 +jobs: + include: + - stage: "Documentation" + julia: 1.0 + os: linux + script: + - julia --project=docs/ -e 'using Pkg; Pkg.instantiate(); + Pkg.add(PackageSpec(path=pwd()))' + - julia --project=docs/ docs/make.jl + after_success: skip diff --git a/REQUIRE b/REQUIRE index e2679aa..42c61d7 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,3 +1,2 @@ -julia 0.6 -PyCall 1.11.0 -Compat 0.34.0 +julia 0.7 +PyCall 1.17.1 diff --git a/docs/Project.toml b/docs/Project.toml new file mode 100644 index 0000000..53bc6f8 --- /dev/null +++ b/docs/Project.toml @@ -0,0 +1,5 @@ +[deps] +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" + +[compat] +Documenter = "~0.19" diff --git a/docs/make.jl b/docs/make.jl index 79221e8..a5cc33e 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -11,6 +11,6 @@ deploydocs( branch = "gh-pages", latest = "master", osname="linux", - julia="0.6", + julia="1.0", deps=Deps.pip("mkdocs"), ) diff --git a/docs/src/index.md b/docs/src/index.md index c752d1b..1fc7776 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -77,13 +77,12 @@ load-time latency (useful for real-life applications!) by generating the ROS typ 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()) + rostypegen(@__MODULE__) import .geometry_msgs.msg: Pose # ... diff --git a/src/RobotOS.jl b/src/RobotOS.jl index 05c3c2b..88f8c11 100644 --- a/src/RobotOS.jl +++ b/src/RobotOS.jl @@ -1,5 +1,3 @@ -isdefined(Base, :__precompile__) && __precompile__() - module RobotOS using PyCall @@ -9,6 +7,14 @@ const _py_sys = PyCall.PyNULL() const _py_ros_callbacks = PyCall.PyNULL() const __rospy__ = PyCall.PyNULL() +include("debug.jl") +include("time.jl") +include("gentypes.jl") +include("rospy.jl") +include("pubsub.jl") +include("services.jl") +include("callbacks.jl") + function __init__() #Put julia's ARGS into python's so remappings will work copy!(_py_sys, pyimport("sys")) @@ -16,7 +22,7 @@ function __init__() #Fill in empty PyObjects if ! (dirname(@__FILE__) in _py_sys["path"]) - unshift!(_py_sys["path"], dirname(@__FILE__)) + pushfirst!(_py_sys["path"], dirname(@__FILE__)) end copy!(_py_ros_callbacks, pyimport("ros_callbacks")) @@ -32,15 +38,7 @@ function __init__() end #Compile the callback notify function, see callbacks.jl - CB_NOTIFY_PTR[] = cfunction(_callback_notify, Cint, Tuple{Ptr{Void}}) + CB_NOTIFY_PTR[] = @cfunction(_callback_notify, Cint, (Ptr{Cvoid},)) end -include("debug.jl") -include("time.jl") -include("gentypes.jl") -include("rospy.jl") -include("pubsub.jl") -include("services.jl") -include("callbacks.jl") - end diff --git a/src/callbacks.jl b/src/callbacks.jl index 517d8ac..3cc5c86 100644 --- a/src/callbacks.jl +++ b/src/callbacks.jl @@ -1,12 +1,12 @@ #This function will run in a new python thread created by rospy. #No julia allocation allowed. -function _callback_notify(handle::Ptr{Void}) - ccall(:uv_async_send, Cint, (Ptr{Void},), handle) +function _callback_notify(handle::Ptr{Cvoid}) + ccall(:uv_async_send, Cint, (Ptr{Cvoid},), handle) end #The pointer to the compiled notify function. This can't be precompiled so it gets initialized in #the module __init__ function. -const CB_NOTIFY_PTR = Ref{Ptr{Void}}() +const CB_NOTIFY_PTR = Ref{Ptr{Cvoid}}() function _callback_async_loop(rosobj, cond) @debug("Spinning up callback loop...") diff --git a/src/gentypes.jl b/src/gentypes.jl index 469c174..16d959b 100644 --- a/src/gentypes.jl +++ b/src/gentypes.jl @@ -64,6 +64,10 @@ abstract type AbstractMsg end abstract type AbstractSrv end abstract type AbstractService end +_is_tuple_expr(input) = input isa Expr && input.head == :tuple +_is_colon_expr(input) = input isa Expr && input.head == :call && input.args[1] == :(:) +_is_dot_expr(input) = input isa Expr && input.head == :(.) + """ @rosimport @@ -81,13 +85,11 @@ macro rosimport(input) #Rearranges the expression into a RobotOS._rosimport call. Input comes in as a single package #qualified expression, or as a tuple expression where the first element is the same as the #single expression case. Most of the code is just error checking that the input takes that form. - @assert input.head in [:tuple, :(.), :(:)] "Improper @rosimport input" - if input.head == :tuple - @assert isa(input.args[1], Expr) "Improper @rosimport input" - @assert input.args[1].head == :(:) "First argument needs ':' following" - types = String[] + @assert _is_tuple_expr(input) || _is_colon_expr(input) || _is_dot_expr(input) "Improper @rosimport input" + if _is_tuple_expr(input) + @assert _is_colon_expr(input.args[1]) "Improper @rosimport input, first argument needs ':' following" pkg, ismsg, typ = _pkgtype_import(input.args[1]) - push!(types, typ) + types = String[typ] for t in input.args[2:end] @assert isa(t, Symbol) "Type name ($(string(t))) not a symbol" push!(types, string(t)) @@ -98,32 +100,29 @@ macro rosimport(input) return :(_rosimport($pkg, $ismsg, $typ)) end end - -_get_quote_value(input::QuoteNode) = input.value -_get_quote_value(input::Expr) = (@assert input.head == :quote; input.args[1]) - #Return the pkg and types strings for a single expression of form: # pkg.[msg|srv].type or pkg.[msg|srv]:type function _pkgtype_import(input::Expr) - @assert input.head in (:(.), :(:)) "Improper @rosimport input" - @assert isa(input.args[1], Expr) "Improper @rosimport input" - @assert input.args[1].head == :(.) "Improper @rosimport input" - p = input.args[1].args[1] + @assert _is_colon_expr(input) || _is_dot_expr(input) "Improper @rosimport input" + p_ms, t = _is_colon_expr(input) ? (input.args[2], input.args[3]) : (input.args[1], input.args[2]) + @assert _is_dot_expr(p_ms) "Improper @rosimport input" + p = p_ms.args[1] @assert isa(p, Symbol) "Package name ($(string(p))) not a symbol" - m_or_s = _get_quote_value(input.args[1].args[2]) + @assert isa(p_ms.args[2], QuoteNode) "Improper @rosimport input" + m_or_s = p_ms.args[2].value @assert m_or_s in (:msg,:srv) "Improper @rosimport input" ps = string(p) msb = m_or_s == :msg ts = "" - if isa(input.args[2], Symbol) - ts = string(input.args[2]) - elseif isa(input.args[2], Expr) - @assert length(input.args[2].args) == 1 "Type name not a symbol" - tsym = input.args[2].args[1] + if isa(t, Symbol) + ts = string(t) + elseif isa(t, Expr) + @assert length(t.args) == 1 "Type name ($(t)) not a symbol" + tsym = t.args[1] @assert isa(tsym, Symbol) "Type name ($(string(tsym))) not a symbol" ts = string(tsym) - elseif isa(input.args[2], QuoteNode) - tsym = input.args[2].value + elseif isa(t, QuoteNode) + tsym = t.value @assert isa(tsym, Symbol) "Type name ($(string(tsym))) not a symbol" ts = string(tsym) end @@ -179,8 +178,8 @@ function addtype!(mod::ROSMsgModule, typ::String) if !(typ in mod.members) @debug("Message type import: ", _fullname(mod), ".", typ) if _nameconflicts(typ) - warn("Message type '$typ' conflicts with Julia builtin, ", - "will be imported as '$(_jl_safe_name(typ,"Msg"))'") + @warn("Message type '$typ' conflicts with Julia builtin, " * + "will be imported as '$(_jl_safe_name(typ,"Msg"))'") end pymod, pyobj = _pyvars(_fullname(mod), typ) @@ -350,19 +349,19 @@ end #The imports specific to each module, including dependant packages function _importexprs(mod::ROSMsgModule, rosrootmod::Module) - imports = Expr[Expr(:import, :RobotOS, :AbstractMsg)] + imports = Expr[:(import RobotOS.AbstractMsg)] othermods = filter(d -> d != _name(mod), mod.deps) - append!(imports, [Expr(:using,fullname(rosrootmod)...,Symbol(m),:msg) for m in othermods]) + append!(imports, [Expr(:using, Expr(:., fullname(rosrootmod)..., Symbol(m), :msg)) for m in othermods]) imports end 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), + :(import RobotOS.AbstractSrv), + :(import RobotOS.AbstractService), + :(import RobotOS._srv_reqtype), + :(import RobotOS._srv_resptype) ] - append!(imports, [Expr(:using,fullname(rosrootmod)...,Symbol(m),:msg) for m in mod.deps]) + append!(imports, [Expr(:using, Expr(:., fullname(rosrootmod)..., Symbol(m), :msg)) for m in mod.deps]) imports end @@ -513,8 +512,8 @@ function _addtypemember!(exprs, namestr, typestr) jlconargs = exprs[4].args[2].args if typestr == "char" || typestr == "byte" - warn("Use of type '$typestr' is deprecated in message definitions, ", - "use '$(lowercase(string(_ros_builtin_types[typestr])))' instead.") + @warn("Use of type '$typestr' is deprecated in message definitions, " * + "use '$(lowercase(string(_ros_builtin_types[typestr])))' instead.") end typestr, arraylen = _check_array_type(typestr) @@ -609,7 +608,7 @@ function _splittypestr(typestr::String) end #Valid ROS type string is all word chars split by a single forward slash, with #optional square brackets for array types -_isrostype(s::String) = ismatch(r"^\w+/\w+(?:\[\d*\])?$", s) +_isrostype(s::String) = occursin(r"^\w+/\w+(?:\[\d*\])?$", s) #Sanitize a string by checking for and removing brackets if they are present #Return the sanitized type and the number inside the brackets if it is a fixed diff --git a/test/pubsub.jl b/test/pubsub.jl index 13c0c72..79089ca 100644 --- a/test/pubsub.jl +++ b/test/pubsub.jl @@ -7,7 +7,7 @@ using .geometry_msgs.msg const Nmsgs = 10 const rate = 20. #Hz const msgs = PoseStamped[] -const refs = Array{Vector3}(Nmsgs) +const refs = Array{Vector3}(undef, Nmsgs) const t0 = to_nsec(get_rostime()) for i=1:Nmsgs diff --git a/test/runtests.jl b/test/runtests.jl index 9363d2e..a1fa188 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -using Compat.Test +using Test using PyCall using RobotOS RobotOS.debug(true) diff --git a/test/typegeneration.jl b/test/typegeneration.jl index 77a35ec..68d60fa 100644 --- a/test/typegeneration.jl +++ b/test/typegeneration.jl @@ -32,7 +32,7 @@ rostypegen() module TestModule using RobotOS @rosimport std_msgs.msg: Float32 - rostypegen(current_module()) + rostypegen(@__MODULE__) end @test !isdefined(std_msgs.msg, :Float32Msg) @test isdefined(TestModule, :std_msgs) @@ -84,7 +84,7 @@ path2 = convert(nav_msgs.msg.Path, pypath) #Issue #6 - Empty message emptymsg = std_msgs.msg.Empty() -@test length(fieldnames(emptymsg)) == 0 +@test length(fieldnames(typeof(emptymsg))) == 0 #Issue #7/8 - Renaming conflicting message types @test isdefined(std_msgs.msg, :Float64Msg)