记录一下自己从px4到板载控制的一系列流程以及踩坑过程,希望也可以帮助大家避雷。话不多说直接上干货。
一、软、硬件平台
px4版本:1.10.1
飞控板:pixhawk4
ros版本:melodic
linux版本:ubuntu18.04.05
二、px4下载、编译、环境配置
推荐直接到官网下载,一次不成功就多试几次,也可以换github的国内镜像站:
https://github.com.cnpmjs.org
https://hub.fastgit.org
下载完成之后在linux下配置环境:
1.先将串口加入到用户组,增加串口权限
sudo usermod -a -G dialout $USER
2.首先运行firmware/tools/setup/ubuntu.sh安装需要的依赖,找一个好一点的网络一次成功,有配置不成功的地方之后再说。
然后这其中可能出现的问题就是交叉编译器有问题,推荐2017-q4版本的编译器,gcc-arm-none-eabi-7-2017-q4-major,,这里有链接:
1.https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
2.tar -jxf gcc-arm-none-eabi-7-2017-q4-major-linux.tar.bz2//解压
3.gedit .bashrc
#在最后一行加入下列代码。注意:gcc-arm-none-eabi-7-2017-q4-major替换成你的
4.export PATH=$HOME/gcc-arm-none-eabi-7-2017-q4-major/bin:$PATH
#查看版本是否成功
5.echo $PATH
6.arm-none-eabi-gcc --version
用ROS(melodic)的话建议python2,这样不会冲突。
3.这两个关键步骤做完之后,直接进firmware目录编译固件,然后就会报错,这个时候缺啥补啥就完事啦!
编译
make px4_fmu-v5_default
下载
make px4_fmu-v5_default upload
三、mavros安装(这里推荐使用源码方式安装,因为后续要是修改mavros源码包的话方便修改)
1.我把mavros工作空间放在了home下,打开终端,执行下面命令,会逐级新建文件夹
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws
catkin init
wstool init src
2.安装必要的工具链,加-y目的是为了省略之后要一直输入y来确认安装的步骤
sudo apt-get install python-catkin-tools python-rosinstall-generator -y
3初始化工作空间
wstool init ~/catkin_ws/src
4.安装mavlink,4 5步骤进到src里边执行
rosinstall_generator --rosdistro melodic mavlink | tee /tmp/mavros.rosinstall
这时候可能会出现(这里引用了别人的图片)
这样的意思就是告诉你新的版本是多少,并没有完成安装。我们可以先忽略这个进行下一步。
5.安装mavros
rosinstall_generator --upstream-development mavros | tee -a /tmp/mavros.rosinstall
如果下载不成功。可以进行换镜像站操作,打开src目录下的.rosinstall文件,将github.com更换为hub.fastgit.org,然后重新执行命令
6.创建工作空间以及依赖,注意回退到上级目录执行,即catkin_ws目录下
wstool merge -t src /tmp/mavros.rosinstall
wstool update -t src -j4
rosdep install --from-paths src --ignore-src -y
注意:这里有个大坑,在进行上面两步之后如果直接执行rosdep install这行代码,很大程度上是运行不成功的,因为rosdep并没有进行更新,要执行rosdep update,但是在国内的话rosdep update基本是运行不成功的,因为rosdep update的网址在国外,这里有如下解决办法
找到这几个文件:为了兼容melodic版本,我用的是python2.7,所以这三个文件在这个路径下,每个人情况不同,具体路径视情况而定,找到之后分别按以下的代码进行替换,如果有版本不一样的,可以对照进行修改,主要是在网址前加了代理
sudo gedit /usr/lib/python2.7/dist-packages/rosdep2/rep3.py
sudo gedit /usr/lib/python2.7/dist-packages/rosdep2/sources_list.py
sudo gedit /usr/lib/python2.7/dist-packages/rosdep2/gbpdistro_suppor
# Copyright (c) 2012, Willow Garage, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Willow Garage, Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
try:
from urllib.request import urlopen
except ImportError:
from urllib2 import urlopen
import yaml
import warnings
from .core import DownloadFailure
from .rosdistrohelper import PreRep137Warning
# location of targets file for processing gbpdistro files
REP3_TARGETS_URL = 'https://ghproxy.com/https://raw.githubusercontent.com/ros/rosdistro/master/releases/targets.yaml'
# seconds to wait before aborting download of gbpdistro data
DOWNLOAD_TIMEOUT = 15.0
def download_targets_data(targets_url=None):
"""
Download REP 3 targets file and unmarshal from YAML.
DEPRECATED: this function is deprecated. List of targets should be obtained
from the rosdistro module.
The body of this function is an example.
:param target_url: override URL of platform targets file. Defaults
to ``REP3_TARGETS_URL``.
:raises: :exc:`DownloadFailure`
:raises: :exc:`InvalidData` If targets file does not pass cursory validation checks.
"""
warnings.warn('deprecated, use rosdistro instead', PreRep137Warning)
if targets_url is None:
targets_url = REP3_TARGETS_URL
try:
f = urlopen(targets_url, timeout=DOWNLOAD_TIMEOUT)
text = f.read()
f.close()
targets_data = yaml.safe_load(text)
except Exception as e:
raise DownloadFailure('Failed to download target platform data for gbpdistro:\n\t%s' % (str(e)))
if type(targets_data) == list:
# convert to dictionary
new_targets_data = {}
for t in targets_data:
platform = list(t.keys())[0]
new_targets_data[platform] = t[platform]
targets_data = new_targets_data
return targets_data
# Copyright (c) 2012, Willow Garage, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Willow Garage, Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# Author Ken Conley/[email protected]
from __future__ import print_function
import os
import sys
import yaml
try:
from urllib.request import urlopen
from urllib.error import URLError
import urllib.request as request
except ImportError:
from urllib2 import urlopen
from urllib2 import URLError
import urllib2 as request
try:
import cPickle as pickle
except ImportError:
import pickle
from .cache_tools import compute_filename_hash, PICKLE_CACHE_EXT, write_atomic, write_cache_file
from .core import InvalidData, DownloadFailure, CachePermissionError
from .gbpdistro_support import get_gbprepo_as_rosdep_data, download_gbpdistro_as_rosdep_data
from .meta import MetaDatabase
from ._version import __version__
try:
import urlparse
except ImportError:
import urllib.parse as urlparse # py3k
try:
import httplib
except ImportError:
import http.client as httplib # py3k
import rospkg
import rospkg.distro
from .loader import RosdepLoader
from .rosdistrohelper import get_index, get_index_url
# default file to download with 'init' command in order to bootstrap
# rosdep
DEFAULT_SOURCES_LIST_URL = 'https://ghproxy.com/https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/sources.list.d/20-default.list'
# seconds to wait before aborting download of rosdep data
DOWNLOAD_TIMEOUT = 15.0
SOURCES_LIST_DIR = 'sources.list.d'
SOURCES_CACHE_DIR = 'sources.cache'
# name of index file for sources cache
CACHE_INDEX = 'index'
# extension for binary cache
SOURCE_PATH_ENV = 'ROSDEP_SOURCE_PATH'
def get_sources_list_dirs(source_list_dir):
if SOURCE_PATH_ENV in os.environ:
sdirs = os.environ[SOURCE_PATH_ENV].split(os.pathsep)
else:
sdirs = [source_list_dir]
for p in list(sdirs):
if not os.path.exists(p):
sdirs.remove(p)
return sdirs
def get_sources_list_dir():
# base of where we read config files from
# TODO: windows
if 0:
# we can't use etc/ros because environment config does not carry over under sudo
etc_ros = rospkg.get_etc_ros_dir()
else:
etc_ros = '/etc/ros'
# compute default system wide sources directory
sys_sources_list_dir = os.path.join(etc_ros, 'rosdep', SOURCES_LIST_DIR)
sources_list_dirs = get_sources_list_dirs(sys_sources_list_dir)
if sources_list_dirs:
return sources_list_dirs[0]
else:
return sys_sources_list_dir
def get_default_sources_list_file():
return os.path.join(get_sources_list_dir(), '20-default.list')
def get_sources_cache_dir():
ros_home = rospkg.get_ros_home()
return os.path.join(ros_home, 'rosdep', SOURCES_CACHE_DIR)
# Default rosdep.yaml format. For now this is the only valid type and
# is specified for future compatibility.
TYPE_YAML = 'yaml'
# git-buildpackage repo list
TYPE_GBPDISTRO = 'gbpdistro'
VALID_TYPES = [TYPE_YAML, TYPE_GBPDISTRO]
class DataSource(object):
def __init__(self, type_, url, tags, origin=None):
"""
:param type_: data source type, e.g. TYPE_YAML, TYPE_GBPDISTRO
:param url: URL of data location. For file resources, must
start with the file:// scheme. For remote resources, URL
must include a path.
:param tags: tags for matching data source to configurations
:param origin: filename or other indicator of where data came from for debugging.
:raises: :exc:`ValueError` if parameters do not validate
"""
# validate inputs
if type_ not in VALID_TYPES:
raise ValueError('type must be one of [%s]' % (','.join(VALID_TYPES)))
parsed = urlparse.urlparse(url)
if not parsed.scheme or (parsed.scheme != 'file' and not parsed.netloc) or parsed.path in ('', '/'):
raise ValueError('url must be a fully-specified URL with scheme, hostname, and path: %s' % (str(url)))
if not type(tags) == list:
raise ValueError('tags must be a list: %s' % (str(tags)))
self.type = type_
self.tags = tags
self.url = url
self.origin = origin
def __eq__(self, other):
return isinstance(other, DataSource) and \
self.type == other.type and \
self.tags == other.tags and \
self.url == other.url and \
self.origin == other.origin
def __str__(self):
if self.origin:
return '[%s]:\n%s %s %s' % (self.origin, self.type, self.url, ' '.join(self.tags))
else:
return '%s %s %s' % (self.type, self.url, ' '.join(self.tags))
def __repr__(self):
return repr((self.type, self.url, self.tags, self.origin))
class RosDistroSource(DataSource):
def __init__(self, distro):
self.type = TYPE_GBPDISTRO
self.tags = [distro]
# In this case self.url is a list if REP-143 is being used
self.url = get_index().distributions[distro]['distribution']
self.origin = None
# create function we can pass in as model to parse_source_data. The
# function emulates the CachedDataSource constructor but does the
# necessary full filepath calculation and loading of data.
def cache_data_source_loader(sources_cache_dir, verbose=False):
def create_model(type_, uri, tags, origin=None):
# compute the filename has from the URL
filename = compute_filename_hash(uri)
filepath = os.path.join(sources_cache_dir, filename)
pickle_filepath = filepath + PICKLE_CACHE_EXT
if os.path.exists(pickle_filepath):
if verbose:
print('loading cached data source:\n\t%s\n\t%s' % (uri, pickle_filepath), file=sys.stderr)
with open(pickle_filepath, 'rb') as f:
rosdep_data = pickle.loads(f.read())
elif os.path.exists(filepath):
if verbose:
print('loading cached data source:\n\t%s\n\t%s' % (uri, filepath), file=sys.stderr)
with open(filepath) as f:
rosdep_data = yaml.safe_load(f.read())
else:
rosdep_data = {}
return CachedDataSource(type_, uri, tags, rosdep_data, origin=filepath)
return create_model
class CachedDataSource(object):
def __init__(self, type_, url, tags, rosdep_data, origin=None):
"""
Stores data source and loaded rosdep data for that source.
NOTE: this is not a subclass of DataSource, though it's API is
duck-type compatible with the DataSource API.
"""
self.source = DataSource(type_, url, tags, origin=origin)
self.rosdep_data = rosdep_data
def __eq__(self, other):
try:
return self.source == other.source and \
self.rosdep_data == other.rosdep_data
except AttributeError:
return False
def __str__(self):
return '%s\n%s' % (self.source, self.rosdep_data)
def __repr__(self):
return repr((self.type, self.url, self.tags, self.rosdep_data, self.origin))
@property
def type(self):
"""
:returns: data source type
"""
return self.source.type
@property
def url(self):
"""
:returns: data source URL
"""
return self.source.url
@property
def tags(self):
"""
:returns: data source tags
"""
return self.source.tags
@property
def origin(self):
"""
:returns: data source origin, if set, or ``None``
"""
return self.source.origin
class DataSourceMatcher(object):
def __init__(self, tags):
self.tags = tags
def matches(self, rosdep_data_source):
"""
Check if the datasource matches this configuration.
:param rosdep_data_source: :class:`DataSource`
"""
# all of the rosdep_data_source tags must be in our matcher tags
return not any(set(rosdep_data_source.tags) - set(self.tags))
@staticmethod
def create_default(os_override=None):
"""
Create a :class:`DataSourceMatcher` to match the current
configuration.
:param os_override: (os_name, os_codename) tuple to override
OS detection
:returns: :class:`DataSourceMatcher`
"""
distro_name = rospkg.distro.current_distro_codename()
if os_override is None:
os_detect = rospkg.os_detect.OsDetect()
os_name, os_version, os_codename = os_detect.detect_os()
else:
os_name, os_codename = os_override
tags = [t for t in (distro_name, os_name, os_codename) if t]
return DataSourceMatcher(tags)
def download_rosdep_data(url):
"""
:raises: :exc:`DownloadFailure` If data cannot be
retrieved (e.g. 404, bad YAML format, server down).
"""
try:
# http/https URLs need custom requests to specify the user-agent, since some repositories reject
# requests from the default user-agent.
url="https://ghproxy.com/"+url
if url.startswith("http://") or url.startswith("https://"):
url_request = request.Request(url, headers={'User-Agent': 'rosdep/{version}'.format(version=__version__)})
else:
url_request = url
f = urlopen(url_request, timeout=DOWNLOAD_TIMEOUT)
text = f.read()
f.close()
data = yaml.safe_load(text)
if type(data) != dict:
raise DownloadFailure('rosdep data from [%s] is not a YAML dictionary' % (url))
return data
except (URLError, httplib.HTTPException) as e:
raise DownloadFailure(str(e) + ' (%s)' % url)
except yaml.YAMLError as e:
raise DownloadFailure(str(e))
def download_default_sources_list(url=DEFAULT_SOURCES_LIST_URL):
"""
Download (and validate) contents of default sources list.
:param url: override URL of default sources list file
:return: raw sources list data, ``str``
:raises: :exc:`DownloadFailure` If data cannot be
retrieved (e.g. 404, bad YAML format, server down).
:raises: :exc:`urllib2.URLError` If data cannot be
retrieved (e.g. 404, server down).
"""
try:
f = urlopen(url, timeout=DOWNLOAD_TIMEOUT)
except (URLError, httplib.HTTPException) as e:
raise URLError(str(e) + ' (%s)' % url)
data = f.read().decode()
f.close()
if not data:
raise DownloadFailure('cannot download defaults file from %s : empty contents' % url)
# parse just for validation
try:
parse_sources_data(data)
except InvalidData as e:
raise DownloadFailure(
'The content downloaded from %s failed to pass validation.'
' It is likely that the source is invalid unless the data was corrupted during the download.'
' The contents were:{{{%s}}} The error raised was: %s' % (url, data, e))
return data
def parse_sources_data(data, origin='', model=None):
"""
Parse sources file format (tags optional)::
# comments and empty lines allowed
[tags]
e.g.::
yaml http://foo/rosdep.yaml fuerte lucid ubuntu
If tags are specified, *all* tags must match the current
configuration for the sources data to be used.
:param data: data in sources file format
:param model: model to load data into. Defaults to :class:`DataSource`
:returns: List of data sources, [:class:`DataSource`]
:raises: :exc:`InvalidData`
"""
if model is None:
model = DataSource
sources = []
for line in data.split('\n'):
line = line.strip()
# ignore empty lines or comments
if not line or line.startswith('#'):
continue
splits = line.split(' ')
if len(splits) < 2:
raise InvalidData('invalid line:\n%s' % (line), origin=origin)
type_ = splits[0]
url = splits[1]
tags = splits[2:]
try:
sources.append(model(type_, url, tags, origin=origin))
except ValueError as e:
raise InvalidData('line:\n\t%s\n%s' % (line, e), origin=origin)
return sources
def parse_sources_file(filepath):
"""
Parse file on disk
:returns: List of data sources, [:class:`DataSource`]
:raises: :exc:`InvalidData` If any error occurs reading
file, so an I/O error, non-existent file, or invalid format.
"""
try:
with open(filepath, 'r') as f:
return parse_sources_data(f.read(), origin=filepath)
except IOError as e:
raise InvalidData('I/O error reading sources file: %s' % (str(e)), origin=filepath)
def parse_sources_list(sources_list_dir=None):
"""
Parse data stored in on-disk sources list directory into a list of
:class:`DataSource` for processing.
:returns: List of data sources, [:class:`DataSource`]. If there is
no sources list dir, this returns an empty list.
:raises: :exc:`InvalidData`
:raises: :exc:`OSError` if *sources_list_dir* cannot be read.
:raises: :exc:`IOError` if *sources_list_dir* cannot be read.
"""
if sources_list_dir is None:
sources_list_dir = get_sources_list_dir()
sources_list_dirs = get_sources_list_dirs(sources_list_dir)
filelist = []
for sdir in sources_list_dirs:
filelist += sorted([os.path.join(sdir, f) for f in os.listdir(sdir) if f.endswith('.list')])
sources_list = []
for f in filelist:
sources_list.extend(parse_sources_file(f))
return sources_list
def _generate_key_from_urls(urls):
# urls may be a list of urls or a single string
try:
assert isinstance(urls, (list, basestring))
except NameError:
assert isinstance(urls, (list, str))
# We join the urls by the '^' character because it is not allowed in urls
return '^'.join(urls if isinstance(urls, list) else [urls])
def update_sources_list(sources_list_dir=None, sources_cache_dir=None,
success_handler=None, error_handler=None,
skip_eol_distros=False, ros_distro=None):
"""
Re-downloaded data from remote sources and store in cache. Also
update the cache index based on current sources.
:param sources_list_dir: override source list directory
:param sources_cache_dir: override sources cache directory
:param success_handler: fn(DataSource) to call if a particular
source loads successfully. This hook is mainly for printing
errors to console.
:param error_handler: fn(DataSource, DownloadFailure) to call
if a particular source fails. This hook is mainly for
printing errors to console.
:param skip_eol_distros: skip downloading sources for EOL distros
:returns: list of (`DataSource`, cache_file_path) pairs for cache
files that were updated, ``[str]``
:raises: :exc:`InvalidData` If any of the sources list files is invalid
:raises: :exc:`OSError` if *sources_list_dir* cannot be read.
:raises: :exc:`IOError` If *sources_list_dir* cannot be read or cache data cannot be written
"""
if sources_cache_dir is None:
sources_cache_dir = get_sources_cache_dir()
sources = parse_sources_list(sources_list_dir=sources_list_dir)
retval = []
for source in list(sources):
try:
if source.type == TYPE_YAML:
rosdep_data = download_rosdep_data(source.url)
elif source.type == TYPE_GBPDISTRO: # DEPRECATED, do not use this file. See REP137
if not source.tags[0] in ['electric', 'fuerte']:
print('Ignore legacy gbpdistro "%s"' % source.tags[0])
sources.remove(source)
continue # do not store this entry in the cache
rosdep_data = download_gbpdistro_as_rosdep_data(source.url)
retval.append((source, write_cache_file(sources_cache_dir, source.url, rosdep_data)))
if success_handler is not None:
success_handler(source)
except DownloadFailure as e:
if error_handler is not None:
error_handler(source, e)
# Additional sources for ros distros
# In compliance with REP137 and REP143
python_versions = {}
print('Query rosdistro index %s' % get_index_url())
distribution_names = get_index().distributions.keys()
if ros_distro is not None and ros_distro not in distribution_names:
raise ValueError(
'Requested distribution "%s" is not in the index.' % ros_distro)
for dist_name in sorted(distribution_names):
distribution = get_index().distributions[dist_name]
if dist_name != ros_distro:
if ros_distro is not None:
print('Skip distro "%s" different from requested "%s"' % (dist_name, ros_distro))
continue
if skip_eol_distros:
if distribution.get('distribution_status') == 'end-of-life':
print('Skip end-of-life distro "%s"' % dist_name)
continue
print('Add distro "%s"' % dist_name)
rds = RosDistroSource(dist_name)
rosdep_data = get_gbprepo_as_rosdep_data(dist_name)
# Store Python version from REP153
if distribution.get('python_version'):
python_versions[dist_name] = distribution.get('python_version')
# dist_files can either be a string (single filename) or a list (list of filenames)
dist_files = distribution['distribution']
key = _generate_key_from_urls(dist_files)
retval.append((rds, write_cache_file(sources_cache_dir, key, rosdep_data)))
sources.append(rds)
# cache metadata that isn't a source list
MetaDatabase().set('ROS_PYTHON_VERSION', python_versions)
# Create a combined index of *all* the sources. We do all the
# sources regardless of failures because a cache from a previous
# attempt may still exist. We have to do this cache index so that
# loads() see consistent data.
if not os.path.exists(sources_cache_dir):
os.makedirs(sources_cache_dir)
cache_index = os.path.join(sources_cache_dir, CACHE_INDEX)
data = "#autogenerated by rosdep, do not edit. use 'rosdep update' instead\n"
for source in sources:
url = _generate_key_from_urls(source.url)
data += 'yaml %s %s\n' % (url, ' '.join(source.tags))
write_atomic(cache_index, data)
# mainly for debugging and testing
return retval
def load_cached_sources_list(sources_cache_dir=None, verbose=False):
"""
Load cached data based on the sources list.
:returns: list of :class:`CachedDataSource` instance with raw
rosdep data loaded.
:raises: :exc:`OSError` if cache cannot be read
:raises: :exc:`IOError` if cache cannot be read
"""
if sources_cache_dir is None:
sources_cache_dir = get_sources_cache_dir()
cache_index = os.path.join(sources_cache_dir, 'index')
if not os.path.exists(cache_index):
if verbose:
print('no cache index present, not loading cached sources', file=sys.stderr)
return []
try:
with open(cache_index, 'r') as f:
cache_data = f.read()
except IOError as e:
if e.strerror == 'Permission denied':
raise CachePermissionError('Failed to write cache file: ' + str(e))
else:
raise
# the loader does all the work
model = cache_data_source_loader(sources_cache_dir, verbose=verbose)
return parse_sources_data(cache_data, origin=cache_index, model=model)
class SourcesListLoader(RosdepLoader):
"""
SourcesList loader implements the general RosdepLoader API. This
implementation is fairly simple as there is only one view the
source list loader can create. It is also a bit degenerate as it
is not capable of mapping resource names to views, thus any
resource-name-based API fails or returns nothing interesting.
This loader should not be used directly; instead, it is more
useful composed with other higher-level implementations, like the
:class:`rosdep2.rospkg_loader.RospkgLoader`. The general intent
is to compose it with another loader by making all of the other
loader's views depends on all the views in this loader.
"""
ALL_VIEW_KEY = 'sources.list'
def __init__(self, sources):
"""
:param sources: cached sources list entries, [:class:`CachedDataSource`]
"""
self.sources = sources
@staticmethod
def create_default(matcher=None, sources_cache_dir=None, os_override=None, verbose=False):
"""
:param matcher: override DataSourceMatcher. Defaults to
DataSourceMatcher.create_default().
:param sources_cache_dir: override location of sources cache
"""
if matcher is None:
matcher = DataSourceMatcher.create_default(os_override=os_override)
if verbose:
print('using matcher with tags [%s]' % (', '.join(matcher.tags)), file=sys.stderr)
sources = load_cached_sources_list(sources_cache_dir=sources_cache_dir, verbose=verbose)
if verbose:
print('loaded %s sources' % (len(sources)), file=sys.stderr)
sources = [x for x in sources if matcher.matches(x)]
if verbose:
print('%s sources match current tags' % (len(sources)), file=sys.stderr)
return SourcesListLoader(sources)
def load_view(self, view_name, rosdep_db, verbose=False):
"""
Load view data into rosdep_db. If the view has already been
loaded into rosdep_db, this method does nothing.
:param view_name: name of ROS stack to load, ``str``
:param rosdep_db: database to load stack data into, :class:`RosdepDatabase`
:raises: :exc:`InvalidData`
"""
if rosdep_db.is_loaded(view_name):
return
source = self.get_source(view_name)
if verbose:
print('loading view [%s] with sources.list loader' % (view_name), file=sys.stderr)
view_dependencies = self.get_view_dependencies(view_name)
rosdep_db.set_view_data(view_name, source.rosdep_data, view_dependencies, view_name)
def get_loadable_resources(self):
return []
def get_loadable_views(self):
return [x.url for x in self.sources]
def get_view_dependencies(self, view_name):
# use dependencies to implement precedence
if view_name != SourcesListLoader.ALL_VIEW_KEY:
# if the view_name matches one of our sources, return
# empty list as none of our sources has deps.
if any([x for x in self.sources if view_name == x.url]):
return []
# not one of our views, so it depends on everything we provide
return [x.url for x in self.sources]
def get_source(self, view_name):
matches = [x for x in self.sources if x.url == view_name]
if matches:
return matches[0]
else:
raise rospkg.ResourceNotFound(view_name)
def get_rosdeps(self, resource_name, implicit=True):
"""
Always raises as SourceListLoader defines no concrete resources with rosdeps.
:raises: :exc:`rospkg.ResourceNotFound`
"""
raise rospkg.ResourceNotFound(resource_name)
def get_view_key(self, resource_name):
"""
Always raises as SourceListLoader defines no concrete resources with rosdeps.
:returns: Name of view that *resource_name* is in, ``None`` if no associated view.
:raises: :exc:`rospkg.ResourceNotFound` if *resource_name* cannot be found.
"""
raise rospkg.ResourceNotFound(resource_name)
# Copyright (c) 2012, Willow Garage, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Willow Garage, Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# Author Ken Conley/[email protected]
from __future__ import print_function
import os
import sys
import yaml
try:
from urllib.request import urlopen
from urllib.error import URLError
import urllib.request as request
except ImportError:
from urllib2 import urlopen
from urllib2 import URLError
import urllib2 as request
try:
import cPickle as pickle
except ImportError:
import pickle
from .cache_tools import compute_filename_hash, PICKLE_CACHE_EXT, write_atomic, write_cache_file
from .core import InvalidData, DownloadFailure, CachePermissionError
from .gbpdistro_support import get_gbprepo_as_rosdep_data, download_gbpdistro_as_rosdep_data
from .meta import MetaDatabase
from ._version import __version__
try:
import urlparse
except ImportError:
import urllib.parse as urlparse # py3k
try:
import httplib
except ImportError:
import http.client as httplib # py3k
import rospkg
import rospkg.distro
from .loader import RosdepLoader
from .rosdistrohelper import get_index, get_index_url
# default file to download with 'init' command in order to bootstrap
# rosdep
DEFAULT_SOURCES_LIST_URL = 'https://ghproxy.com/https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/sources.list.d/20-default.list'
# seconds to wait before aborting download of rosdep data
DOWNLOAD_TIMEOUT = 15.0
SOURCES_LIST_DIR = 'sources.list.d'
SOURCES_CACHE_DIR = 'sources.cache'
# name of index file for sources cache
CACHE_INDEX = 'index'
# extension for binary cache
SOURCE_PATH_ENV = 'ROSDEP_SOURCE_PATH'
def get_sources_list_dirs(source_list_dir):
if SOURCE_PATH_ENV in os.environ:
sdirs = os.environ[SOURCE_PATH_ENV].split(os.pathsep)
else:
sdirs = [source_list_dir]
for p in list(sdirs):
if not os.path.exists(p):
sdirs.remove(p)
return sdirs
def get_sources_list_dir():
# base of where we read config files from
# TODO: windows
if 0:
# we can't use etc/ros because environment config does not carry over under sudo
etc_ros = rospkg.get_etc_ros_dir()
else:
etc_ros = '/etc/ros'
# compute default system wide sources directory
sys_sources_list_dir = os.path.join(etc_ros, 'rosdep', SOURCES_LIST_DIR)
sources_list_dirs = get_sources_list_dirs(sys_sources_list_dir)
if sources_list_dirs:
return sources_list_dirs[0]
else:
return sys_sources_list_dir
def get_default_sources_list_file():
return os.path.join(get_sources_list_dir(), '20-default.list')
def get_sources_cache_dir():
ros_home = rospkg.get_ros_home()
return os.path.join(ros_home, 'rosdep', SOURCES_CACHE_DIR)
# Default rosdep.yaml format. For now this is the only valid type and
# is specified for future compatibility.
TYPE_YAML = 'yaml'
# git-buildpackage repo list
TYPE_GBPDISTRO = 'gbpdistro'
VALID_TYPES = [TYPE_YAML, TYPE_GBPDISTRO]
class DataSource(object):
def __init__(self, type_, url, tags, origin=None):
"""
:param type_: data source type, e.g. TYPE_YAML, TYPE_GBPDISTRO
:param url: URL of data location. For file resources, must
start with the file:// scheme. For remote resources, URL
must include a path.
:param tags: tags for matching data source to configurations
:param origin: filename or other indicator of where data came from for debugging.
:raises: :exc:`ValueError` if parameters do not validate
"""
# validate inputs
if type_ not in VALID_TYPES:
raise ValueError('type must be one of [%s]' % (','.join(VALID_TYPES)))
parsed = urlparse.urlparse(url)
if not parsed.scheme or (parsed.scheme != 'file' and not parsed.netloc) or parsed.path in ('', '/'):
raise ValueError('url must be a fully-specified URL with scheme, hostname, and path: %s' % (str(url)))
if not type(tags) == list:
raise ValueError('tags must be a list: %s' % (str(tags)))
self.type = type_
self.tags = tags
self.url = url
self.origin = origin
def __eq__(self, other):
return isinstance(other, DataSource) and \
self.type == other.type and \
self.tags == other.tags and \
self.url == other.url and \
self.origin == other.origin
def __str__(self):
if self.origin:
return '[%s]:\n%s %s %s' % (self.origin, self.type, self.url, ' '.join(self.tags))
else:
return '%s %s %s' % (self.type, self.url, ' '.join(self.tags))
def __repr__(self):
return repr((self.type, self.url, self.tags, self.origin))
class RosDistroSource(DataSource):
def __init__(self, distro):
self.type = TYPE_GBPDISTRO
self.tags = [distro]
# In this case self.url is a list if REP-143 is being used
self.url = get_index().distributions[distro]['distribution']
self.origin = None
# create function we can pass in as model to parse_source_data. The
# function emulates the CachedDataSource constructor but does the
# necessary full filepath calculation and loading of data.
def cache_data_source_loader(sources_cache_dir, verbose=False):
def create_model(type_, uri, tags, origin=None):
# compute the filename has from the URL
filename = compute_filename_hash(uri)
filepath = os.path.join(sources_cache_dir, filename)
pickle_filepath = filepath + PICKLE_CACHE_EXT
if os.path.exists(pickle_filepath):
if verbose:
print('loading cached data source:\n\t%s\n\t%s' % (uri, pickle_filepath), file=sys.stderr)
with open(pickle_filepath, 'rb') as f:
rosdep_data = pickle.loads(f.read())
elif os.path.exists(filepath):
if verbose:
print('loading cached data source:\n\t%s\n\t%s' % (uri, filepath), file=sys.stderr)
with open(filepath) as f:
rosdep_data = yaml.safe_load(f.read())
else:
rosdep_data = {}
return CachedDataSource(type_, uri, tags, rosdep_data, origin=filepath)
return create_model
class CachedDataSource(object):
def __init__(self, type_, url, tags, rosdep_data, origin=None):
"""
Stores data source and loaded rosdep data for that source.
NOTE: this is not a subclass of DataSource, though it's API is
duck-type compatible with the DataSource API.
"""
self.source = DataSource(type_, url, tags, origin=origin)
self.rosdep_data = rosdep_data
def __eq__(self, other):
try:
return self.source == other.source and \
self.rosdep_data == other.rosdep_data
except AttributeError:
return False
def __str__(self):
return '%s\n%s' % (self.source, self.rosdep_data)
def __repr__(self):
return repr((self.type, self.url, self.tags, self.rosdep_data, self.origin))
@property
def type(self):
"""
:returns: data source type
"""
return self.source.type
@property
def url(self):
"""
:returns: data source URL
"""
return self.source.url
@property
def tags(self):
"""
:returns: data source tags
"""
return self.source.tags
@property
def origin(self):
"""
:returns: data source origin, if set, or ``None``
"""
return self.source.origin
class DataSourceMatcher(object):
def __init__(self, tags):
self.tags = tags
def matches(self, rosdep_data_source):
"""
Check if the datasource matches this configuration.
:param rosdep_data_source: :class:`DataSource`
"""
# all of the rosdep_data_source tags must be in our matcher tags
return not any(set(rosdep_data_source.tags) - set(self.tags))
@staticmethod
def create_default(os_override=None):
"""
Create a :class:`DataSourceMatcher` to match the current
configuration.
:param os_override: (os_name, os_codename) tuple to override
OS detection
:returns: :class:`DataSourceMatcher`
"""
distro_name = rospkg.distro.current_distro_codename()
if os_override is None:
os_detect = rospkg.os_detect.OsDetect()
os_name, os_version, os_codename = os_detect.detect_os()
else:
os_name, os_codename = os_override
tags = [t for t in (distro_name, os_name, os_codename) if t]
return DataSourceMatcher(tags)
def download_rosdep_data(url):
"""
:raises: :exc:`DownloadFailure` If data cannot be
retrieved (e.g. 404, bad YAML format, server down).
"""
try:
# http/https URLs need custom requests to specify the user-agent, since some repositories reject
# requests from the default user-agent.
url="https://ghproxy.com/"+url
if url.startswith("http://") or url.startswith("https://"):
url_request = request.Request(url, headers={'User-Agent': 'rosdep/{version}'.format(version=__version__)})
else:
url_request = url
f = urlopen(url_request, timeout=DOWNLOAD_TIMEOUT)
text = f.read()
f.close()
data = yaml.safe_load(text)
if type(data) != dict:
raise DownloadFailure('rosdep data from [%s] is not a YAML dictionary' % (url))
return data
except (URLError, httplib.HTTPException) as e:
raise DownloadFailure(str(e) + ' (%s)' % url)
except yaml.YAMLError as e:
raise DownloadFailure(str(e))
def download_default_sources_list(url=DEFAULT_SOURCES_LIST_URL):
"""
Download (and validate) contents of default sources list.
:param url: override URL of default sources list file
:return: raw sources list data, ``str``
:raises: :exc:`DownloadFailure` If data cannot be
retrieved (e.g. 404, bad YAML format, server down).
:raises: :exc:`urllib2.URLError` If data cannot be
retrieved (e.g. 404, server down).
"""
try:
f = urlopen(url, timeout=DOWNLOAD_TIMEOUT)
except (URLError, httplib.HTTPException) as e:
raise URLError(str(e) + ' (%s)' % url)
data = f.read().decode()
f.close()
if not data:
raise DownloadFailure('cannot download defaults file from %s : empty contents' % url)
# parse just for validation
try:
parse_sources_data(data)
except InvalidData as e:
raise DownloadFailure(
'The content downloaded from %s failed to pass validation.'
' It is likely that the source is invalid unless the data was corrupted during the download.'
' The contents were:{{{%s}}} The error raised was: %s' % (url, data, e))
return data
def parse_sources_data(data, origin='', model=None):
"""
Parse sources file format (tags optional)::
# comments and empty lines allowed
[tags]
e.g.::
yaml http://foo/rosdep.yaml fuerte lucid ubuntu
If tags are specified, *all* tags must match the current
configuration for the sources data to be used.
:param data: data in sources file format
:param model: model to load data into. Defaults to :class:`DataSource`
:returns: List of data sources, [:class:`DataSource`]
:raises: :exc:`InvalidData`
"""
if model is None:
model = DataSource
sources = []
for line in data.split('\n'):
line = line.strip()
# ignore empty lines or comments
if not line or line.startswith('#'):
continue
splits = line.split(' ')
if len(splits) < 2:
raise InvalidData('invalid line:\n%s' % (line), origin=origin)
type_ = splits[0]
url = splits[1]
tags = splits[2:]
try:
sources.append(model(type_, url, tags, origin=origin))
except ValueError as e:
raise InvalidData('line:\n\t%s\n%s' % (line, e), origin=origin)
return sources
def parse_sources_file(filepath):
"""
Parse file on disk
:returns: List of data sources, [:class:`DataSource`]
:raises: :exc:`InvalidData` If any error occurs reading
file, so an I/O error, non-existent file, or invalid format.
"""
try:
with open(filepath, 'r') as f:
return parse_sources_data(f.read(), origin=filepath)
except IOError as e:
raise InvalidData('I/O error reading sources file: %s' % (str(e)), origin=filepath)
def parse_sources_list(sources_list_dir=None):
"""
Parse data stored in on-disk sources list directory into a list of
:class:`DataSource` for processing.
:returns: List of data sources, [:class:`DataSource`]. If there is
no sources list dir, this returns an empty list.
:raises: :exc:`InvalidData`
:raises: :exc:`OSError` if *sources_list_dir* cannot be read.
:raises: :exc:`IOError` if *sources_list_dir* cannot be read.
"""
if sources_list_dir is None:
sources_list_dir = get_sources_list_dir()
sources_list_dirs = get_sources_list_dirs(sources_list_dir)
filelist = []
for sdir in sources_list_dirs:
filelist += sorted([os.path.join(sdir, f) for f in os.listdir(sdir) if f.endswith('.list')])
sources_list = []
for f in filelist:
sources_list.extend(parse_sources_file(f))
return sources_list
def _generate_key_from_urls(urls):
# urls may be a list of urls or a single string
try:
assert isinstance(urls, (list, basestring))
except NameError:
assert isinstance(urls, (list, str))
# We join the urls by the '^' character because it is not allowed in urls
return '^'.join(urls if isinstance(urls, list) else [urls])
def update_sources_list(sources_list_dir=None, sources_cache_dir=None,
success_handler=None, error_handler=None,
skip_eol_distros=False, ros_distro=None):
"""
Re-downloaded data from remote sources and store in cache. Also
update the cache index based on current sources.
:param sources_list_dir: override source list directory
:param sources_cache_dir: override sources cache directory
:param success_handler: fn(DataSource) to call if a particular
source loads successfully. This hook is mainly for printing
errors to console.
:param error_handler: fn(DataSource, DownloadFailure) to call
if a particular source fails. This hook is mainly for
printing errors to console.
:param skip_eol_distros: skip downloading sources for EOL distros
:returns: list of (`DataSource`, cache_file_path) pairs for cache
files that were updated, ``[str]``
:raises: :exc:`InvalidData` If any of the sources list files is invalid
:raises: :exc:`OSError` if *sources_list_dir* cannot be read.
:raises: :exc:`IOError` If *sources_list_dir* cannot be read or cache data cannot be written
"""
if sources_cache_dir is None:
sources_cache_dir = get_sources_cache_dir()
sources = parse_sources_list(sources_list_dir=sources_list_dir)
retval = []
for source in list(sources):
try:
if source.type == TYPE_YAML:
rosdep_data = download_rosdep_data(source.url)
elif source.type == TYPE_GBPDISTRO: # DEPRECATED, do not use this file. See REP137
if not source.tags[0] in ['electric', 'fuerte']:
print('Ignore legacy gbpdistro "%s"' % source.tags[0])
sources.remove(source)
continue # do not store this entry in the cache
rosdep_data = download_gbpdistro_as_rosdep_data(source.url)
retval.append((source, write_cache_file(sources_cache_dir, source.url, rosdep_data)))
if success_handler is not None:
success_handler(source)
except DownloadFailure as e:
if error_handler is not None:
error_handler(source, e)
# Additional sources for ros distros
# In compliance with REP137 and REP143
python_versions = {}
print('Query rosdistro index %s' % get_index_url())
distribution_names = get_index().distributions.keys()
if ros_distro is not None and ros_distro not in distribution_names:
raise ValueError(
'Requested distribution "%s" is not in the index.' % ros_distro)
for dist_name in sorted(distribution_names):
distribution = get_index().distributions[dist_name]
if dist_name != ros_distro:
if ros_distro is not None:
print('Skip distro "%s" different from requested "%s"' % (dist_name, ros_distro))
continue
if skip_eol_distros:
if distribution.get('distribution_status') == 'end-of-life':
print('Skip end-of-life distro "%s"' % dist_name)
continue
print('Add distro "%s"' % dist_name)
rds = RosDistroSource(dist_name)
rosdep_data = get_gbprepo_as_rosdep_data(dist_name)
# Store Python version from REP153
if distribution.get('python_version'):
python_versions[dist_name] = distribution.get('python_version')
# dist_files can either be a string (single filename) or a list (list of filenames)
dist_files = distribution['distribution']
key = _generate_key_from_urls(dist_files)
retval.append((rds, write_cache_file(sources_cache_dir, key, rosdep_data)))
sources.append(rds)
# cache metadata that isn't a source list
MetaDatabase().set('ROS_PYTHON_VERSION', python_versions)
# Create a combined index of *all* the sources. We do all the
# sources regardless of failures because a cache from a previous
# attempt may still exist. We have to do this cache index so that
# loads() see consistent data.
if not os.path.exists(sources_cache_dir):
os.makedirs(sources_cache_dir)
cache_index = os.path.join(sources_cache_dir, CACHE_INDEX)
data = "#autogenerated by rosdep, do not edit. use 'rosdep update' instead\n"
for source in sources:
url = _generate_key_from_urls(source.url)
data += 'yaml %s %s\n' % (url, ' '.join(source.tags))
write_atomic(cache_index, data)
# mainly for debugging and testing
return retval
def load_cached_sources_list(sources_cache_dir=None, verbose=False):
"""
Load cached data based on the sources list.
:returns: list of :class:`CachedDataSource` instance with raw
rosdep data loaded.
:raises: :exc:`OSError` if cache cannot be read
:raises: :exc:`IOError` if cache cannot be read
"""
if sources_cache_dir is None:
sources_cache_dir = get_sources_cache_dir()
cache_index = os.path.join(sources_cache_dir, 'index')
if not os.path.exists(cache_index):
if verbose:
print('no cache index present, not loading cached sources', file=sys.stderr)
return []
try:
with open(cache_index, 'r') as f:
cache_data = f.read()
except IOError as e:
if e.strerror == 'Permission denied':
raise CachePermissionError('Failed to write cache file: ' + str(e))
else:
raise
# the loader does all the work
model = cache_data_source_loader(sources_cache_dir, verbose=verbose)
return parse_sources_data(cache_data, origin=cache_index, model=model)
class SourcesListLoader(RosdepLoader):
"""
SourcesList loader implements the general RosdepLoader API. This
implementation is fairly simple as there is only one view the
source list loader can create. It is also a bit degenerate as it
is not capable of mapping resource names to views, thus any
resource-name-based API fails or returns nothing interesting.
This loader should not be used directly; instead, it is more
useful composed with other higher-level implementations, like the
:class:`rosdep2.rospkg_loader.RospkgLoader`. The general intent
is to compose it with another loader by making all of the other
loader's views depends on all the views in this loader.
"""
ALL_VIEW_KEY = 'sources.list'
def __init__(self, sources):
"""
:param sources: cached sources list entries, [:class:`CachedDataSource`]
"""
self.sources = sources
@staticmethod
def create_default(matcher=None, sources_cache_dir=None, os_override=None, verbose=False):
"""
:param matcher: override DataSourceMatcher. Defaults to
DataSourceMatcher.create_default().
:param sources_cache_dir: override location of sources cache
"""
if matcher is None:
matcher = DataSourceMatcher.create_default(os_override=os_override)
if verbose:
print('using matcher with tags [%s]' % (', '.join(matcher.tags)), file=sys.stderr)
sources = load_cached_sources_list(sources_cache_dir=sources_cache_dir, verbose=verbose)
if verbose:
print('loaded %s sources' % (len(sources)), file=sys.stderr)
sources = [x for x in sources if matcher.matches(x)]
if verbose:
print('%s sources match current tags' % (len(sources)), file=sys.stderr)
return SourcesListLoader(sources)
def load_view(self, view_name, rosdep_db, verbose=False):
"""
Load view data into rosdep_db. If the view has already been
loaded into rosdep_db, this method does nothing.
:param view_name: name of ROS stack to load, ``str``
:param rosdep_db: database to load stack data into, :class:`RosdepDatabase`
:raises: :exc:`InvalidData`
"""
if rosdep_db.is_loaded(view_name):
return
source = self.get_source(view_name)
if verbose:
print('loading view [%s] with sources.list loader' % (view_name), file=sys.stderr)
view_dependencies = self.get_view_dependencies(view_name)
rosdep_db.set_view_data(view_name, source.rosdep_data, view_dependencies, view_name)
def get_loadable_resources(self):
return []
def get_loadable_views(self):
return [x.url for x in self.sources]
def get_view_dependencies(self, view_name):
# use dependencies to implement precedence
if view_name != SourcesListLoader.ALL_VIEW_KEY:
# if the view_name matches one of our sources, return
# empty list as none of our sources has deps.
if any([x for x in self.sources if view_name == x.url]):
return []
# not one of our views, so it depends on everything we provide
return [x.url for x in self.sources]
def get_source(self, view_name):
matches = [x for x in self.sources if x.url == view_name]
if matches:
return matches[0]
else:
raise rospkg.ResourceNotFound(view_name)
def get_rosdeps(self, resource_name, implicit=True):
"""
Always raises as SourceListLoader defines no concrete resources with rosdeps.
:raises: :exc:`rospkg.ResourceNotFound`
"""
raise rospkg.ResourceNotFound(resource_name)
def get_view_key(self, resource_name):
"""
Always raises as SourceListLoader defines no concrete resources with rosdeps.
:returns: Name of view that *resource_name* is in, ``None`` if no associated view.
:raises: :exc:`rospkg.ResourceNotFound` if *resource_name* cannot be found.
"""
raise rospkg.ResourceNotFound(resource_name)
完成以上步骤之后,mavros环境就基本配置成功啦! 下期再更新offboard控制+gazebo仿真以及与pixhawk4飞控连接!大家加油!