-
Notifications
You must be signed in to change notification settings - Fork 98
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
Add "get_licenses" method #133
Changes from all commits
df1a34f
4494b27
b39da30
1173f81
b746a4a
2629623
61e1bb0
8fca168
f5b28e0
05bed76
fe83169
2794d81
14c6441
b7b532f
9b58419
e5b85eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,7 +42,7 @@ | |
from .common import MANIFEST_FILE, PACKAGE_FILE, STACK_FILE | ||
|
||
# stack.xml and manifest.xml have the same internal tags right now | ||
REQUIRED = ['license'] | ||
REQUIRED = ['license', 'name'] | ||
ALLOWXHTML = ['description'] | ||
OPTIONAL = ['author', 'logo', 'url', 'brief', 'description', 'status', | ||
'notes', 'depend', 'rosdep', 'export', 'review', | ||
|
@@ -318,7 +318,7 @@ class Manifest(object): | |
'author', 'license', 'licenses', 'license_url', 'url', | ||
'depends', 'rosdeps', 'platforms', | ||
'exports', 'version', | ||
'status', 'notes', | ||
'status', 'name', 'notes', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please move |
||
'unknown_tags', 'type', 'filename', | ||
'is_catkin'] | ||
|
||
|
@@ -334,6 +334,7 @@ def __init__(self, type_='package', filename=None, is_catkin=False): | |
self.version = self.notes = '' | ||
self.licenses = [] | ||
self.depends = [] | ||
self.licenses = [] | ||
self.rosdeps = [] | ||
self.exports = [] | ||
self.platforms = [] | ||
|
@@ -396,6 +397,7 @@ def parse_manifest_file(dirpath, manifest_name, rospack=None): | |
p = parse_package(package_filename) | ||
# put these into manifest | ||
manifest.description = p.description | ||
manifest.name = p.name | ||
manifest.author = ', '.join([('Maintainer: %s' % str(m)) for m in p.maintainers] + [str(a) for a in p.authors]) | ||
manifest.license = ', '.join(p.licenses) | ||
manifest.licenses = p.licenses | ||
|
@@ -499,6 +501,7 @@ def parse_manifest(manifest_name, string, filename='string'): | |
pass # manifest is missing optional 'review notes' tag | ||
|
||
m.author = _check('author', True)(p, filename) | ||
m.name = _check('name')(p, filename) | ||
m.url = _check('url')(p, filename) | ||
m.version = _check('version')(p, filename) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ | |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||
# POSSIBILITY OF SUCH DAMAGE. | ||
|
||
from collections import defaultdict, OrderedDict | ||
import os | ||
from threading import Lock | ||
from xml.etree.cElementTree import ElementTree | ||
|
@@ -387,6 +388,38 @@ def stack_of(self, package): | |
else: | ||
d = os.path.dirname(d) | ||
|
||
def get_licenses(self, pkg_name, implicit=True): | ||
""" | ||
@summary: Return a list of licenses and the packages in the dependency tree | ||
for the given package. Special value 'ERR' is used as the license for the | ||
packages that license was not detected for. | ||
@param pkg_name: Name of the package the dependency tree begins from. | ||
@return Dictionary of license name and a list of packages. | ||
@rtype { k, [d] } | ||
@raise ResourceNotFound | ||
""" | ||
MSG_LICENSE_NOTFOUND_SYSPKG = "ERR" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to be a pretty awkward choice for the API. Maybe use the key |
||
license_dict = defaultdict(list) | ||
|
||
self.get_depends(name=pkg_name, implicit=implicit) | ||
|
||
for p_name, manifest in self._manifests.items(): | ||
for license in manifest.licenses: | ||
license_dict[license].append(p_name) | ||
|
||
# Traverse for Non-ROS, system packages | ||
pkgnames_rosdep = self.get_rosdeps(package=pkg_name, implicit=implicit) | ||
for pkgname_rosdep in pkgnames_rosdep: | ||
license_dict[MSG_LICENSE_NOTFOUND_SYSPKG].append(pkgname_rosdep) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why should this function combine both - the results from In general since the function only converts the results into a reverse mapping from license names to package names I am wondering how useful this is as part of the generic API of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I couldn't come up with a better alternative direction, so I just share my usecase below. I found this function convenient as a one-off to get all the list of licenses and all relevant packages (catkin and non-catkin packages), so that no other command is needed to get a complete list. Indeed, for those packages where license declaration wasn't found, I need additional steps (which is not part of this PR), but getting all the package names whose licenses couldn't be determined is a useful information IMO. That said, with the current implementation in this PR, there's no room for capturing the license from the non-catkin packages (e.g. what if the upstream implementation changes so that license of non-catkin package can be obtained?). I'll think about updating this PR so that the code doesn't rule out the chance of getting license for non-catkin pkgs. |
||
|
||
# Sort pkg names in each license | ||
for list_key in license_dict.values(): | ||
list_key.sort() | ||
# Sort license names | ||
licenses = OrderedDict(sorted(license_dict.items())) | ||
# Convert to dict for user friendlier output. | ||
return dict(licenses) | ||
130s marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
class RosStack(ManifestManager): | ||
""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?xml version="1.0"?> | ||
<package format="2"> | ||
<name>bar</name> | ||
<version>1.2.3</version> | ||
<description> | ||
I AM THE VERY MODEL OF A MODERN MAJOR GENERAL | ||
</description> | ||
<maintainer email="[email protected]">Someone</maintainer> | ||
|
||
<license>MIT</license> | ||
|
||
<url type="website">http://wiki.ros.org/bar</url> | ||
<url type="bugtracker">http://www.github.com/my_org/bar/issues</url> | ||
<author>Jane Doe</author> | ||
<author email="[email protected]">Jane Doe</author> | ||
|
||
<depend>liburdfdom-dev</depend> | ||
<exec_depend>foo</exec_depend> | ||
<test_depend>gtest</test_depend> | ||
<export /> | ||
</package> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,9 +14,6 @@ | |
<author>John Doe</author> | ||
<author email="[email protected]">Jane Doe</author> | ||
|
||
<build_depend>catkin</build_depend> | ||
<build_depend version_gte="1.1" version_lt="2.0">genmsg</build_depend> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm afraid these packages would not be available in non-ROS context, which nosetest in CI runs in. So getting rid of these. I looked around and couldn't find test cases that these removal directly affects (although there may be test cases that implicitly depend on them?). |
||
|
||
<build_depend>libboost-thread-dev</build_depend> | ||
<run_depend>libboost-thread</run_depend> | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,17 +40,18 @@ | |
|
||
|
||
def test_find_packages(): | ||
PKGS_EXIST = ['foo', 'bar'] | ||
manager = rospkg.rospack.ManifestManager(rospkg.common.MANIFEST_FILE, ros_paths=[search_path]) | ||
# for backward compatibility a wet package which is not a metapackage is found when searching for MANIFEST_FILE | ||
assert(len(manager.list()) == 1) | ||
assert(len(manager.list()) == 2) | ||
manager = rospkg.rospack.ManifestManager(rospkg.common.STACK_FILE, ros_paths=[search_path]) | ||
assert(len(manager.list()) == 0) | ||
manager = rospkg.rospack.ManifestManager(rospkg.common.PACKAGE_FILE, ros_paths=[search_path]) | ||
|
||
for pkg_name in manager.list(): | ||
assert(pkg_name == 'foo') | ||
assert(pkg_name in PKGS_EXIST) | ||
path = manager.get_path(pkg_name) | ||
assert(path == os.path.join(search_path, 'p1', 'foo')) | ||
assert(path == os.path.join(search_path, 'p1', PKGS_EXIST[PKGS_EXIST.index(pkg_name)])) | ||
|
||
|
||
def test_get_manifest(): | ||
|
@@ -67,3 +68,12 @@ def test_licenses(): | |
assert(len(manif.licenses) == 2) | ||
for l in manif.licenses: | ||
assert(l in licenses_list) | ||
|
||
|
||
def test_get_licenses(): | ||
"""Check licenses from all packages in the dependency chain""" | ||
rospack = rospkg.rospack.RosPack(ros_paths=[search_path]) | ||
licenses = rospack.get_licenses("bar", implicit=True) | ||
assert("MIT" in licenses) | ||
assert("BSD" in licenses) | ||
assert("LGPL" in licenses) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please also test for the expected values. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is
which
needed here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly I took this portion from ros-infrastructure/bloom/.travis.yml#L17, which seems to have been added in ros-infrastructure/bloom#221. I can only guess that it is needed as
sudo
doesn't know where the executable ofrosdep
is, which is installed into user's space bypip
. Not yet super sure whysudo
was/is needed on Travis CI though.