kinetic-devel/tools/rospack/rosstack.cpp

1077 lines
31 KiB
C++

/*
* Copyright (C) 2009, Morgan Quigley and Brian Gerkey
*
* 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 names of Stanford University 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: Morgan Quigley, Brian Gerkey */
#include <cstdlib>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cerrno>
#include <string>
#include <vector>
#include <map>
#include <stack>
#include <queue>
#include <cassert>
#include <unistd.h>
#include <dirent.h>
#include <stdexcept>
#include <sys/time.h>
#include <sys/file.h>
#include <time.h>
#include <sstream>
#include <iterator>
#include <stdint.h>
#include <libgen.h>
#include "tinyxml-2.5.3/tinyxml.h"
#include "rospack/rosstack.h"
#include "rospack/rospack.h"
using namespace std;
//#define VERBOSE_DEBUG
const double DEFAULT_MAX_CACHE_AGE = 60.0; // rebuild cache every minute
#include <sys/stat.h>
#ifndef S_ISDIR
#define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR)
#endif
using namespace rosstack;
#ifdef __APPLE__
const string g_ros_os("osx");
#else
const string g_ros_os("linux");
#endif
//////////////////////////////////////////////////////////////////////////////
// Global storage for --foo options
// --deps-only
bool g_deps_only;
// --length=
string g_length;
// The stack name
string g_stack;
// the number of entries to list in the profile table
uint32_t g_profile_length = 0;
// global singleton rosstack pointer... yeah, I know.
ROSStack *g_rosstack = NULL;
//////////////////////////////////////////////////////////////////////////////
const char *rosstack::fs_delim = "/"; // ifdef this for windows someday
Stack::Stack(string _path) : path(_path),
deps_calculated(false), direct_deps_calculated(false),
descendants_calculated(false), manifest_loaded(false)
{
vector<string> path_tokens;
string_split(path, path_tokens, "/");
name = path_tokens.back();
// Don't load the manifest here, because it causes spurious errors to be
// printed if the stack has been moved (#1785). Presumably the manifest
// will be loaded later, prior to being needed.
//load_manifest();
}
bool Stack::is_stack(const string &path)
{
return file_exists(path + string(fs_delim) + "stack.xml");
}
bool Stack::is_package(const string &path)
{
return file_exists(path + string(fs_delim) + "manifest.xml");
}
bool Stack::is_no_subdirs(const string &path)
{
return file_exists(path + string(fs_delim) + "rosstack_nosubdirs");
}
const VecStack &Stack::deps1()
{
return direct_deps();
}
const VecStack &Stack::deps(traversal_order_t order, int depth)
{
if (depth > 1000)
{
fprintf(stderr,"[rospack] woah! expanding the dependency tree made it blow "
"up.\n There must be a circular dependency somewhere.\n");
throw runtime_error(string("circular dependency"));
}
if (deps_calculated)
return _deps;
// postorder traversal of the dependency tree
VecStack my_dd = direct_deps();
for (VecStack::iterator i = my_dd.begin(); i != my_dd.end(); ++i)
{
VecStack d = (*i)->deps(order, depth+1); // recurse on direct dependencies
if (order == PREORDER)
_deps.push_back(*i);
for (VecStack::iterator j = d.begin(); j != d.end(); ++j)
{
// don't add things twice, but if you have something already
// and we're doing a quasi-preorder traversal, bump it to the back
bool have = false;
VecStack::iterator prior_loc;
for (VecStack::iterator k = _deps.begin(); k != _deps.end() && !have; ++k)
if ((*k) == (*j))
{
prior_loc = k;
have = true;
}
if (have && order == PREORDER)
{
_deps.erase(prior_loc);
_deps.push_back(*j);
}
else if (!have)
_deps.push_back(*j);
}
if (order == POSTORDER)
{
// only stuff it to the end if it isn't there already
bool have = false;
for (VecStack::iterator k = _deps.begin(); k != _deps.end() && !have; ++k)
if ((*k) == (*i))
have = true;
if (!have)
_deps.push_back(*i);
}
}
deps_calculated = true;
return _deps;
}
string Stack::manifest_path()
{
return path + string(fs_delim) + "stack.xml";
}
VecStack Stack::descendants1()
{
VecStack children;
for (VecStack::iterator p = stacks.begin(); p != stacks.end(); ++p)
{
// We catch exceptions here, because we don't care if some
// unrelated packages in the system have invalid manifests
try
{
if ((*p)->has_parent(name))
children.push_back(*p);
}
catch (runtime_error &e)
{
}
}
return children;
}
const VecStack &Stack::descendants(int depth)
{
if (depth > 100)
{
fprintf(stderr, "[rosstack] woah! circular dependency! aaaaaa!\n");
throw runtime_error(string("circular dependency"));
}
if (descendants_calculated)
return _descendants;
VecStack desc_with_dups;
for (VecStack::iterator p = stacks.begin(); p != stacks.end(); ++p)
{
// We catch exceptions here, because we don't care if some
// unrelated packages in the system have invalid manifests
try
{
if ((*p)->has_parent(name))
{
desc_with_dups.push_back(*p);
const VecStack &p_desc = (*p)->descendants(depth+1);
for (VecStack::const_iterator q = p_desc.begin();
q != p_desc.end(); ++q)
desc_with_dups.push_back(*q);
}
}
catch (runtime_error &e)
{
}
}
assert(_descendants.size() == 0);
for (VecStack::iterator p = desc_with_dups.begin();
p != desc_with_dups.end(); ++p)
{
bool found = false;
for (VecStack::iterator q = _descendants.begin();
q != _descendants.end() && !found; ++q)
if ((*q)->name == (*p)->name)
found = true;
if (!found)
_descendants.push_back(*p);
}
descendants_calculated = true;
return _descendants;
}
bool Stack::has_parent(string pkg)
{
VecStack parents = direct_deps(true);
for (VecStack::iterator i = parents.begin(); i != parents.end(); ++i)
if ((*i)->name == pkg)
return true;
return false;
}
const VecStack &Stack::direct_deps(bool missing_stack_as_warning)
{
if (direct_deps_calculated)
return _direct_deps;
#ifdef VERBOSE_DEBUG
printf("calculating direct deps for package [%s]\n", name.c_str());
#endif
TiXmlElement *mroot = manifest_root();
TiXmlNode *dep_node = 0;
while ((dep_node = mroot->IterateChildren(string("depend"), dep_node)))
{
TiXmlElement *dep_ele = dep_node->ToElement();
assert(dep_ele);
const char *dep_stackname = dep_ele->Attribute("stack");
if (!dep_stackname)
{
fprintf(stderr,"[rosstack] bad depend syntax (no 'stack' attribute) in "
"[%s]\n", manifest_path().c_str());
throw runtime_error(string("invalid manifest"));
}
// Must make a copy here, because the call to g_get_stack() below might
// cause a recrawl, which blows aways the accumulated data structure.
string dep_stackname_copy = string(dep_stackname);
string name_copy = name;
#ifdef VERBOSE_DEBUG
printf("direct_deps: stk %s has dep %s\n",
name.c_str(), dep_stackname_copy.c_str());
#endif
try
{
_direct_deps.push_back(g_get_stack(dep_stackname_copy));
}
catch (runtime_error &e)
{
if (missing_stack_as_warning)
fprintf(stderr, "[rosstack] warning: couldn't find dependency "
"[%s] of [%s]\n",
dep_stackname_copy.c_str(), name_copy.c_str());
else
{
fprintf(stderr, "[rosstack] couldn't find dependency [%s] of [%s]\n",
dep_stackname_copy.c_str(), name_copy.c_str());
throw runtime_error(string("missing dependency"));
}
}
}
direct_deps_calculated = true;
return _direct_deps;
}
void Stack::load_manifest()
{
if (manifest_loaded)
return;
if (!manifest.LoadFile(manifest_path()))
{
string errmsg = string("error parsing manifest file at [") + manifest_path().c_str() + string("]");
fprintf(stderr, "[rosstack] warning: error parsing manifest file at [%s]. Blowing away the cache...\n",
manifest_path().c_str());
g_rosstack->deleteCache();
// Only want this warning printed once.
manifest_loaded = true;
throw runtime_error(errmsg);
}
TiXmlElement *mroot = manifest.RootElement();
}
TiXmlElement *Stack::manifest_root()
{
load_manifest();
TiXmlElement *ele = manifest.RootElement();
if (!ele)
{
string errmsg = string("error parsing manifest file at [") + manifest_path().c_str() + string("]");
throw runtime_error(errmsg);
}
return ele;
}
VecStack Stack::stacks;
//////////////////////////////////////////////////////////////////////////////
ROSStack::ROSStack() : ros_root(NULL), crawled(false)
{
g_rosstack = this;
Stack::stacks.reserve(500); // get some space to avoid early recopying...
ros_root = getenv("ROS_ROOT");
if (!ros_root)
{
fprintf(stderr,"[rosstack] ROS_ROOT is not defined in the environment.\n");
throw runtime_error(string("no ROS_ROOT"));
}
if (!file_exists(ros_root))
{
fprintf(stderr,"[rosstack] the path specified as ROS_ROOT is not "
"accessible. Please ensure that this environment variable "
"is set and is writeable by your user account.\n");
throw runtime_error(string("no ROS_ROOT"));
}
createROSHomeDirectory();
crawl_for_stacks();
}
ROSStack::~ROSStack()
{
for (VecStack::iterator p = Stack::stacks.begin();
p != Stack::stacks.end(); ++p)
delete (*p);
Stack::stacks.clear();
}
const char* ROSStack::usage()
{
return "USAGE: rosstack [options] <command> [stack]\n"
" Allowed commands:\n"
" help\n"
" find [stack]\n"
" contents [stack]\n"
" list\n"
" list-names\n"
" depends [stack] (alias: deps)\n"
" depends-manifests [stack] (alias: deps-manifests)\n"
" depends1 [stack] (alias: deps1)\n"
" depends-indent [stack] (alias: deps-indent)\n"
" depends-on [stack]\n"
" depends-on1 [stack]\n"
" contains [package]\n"
" contains-path [package]\n"
" profile [--length=<length>] \n\n"
" If [stack] is omitted, the current working directory\n"
" is used (if it contains a stack.xml).\n\n";
}
Stack *ROSStack::get_stack(const string &stack_name)
{
#ifdef VERBOSE_DEBUG
printf("searching for stack %s\n", stack_name.c_str());
#endif
for (VecStack::iterator p = Stack::stacks.begin();
p != Stack::stacks.end(); ++p)
{
if ((*p)->name == stack_name)
{
if(!crawled)
{
// Answer come from the cache; check that the path is valid, and
// contains a manifest (related to #1115).
std::string manifest_path = (*p)->path + fs_delim + "stack.xml";
struct stat s;
int ret;
while((ret = stat(manifest_path.c_str(), &s)) != 0 &&
errno == EINTR);
if(ret == 0)
{
// Answer looks good
return (*p);
}
else
{
// Bad cache. Warn and fall through to the recrawl below.
fprintf(stderr, "[rosstack] warning: invalid cached location %s for package %s; forcing recrawl\n",
(*p)->path.c_str(),
(*p)->name.c_str());
break;
}
}
else
{
// Answer came from a fresh crawl; no further checking needed.
return (*p);
}
}
}
if (!crawled) // maybe it's a brand-new stack. force a crawl.
{
crawl_for_stacks(true); // will set the crawled flag; recursion is safe
return get_stack(stack_name);
}
throw runtime_error(string("couldn't find stack ") + stack_name);
return NULL; // or not
}
int ROSStack::cmd_depends_on(bool include_indirect)
{
// Explicitly crawl for stacks, to ensure that we get newly added
// dependent stacks. We also avoid the possibility of a recrawl
// happening within the loop below, which could invalidate the stacks
// vector as we loop over it.
crawl_for_stacks(true);
Stack* s;
try
{
s = get_stack(g_stack);
}
catch(runtime_error)
{
fprintf(stderr, "[rosstack] warning: stack %s doesn't exist\n",
g_stack.c_str());
s = new Stack(g_stack);
Stack::stacks.push_back(s);
}
assert(s);
const VecStack descendants = include_indirect ? s->descendants()
: s->descendants1();
for (VecStack::const_iterator sit = descendants.begin();
sit != descendants.end(); ++sit)
printf("%s\n", (*sit)->name.c_str());
return 0;
}
int ROSStack::cmd_find()
{
// todo: obey the search order
Stack *p = get_stack(g_stack);
printf("%s\n", p->path.c_str());
return 0;
}
string ROSStack::lookup_owner(string pkg_name, bool just_owner_name)
{
// hack... we'll treat g_stack as the name of the package to look up.
rospack::Package *pkg = rp.get_pkg(pkg_name);
#ifdef VERBOSE_DEBUG
printf("package path: [%s]\n", pkg->path.c_str());
#endif
map<string, string> bases; // all the places the search can bottom out
for (VecStack::iterator p = Stack::stacks.begin(); // first, add stacks
p != Stack::stacks.end(); ++p)
bases[(*p)->path] = (*p)->name;
/*
char *rr = getenv("ROS_ROOT"); // add ROS_ROOT
if (rr)
{
bases
}
*/
char *rpp = getenv("ROS_PACKAGE_PATH"); // add ROS_PACKAGE_PATH entries
if (rpp)
{
vector<string> rppvec;
string_split(rpp, rppvec, ":");
sanitize_rppvec(rppvec);
for (vector<string>::iterator i = rppvec.begin(); i != rppvec.end(); ++i)
bases[*i] = string("");
}
#ifdef VERBOSE_DEBUG
printf("bases:\n");
for (map<string, string>::iterator i = bases.begin(); i != bases.end(); ++i)
printf("%s -> %s\n", i->first.c_str(), i->second.c_str());
#endif
// now, chop the package path until we hit one of the bases
string pkg_path_fragment = pkg->path;
while (pkg_path_fragment.length() > 1)
{
// chop off everything to the right of the last slash
size_t last_slash_pos = pkg_path_fragment.find_last_of('/');
if (last_slash_pos == string::npos)
break; // shouldn't happen, but might as well catch it
pkg_path_fragment = pkg_path_fragment.substr(0, last_slash_pos);
#ifdef VERBOSE_DEBUG
printf("frag = %s\n", pkg_path_fragment.c_str());
#endif
map<string, string>::iterator i = bases.find(pkg_path_fragment);
if (i != bases.end())
{
if (just_owner_name)
return bases[pkg_path_fragment];
else
return pkg_path_fragment;
break;
}
}
return string("");
}
int ROSStack::cmd_contains()
{
printf("%s\n", lookup_owner(g_stack, true).c_str());
return 0;
}
int ROSStack::cmd_contains_path()
{
printf("%s\n", lookup_owner(g_stack, false).c_str());
return 0;
}
int ROSStack::cmd_deps()
{
VecStack d = get_stack(g_stack)->deps(Stack::POSTORDER);
for (VecStack::iterator i = d.begin(); i != d.end(); ++i)
printf("%s\n", (*i)->name.c_str());
return 0;
}
int ROSStack::cmd_deps_manifests()
{
VecStack d = get_stack(g_stack)->deps(Stack::POSTORDER);
for (VecStack::iterator i = d.begin(); i != d.end(); ++i)
printf("%s/stack.xml ", (*i)->path.c_str());
puts("");
return 0;
}
int ROSStack::cmd_deps1()
{
VecStack d = get_stack(g_stack)->deps1();
for (VecStack::iterator i = d.begin(); i != d.end(); ++i)
printf("%s\n", (*i)->name.c_str());
return 0;
}
int ROSStack::cmd_depsindent(Stack *stack, int indent)
{
VecStack d = stack->deps1();
for (VecStack::iterator i = d.begin(); i != d.end(); ++i)
{
for(int s=0; s<indent; s++)
printf(" ");
printf("%s\n", (*i)->name.c_str());
cmd_depsindent(*i, indent+2);
}
return 0;
}
static bool space(char c) { return isspace(c); }
static bool not_space(char c) { return !isspace(c); }
static vector<string> split_space(const string& str)
{
typedef string::const_iterator iter;
vector<string> ret;
iter i = str.begin();
while (i != str.end())
{
i = find_if(i, str.end(), not_space);
iter j = find_if(i, str.end(), space);
if (i != str.end())
ret.push_back(string(i, j));
i = j;
}
return ret;
}
int ROSStack::run(int argc, char **argv)
{
assert(argc >= 2);
int i;
const char* opt_length = "--length=";
string errmsg = string(usage());
i=1;
const char* cmd = argv[i++];
for(;i<argc;i++)
{
if(!strncmp(argv[i], opt_length, strlen(opt_length)))
{
if(strlen(argv[i]) > strlen(opt_length))
g_length = string(argv[i]+strlen(opt_length));
else
throw runtime_error(errmsg);
}
else
break;
}
if(strcmp(cmd, "profile") && g_length.size())
throw runtime_error(errmsg);
if(i < argc)
{
if(!strcmp(cmd, "help") ||
!strcmp(cmd, "list") ||
!strcmp(cmd, "list-names") ||
!strcmp(cmd, "profile"))
throw runtime_error(errmsg);
g_stack = string(argv[i++]);
}
// Are we sitting in a stack?
else if(Stack::is_stack("."))
{
char buf[1024];
if(!getcwd(buf,sizeof(buf)))
throw runtime_error(errmsg);
g_stack = string(basename(buf));
}
if (i != argc)
throw runtime_error(errmsg);
if (!strcmp(cmd, "profile"))
{
if (g_length.size())
g_profile_length = atoi(g_length.c_str());
else
g_profile_length = 20; // default is about a screenful or so
#ifdef VERBOSE_DEBUG
printf("profile_length = %d\n", g_profile_length);
#endif
// re-crawl with profiling enabled
crawl_for_stacks(true);
return 0;
}
else if (!strcmp(cmd, "find"))
return cmd_find();
else if (!strcmp(cmd, "contains"))
return cmd_contains();
else if (!strcmp(cmd, "contains-path"))
return cmd_contains_path();
else if (!strcmp(cmd, "list"))
return cmd_print_stack_list(true);
else if (!strcmp(cmd, "list-names"))
return cmd_print_stack_list(false);
else if (!strcmp(cmd, "contents"))
return cmd_print_packages();
else if (!strcmp(cmd, "depends") || !strcmp(cmd, "deps"))
return cmd_deps();
else if (!strcmp(cmd, "depends-manifests") || !strcmp(cmd, "deps-manifests"))
return cmd_deps_manifests();
else if (!strcmp(cmd, "depends1") || !strcmp(cmd, "deps1"))
return cmd_deps1();
else if (!strcmp(cmd, "depends-indent") || !strcmp(cmd, "deps-indent"))
return cmd_depsindent(get_stack(g_stack), 0);
else if (!strcmp(cmd, "depends-on"))
return cmd_depends_on(true);
else if (!strcmp(cmd, "depends-on1"))
return cmd_depends_on(false);
else if (!strcmp(cmd, "help"))
fputs(usage(), stderr);
else
throw runtime_error(errmsg);
return 0;
}
int ROSStack::cmd_print_stack_list(bool print_path)
{
for (VecStack::iterator i = Stack::stacks.begin();
i != Stack::stacks.end(); ++i)
if (print_path)
printf("%s %s\n", (*i)->name.c_str(), (*i)->path.c_str());
else
printf("%s\n", (*i)->name.c_str());
return 0;
}
int ROSStack::cmd_print_packages()
{
rospack::ROSPack rp;
string path = get_stack(g_stack)->path;
//printf("partial crawl of %s\n", path.c_str());
rospack::VecPkg pkgs = rp.partial_crawl(path);
//printf("found %d pkgs\n", pkgs.size());
for (rospack::VecPkg::iterator i = pkgs.begin(); i != pkgs.end(); ++i)
{
printf("%s\n", (*i)->name.c_str());
delete *i;
}
return 0;
}
void ROSStack::createROSHomeDirectory()
{
char *homedir = getenv("HOME");
if (!homedir) {
//fprintf(stderr, "[rospack] WARNING: cannot create ~/.ros directory.\n");
}
else
{
string path = string(homedir) + "/.ros";
if (access(path.c_str(), R_OK) && !mkdir(path.c_str(), 0700))
fprintf(stderr,"[rosstack] WARNING: cannot create ~/.ros directory.\n");
}
}
string ROSStack::getCachePath()
{
string path;
path = string(ros_root) + "/.rosstack_cache";
if (access(ros_root, W_OK) == 0)
return path;
// if we cannot write into the ros_root, then let's try to
// write into the user's .ros directory.
createROSHomeDirectory();
path = string(getenv("HOME")) + "/.ros/rosstack_cache";
return path;
}
void ROSStack::deleteCache()
{
string cache_path = g_rosstack->getCachePath();
if (file_exists(cache_path))
remove(cache_path.c_str());
}
bool ROSStack::cache_is_good()
{
string cache_path = getCachePath();
// first see if it's new enough
double cache_max_age = DEFAULT_MAX_CACHE_AGE;
const char *user_cache_time_str = getenv("ROS_CACHE_TIMEOUT");
if(user_cache_time_str)
cache_max_age = atof(user_cache_time_str);
if(cache_max_age == 0.0)
return false;
struct stat s;
if (stat(cache_path.c_str(), &s) == 0)
{
double dt = difftime(time(NULL), s.st_mtime);
#ifdef VERBOSE_DEBUG
printf("cache age: %f\n", dt);
#endif
// Negative cache_max_age means it's always new enough. It's dangerous
// for the user to set this, but rosbash uses it.
if ((cache_max_age > 0.0) && (dt > cache_max_age))
return false;
}
// try to open it
FILE *cache = fopen(cache_path.c_str(), "r");
if (!cache)
return false; // it's not readable by us. sad.
// see if ROS_ROOT and ROS_PACKAGE_PATH are identical
char linebuf[30000];
bool ros_root_ok = false, ros_package_path_ok = false;
const char *ros_package_path = getenv("ROS_PACKAGE_PATH");
while (!feof(cache))
{
linebuf[0] = 0;
if (!fgets(linebuf, sizeof(linebuf), cache))
break;
if (!linebuf[0])
continue;
linebuf[strlen(linebuf)-1] = 0; // get rid of trailing newline
if (linebuf[0] == '#')
{
if (!strncmp("#ROS_ROOT=", linebuf, 10))
{
if (!strcmp(linebuf+10, ros_root))
ros_root_ok = true;
}
else if (!strncmp("#ROS_PACKAGE_PATH=", linebuf, 18))
{
if (!ros_package_path)
{
if (!strlen(linebuf+18))
ros_package_path_ok = true;
}
else if (!strcmp(linebuf+18, getenv("ROS_PACKAGE_PATH")))
ros_package_path_ok = true;
}
}
else
break; // we're out of the header. nothing more matters to this check.
}
fclose(cache);
return ros_root_ok && ros_package_path_ok;
}
class CrawlQueueEntry
{
public:
string path;
double start_time, elapsed_time;
CrawlQueueEntry(string _path)
: path(_path), start_time(0), elapsed_time(0) { }
bool operator>(const CrawlQueueEntry &rhs) const
{
return elapsed_time > rhs.elapsed_time;
}
};
double ROSStack::time_since_epoch()
{
struct timeval tod;
gettimeofday(&tod, NULL);
return tod.tv_sec + 1e-6 * tod.tv_usec;
}
void ROSStack::crawl_for_stacks(bool force_crawl)
{
for (VecStack::iterator p = Stack::stacks.begin();
p != Stack::stacks.end(); ++p)
delete *p;
Stack::stacks.clear();
if(!force_crawl && cache_is_good())
{
string cache_path = getCachePath();
FILE *cache = fopen(cache_path.c_str(), "r");
if (cache) // one last check just in case nutty stuff happened in between
{
#ifdef VERBOSE_DEBUG
printf("trying to use cache...\n");
#endif
char linebuf[30000];
while (!feof(cache))
{
linebuf[0] = 0;
if (!fgets(linebuf, sizeof(linebuf), cache))
break; // error in read operation
if (!linebuf[0] || linebuf[0] == '#')
continue;
char *newline_pos = strchr(linebuf, '\n');
if (newline_pos)
*newline_pos = 0;
Stack::stacks.push_back(new Stack(linebuf));
}
fclose(cache);
return; // cache load went OK; we're done here.
}
}
// if we get here, this means the cache either bogus or we've been
// instructed to rebuild it.
#ifdef VERBOSE_DEBUG
printf("building cache\n");
#endif
deque<CrawlQueueEntry> q;
q.push_back(CrawlQueueEntry(ros_root));
vector<string> rspvec;
// seed the crawler with ROS_ROOT and ROS_PACKAGE_PATH
char *rr = getenv("ROS_ROOT");
if (!rr)
{
fprintf(stderr, "[rosstack] ERROR: ROS_ROOT not set.\n");
exit(1);
}
Stack::stacks.push_back(new Stack(string(rr))); // add the ROS stack
string rsp;
char *rpp = getenv("ROS_PACKAGE_PATH");
if (rpp)
rsp = string(rpp);
string_split(rsp, rspvec, ":");
sanitize_rppvec(rspvec);
#ifdef VERBOSE_DEBUG
printf("seeding crawler with [%s], which has %lu entries\n", rsp.c_str(), rspvec.size());
#endif
for (vector<string>::iterator i = rspvec.begin(); i != rspvec.end(); ++i)
{
// Check whether this part of ROS_PACKAGE_PATH is itself a package/stack
if (Stack::is_stack(*i))
Stack::stacks.push_back(new Stack(*i));
else if (Stack::is_package(*i))
continue; // ignore it.
else if (Stack::is_no_subdirs(*i))
fprintf(stderr, "[rosstack] WARNING: non-stack directory in "
"ROS_PACKAGE_PATH marked "
"rosstack_nosubdirs:\n\t%s\n",
i->c_str());
else
q.push_back(CrawlQueueEntry(*i));
}
const double crawl_start_time = time_since_epoch();
priority_queue<CrawlQueueEntry, vector<CrawlQueueEntry>,
greater<CrawlQueueEntry> > profile;
while (!q.empty())
{
CrawlQueueEntry cqe = q.front();
q.pop_front();
//printf("crawling %s\n", cqe.path.c_str());
if (g_profile_length > 0)
{
if (cqe.start_time != 0)
{
// this stack symbol means we've already crawled its children, and it's
// just here for timing purposes. save the traversal time and bail.
cqe.elapsed_time = time_since_epoch() - cqe.start_time;
profile.push(cqe);
if (profile.size() > g_profile_length) // only save the worst guys
profile.pop();
continue;
}
cqe.start_time = time_since_epoch();
q.push_front(cqe);
}
DIR *d = opendir(cqe.path.c_str());
if (!d)
{
fprintf(stderr, "[rosstack] opendir error [%s] while crawling %s\n",
strerror(errno), cqe.path.c_str());
continue;
}
struct dirent *ent;
while ((ent = readdir(d)) != NULL)
{
struct stat s;
string child_path = cqe.path + fs_delim + string(ent->d_name);
if (stat(child_path.c_str(), &s) != 0)
continue;
if (!S_ISDIR(s.st_mode))
continue;
if (ent->d_name[0] == '.')
continue; // ignore hidden dirs
else if (Stack::is_package(child_path))
continue; // ignore this guy, he's a leaf.
if (Stack::is_stack(child_path))
{
// Filter out duplicates; first encountered takes precedence
Stack *newp = new Stack(child_path);
//printf("found stack %s\n", child_path.c_str());
// TODO: make this check more efficient
bool dup = false;
for(std::vector<Stack *>::const_iterator it = Stack::stacks.begin();
it != Stack::stacks.end();
it++)
{
if((*it)->name == newp->name)
{
dup=true;
break;
}
}
if(dup)
delete newp;
else
Stack::stacks.push_back(newp);
}
//check to make sure we're allowed to descend
else if (!Stack::is_no_subdirs(child_path))
q.push_front(CrawlQueueEntry(child_path));
}
closedir(d);
}
crawled = true; // don't try to re-crawl if we can't find something
const double crawl_elapsed_time = time_since_epoch() - crawl_start_time;
// write the results of this crawl to the cache file
string cache_path = getCachePath();
char tmp_cache_dir[PATH_MAX];
char tmp_cache_path[PATH_MAX];
strncpy(tmp_cache_dir, cache_path.c_str(), sizeof(tmp_cache_dir));
snprintf(tmp_cache_path, sizeof(tmp_cache_path), "%s/.rosstack_cache.XXXXXX", dirname(tmp_cache_dir));
int fd = mkstemp(tmp_cache_path);
if (fd < 0)
{
fprintf(stderr, "Unable to create temporary cache file: %s\n", tmp_cache_path);
throw runtime_error(string("failed to create tmp cache file"));
}
FILE *cache = fdopen(fd, "w");
if (!cache)
{
fprintf(stderr, "woah! couldn't create the cache file. Please check "
"ROS_ROOT to make sure it's a writeable directory.\n");
throw runtime_error(string("failed to create tmp cache file"));
}
fprintf(cache, "#ROS_ROOT=%s\n#ROS_PACKAGE_PATH=%s\n", ros_root, rsp.c_str());
for (VecStack::iterator s = Stack::stacks.begin();
s != Stack::stacks.end(); ++s)
fprintf(cache, "%s\n", (*s)->path.c_str());
if(rename(tmp_cache_path, cache_path.c_str()) < 0)
{
fprintf(stderr, "[rospack] Error: failed rename cache file %s to %s\n", tmp_cache_path, cache_path.c_str());
perror("rename");
throw runtime_error(string("failed to rename cache file"));
}
fclose(cache);
if (g_profile_length)
{
// dump it into a stack to reverse it (so slowest guys are first)
stack<CrawlQueueEntry> reverse_profile;
while (!profile.empty())
{
reverse_profile.push(profile.top());
profile.pop();
}
printf("\nFull tree crawl took %.6f seconds.\n", crawl_elapsed_time);
printf("-------------------------------------------------------------\n");
while (!reverse_profile.empty())
{
CrawlQueueEntry cqe = reverse_profile.top();
reverse_profile.pop();
printf("%.6f %s\n", cqe.elapsed_time, cqe.path.c_str());
}
printf("\n");
}
}
//////////////////////////////////////////////////////////////////////////////
void rosstack::string_split(const string &s, vector<string> &t, const string &d)
{
t.clear();
size_t start = 0, end;
while ((end = s.find_first_of(d, start)) != string::npos)
{
t.push_back(s.substr(start, end-start));
start = end + 1;
}
if (start != s.length())
t.push_back(s.substr(start));
}
bool rosstack::file_exists(const string &fname)
{
return (access(fname.c_str(), F_OK) == 0); // will be different in windows
}
Stack *rosstack::g_get_stack(const string &name)
{
return g_rosstack->get_stack(name);
}
void ROSStack::sanitize_rppvec(std::vector<std::string> &rppvec)
{
// drop any trailing slashes
for (size_t i = 0; i < rppvec.size(); i++)
{
size_t last_slash_pos = rppvec[i].find_last_of("/");
if (last_slash_pos != string::npos &&
last_slash_pos == rppvec[i].length()-1)
{
fprintf(stderr, "[rosstack] warning: trailing slash found in "
"ROS_PACKAGE_PATH\n");
rppvec[i].erase(last_slash_pos);
}
}
}