Repository: conan-io/conan

Issue:
Packages should include licensing information
Some projects cannot operate with specific licenses and having that information would be significantly helpful. An added bonus would be to add restrictions on what licenses can be used/not used for a project. This feature would need to support libraries that have multiple licensing agreements (i.e. Qt with LGPL2 and LGPL3).



Discussion:
As a initial step towards this functionality, current `conan info` command will show declared licenses (package authors should add the license attribute) for packages, so it can be easily checked. Probably the command can be shortened to --only-license or the like, to avoid the extra output. No active constraints are imposed when adding requirements to conanfiles, it can be complicated to deal with all kind of licenses and transitivity, but I think an useful information command could be enough at the moment. Have you tried it? What do you think?

I think that would be a great first step. I like that the package dependencies also get printed.

Not sure if I understood, the command `conan info` is already available and working. You mean that "would be a great first step" the --only-license? Could you be a bit more precise? Have you tried to run the command? Thanks for your input!

Sorry, the --only-license would be great. It has already been a long day.


Resolving patch:
diff --git a/conans/client/command.py b/conans/client/command.py
--- a/conans/client/command.py
+++ b/conans/client/command.py
@@ -290,6 +290,8 @@ def info(self, *args):
         parser.add_argument("--settings", "-s",
                             help='load settings to build the package, -s compiler:gcc',
                             nargs=1, action=Extender)
+        parser.add_argument("--only", "-n",
+                            help='show fields only')
         parser.add_argument("--integrity", "-i", action='store_true', default=False,
                             help='Check that the stored recipe or package manifests are correct')
         parser.add_argument("--update", "-u", action='store_true', default=False,
@@ -311,7 +313,7 @@ def info(self, *args):
                               options=options,
                               settings=settings,
                               build_mode=False,
-                              info=True,
+                              info=args.only or True,
                               check_updates=args.update,
                               integrity=args.integrity,
                               filename=args.file)
diff --git a/conans/client/printer.py b/conans/client/printer.py
--- a/conans/client/printer.py
+++ b/conans/client/printer.py
@@ -49,6 +49,13 @@ def print_info(self, deps_graph, project_reference, _info, registry, graph_updat
                                        of the printed dependencies.
                 remote: Remote specified in install command. Could be different from the registry one.
         """
+        def show(field):
+            if _info is True:
+                return True
+            if field in [s.lower() for s in _info.split(",")]:
+                return True
+            return False
+
         graph_updates_info = graph_updates_info or {}
         for node in sorted(deps_graph.nodes):
             ref, conan = node
@@ -61,7 +68,8 @@ def print_info(self, deps_graph, project_reference, _info, registry, graph_updat
                     ref = project_reference
             self._out.writeln("%s" % str(ref), Color.BRIGHT_CYAN)
             reg_remote = registry.get_ref(ref)
-            if isinstance(ref, ConanFileReference):  # Excludes PROJECT fake reference
+            # Excludes PROJECT fake reference
+            if isinstance(ref, ConanFileReference) and show("remote"):
                 if reg_remote:
                     remote_name = remote or reg_remote.name
                     self._out.writeln("    Remote: %s=%s" % (reg_remote.name, reg_remote.url),
@@ -73,17 +81,18 @@ def print_info(self, deps_graph, project_reference, _info, registry, graph_updat
             url = getattr(conan, "url", None)
             license_ = getattr(conan, "license", None)
             author = getattr(conan, "author", None)
-            if url:
+            if url and show("url"):
                 self._out.writeln("    URL: %s" % url, Color.BRIGHT_GREEN)
-            if license_:
+
+            if license_ and show("license"):
                 if isinstance(license_, (list, tuple, set)):
                     self._out.writeln("    Licenses: %s" % ", ".join(license_), Color.BRIGHT_GREEN)
                 else:
                     self._out.writeln("    License: %s" % license_, Color.BRIGHT_GREEN)
-            if author:
+            if author and show("author"):
                 self._out.writeln("    Author: %s" % author, Color.BRIGHT_GREEN)
 
-            if isinstance(ref, ConanFileReference):  # Excludes PROJECT fake reference
+            if isinstance(ref, ConanFileReference) and show("update"):  # Excludes PROJECT
                 update = graph_updates_info.get(ref)
                 update_messages = {
                  None: ("Version not checked", Color.WHITE),
@@ -94,17 +103,18 @@ def print_info(self, deps_graph, project_reference, _info, registry, graph_updat
                 self._out.writeln("    Updates: %s" % update_messages[update][0], update_messages[update][1])
 
             dependants = deps_graph.inverse_neighbors(node)
-            if isinstance(ref, ConanFileReference):  # Excludes PROJECT fake reference
+            if isinstance(ref, ConanFileReference) and show("required"):  # Excludes 
                 self._out.writeln("    Required by:", Color.BRIGHT_GREEN)
                 for d in dependants:
                     ref = repr(d.conan_ref) if d.conan_ref else project_reference
                     self._out.writeln("        %s" % ref, Color.BRIGHT_YELLOW)
 
-            depends = deps_graph.neighbors(node)
-            if depends:
-                self._out.writeln("    Requires:", Color.BRIGHT_GREEN)
-                for d in depends:
-                    self._out.writeln("        %s" % repr(d.conan_ref), Color.BRIGHT_YELLOW)
+            if show("requires"):
+                depends = deps_graph.neighbors(node)
+                if depends:
+                    self._out.writeln("    Requires:", Color.BRIGHT_GREEN)
+                    for d in depends:
+                        self._out.writeln("        %s" % repr(d.conan_ref), Color.BRIGHT_YELLOW)
 
     def print_search(self, info, pattern=None, verbose=False, extra_verbose=False):
         """ Print all the exported conans information


Test patch:
diff --git a/conans/test/command/info_test.py b/conans/test/command/info_test.py
--- a/conans/test/command/info_test.py
+++ b/conans/test/command/info_test.py
@@ -70,3 +70,27 @@ def reuse_test(self):
                 Requires:
                     Hello0/0.1@lasote/stable""")
         self.assertIn(expected_output, self.client.user_io.out)
+
+        self.client.run("info -u --only=url")
+        expected_output = textwrap.dedent(
+            """\
+            Hello2/0.1@PROJECT
+                URL: myurl
+            Hello0/0.1@lasote/stable
+                URL: myurl
+            Hello1/0.1@lasote/stable
+                URL: myurl""")
+        self.assertIn(expected_output, self.client.user_io.out)
+        self.client.run("info -u --only=url,license")
+        expected_output = textwrap.dedent(
+            """\
+            Hello2/0.1@PROJECT
+                URL: myurl
+                Licenses: MIT, GPL
+            Hello0/0.1@lasote/stable
+                URL: myurl
+                License: MIT
+            Hello1/0.1@lasote/stable
+                URL: myurl
+                License: MIT""")
+        self.assertIn(expected_output, self.client.user_io.out)