Skip to content
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 ament package for pclint #98

Merged
merged 3 commits into from
Jun 14, 2018
Merged

Conversation

jpsamper2009
Copy link
Contributor

@wjwwood @mjcarroll @dejanpan

  • As promised, here is a copy of our ament_pclint and ament_cmake_pclint packages. I haven't made any changes yet (not even the licenses) because I figure it'll have to go through at least one round of review anyway.

  • Neither of these will work if you don't have pclint plus installed (you can buy a license at http://www.gimpel.com)

  • The configuration files are modified versions of the ones found here: http://www.gimpel.com/html/ptch90.htm



def find_executable(file_name, additional_paths=None):
if sys.platform == 'linux':
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a small hack here because the binaries pclp64 is the Windows version, while linux and osx are distributed pclp64_linux and pclp64_osx.

code using PCLint.
</description>
<maintainer email="jp.samper@apex.ai">Juan Pablo Samper</maintainer>
<license>Apex.AI Proprietary License</license>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From this patch it is unclear what this license is. I think each package needs at least a LICENSE file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dirk-thomas It will probably become Apache 2.0 (or another well-known license), I just copy-pasted the code for now, so you could start looking at the content.

Copy link
Contributor

@dirk-thomas dirk-thomas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current the linter invocation fails when pclint isn't available. That would prohibit it from being added as a "default" linter. I think it would be good to have it as a default linter in order to check for it when pclint is available. Therefore I would suggest to gracefully skip linting when pclint isn't available instead.


if(INCLUDED_DIRS)
list(APPEND cmd "--include-directories" ${INCLUDED_DIRS})
set(_INCLUDE_DIRS_FLAG_SET "Found")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: indentation.

Same below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


set(cmd "${ament_pclint_BIN}" ${ARG_UNPARSED_ARGUMENTS})

get_property(INCLUDED_DIRS DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does it handle the case that no global include directories are being used but target specific include directories instead?

Same for compiler definitions below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% I understand the question, but I think what I meant to do here was gather all the directories included through cmake's include_directories. The idea is that you need to tell pclint where to look for header files, not unlike how you have to tell cmake where to find header files.

It's likely that I'm misusing or misunderstanding something in cmake, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, we want to give pclint compiler definitions added through target_compile_definitions, but again, I may be misunderstanding something fundamental about cmake.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CMake code might set these information on a per-targetg level using e.g. target_include_directories and/or target_compile_definitions. In that case the global variables might not be sufficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you're saying now. I was assuming that any headers are included with include_directories

What I'm thinking is we need to add PCLINT_INCLUDE_DIRS and PCLINT_COMPILE_DEFS options to ament_pclint or do you know of something better to get information added via target_*()?

Basically, pclint needs access to the exact same directories as the add_executable or add_library functions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a better way then to let the user pass in the necessary flags manually. That can be either the include dirs directory or a list of targets from which the include dirs are being extracted.

Beside supporting these information as arguments in the CMake function the user also needs to be able to set some CMake variables for the case where the linter is being invoked implicitly by the ament_lint_auto package.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was considering how to do this as well. @jpsamper2009 is there not a way to retrieve all of the includes and flags for a particular target rather than the global scope in CMake?

Copy link
Contributor

@mjcarroll mjcarroll Apr 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what you want to do is:

get_target_property(TARGET_INC ${PROJECT_NAME} INCLUDE_DIRECTORIES)
message(STATUS "INCLUDES: " ${TARGET_INC})

get_target_property(TARGET_FLAGS ${PROJECT_NAME} COMPILE_FLAGS)
message(STATUS "FLAGS: " ${TARGET_FLAGS})

get_target_property(TARGET_DEF ${PROJECT_NAME} COMPILE_DEFINITIONS)
message(STATUS "DEFINITIONS: " ${TARGET_DEF})

And then use those generated variables to pass into pclint.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upon further reflection, I think that the appropriate way to handle ament_cmake_pclint is to add a CMake function that takes a target as an argument. From there, we can retrieve the list of sources, includes, and compiler definitions/flags that are used on that target, to make the linting maximally effective.

This would also allow us to exclude things like unit tests, etc from pclint checking.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mjcarroll I hadn't seen your comment above, so for now I've just added function arguments to allow the user to specify the include directories explicitly.

Do you guys have an example of a package with target-specific include directories and compiler flags?

endif()

set(cmd ${cmd} PARENT_SCOPE)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: remove the empty lines right after function() and before endfunction().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="2">
<name>ament_cmake_pclint</name>
<version>0.0.3</version>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version should match the other packages in this repo which are currently 0.4.0.

Same in the other package as well as the setup.py file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

<maintainer email="jp.samper@apex.ai">Juan Pablo Samper</maintainer>
<license>Apex.AI Proprietary License</license>

<exec_depend>pclp64</exec_depend>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the release this needs to be a valid rosdep key. If that is not feasible the dependency needs to be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My guess is this won't be the case since it's a proprietary license, so I'll remove.

', '.join(["'.%s'" % e for e in extensions]))
parser.add_argument(
'--language',
help="Force pclint to analyze files as specified language. Supported languages c or cpp")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this provide a choices?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

if os.name == 'nt':
parser.add_argument(
'--pclint-config-dir',
nargs=1,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value of nargs is 1 so no need to specify it explicitly.

Same below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


# Add include directories from arguments
if args.include_directories is not None:
for directory in args.include_directories:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion to avoid the extra conditional:

for directory in (args.include_directories or []):

Same below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

# the number of cores cannot be determined, don't parallelize
pass

result = ['' for f in files]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: result = [''] * len(files)

Same below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

'--pclint-config-dir',
nargs=1,
type=str,
default=[os.path.dirname(os.path.realpath(__file__)) + "/config/msvc"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use single quotes rather than double quotes where possible without escaping.

Same below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should probably be using os.path.join, no?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even better 👍

@jpsamper2009
Copy link
Contributor Author

@dirk-thomas Do you have a suggestion on how to gracefully skip this test if pclint is not available? I'm thinking I can either check at the cmake level if the executable exists, and if it doesn't print a Warning and continue. I could also just do it at the ament_pclint.py level where if no executable is found, I print a warning and exit with return code 0.

@dirk-thomas
Copy link
Contributor

I would propose to set the DISABLED property on the CMake test when pclint was not found without printing a warning. Otherwise when the linter is added to the defaults every user would see a warning for every package when pclint is not available. While this new property is only available as of CMake 3.9 that should be fine since the next release can bump the minimum required version of CMake since Bionic ships with 3.10.

For the command line tool I would rather return a non-zero error code when pclint is unavailable. Passing without actually running the linter doesn't sounds like a good idea to me.

@mikaelarguedas mikaelarguedas added in progress Actively being worked on (Kanban column) requires-changes labels Apr 5, 2018
@mjcarroll mjcarroll mentioned this pull request Apr 6, 2018
10 tasks
@jpsamper2009
Copy link
Contributor Author

@mjcarroll Are you using this package? Do you have any feedback?

@mjcarroll
Copy link
Contributor

mjcarroll commented Apr 16, 2018

@jpsamper2009 I have been using it (your internal version, I need to check against this one). My biggest things were the exit without the executable being found, and the handling of the compiler flags.

I was also looking into retrieving the target flags for the open-source cppcheck tool for the open-source CI side.

@jpsamper2009
Copy link
Contributor Author

@mjcarroll @dirk-thomas I've reworked this a bit to get rid of some unused parts and address your suggestions. I'm afraid I didn't see #98 (comment) until just now, but maybe the current solution is sufficient to merge this, and we can work on reading information from a target on a separate issue? It would be good to get some people using it also to see what use cases we actually have instead of implementing a bunch of features that then go unused.

@dirk-thomas
Copy link
Contributor

We have to hold this anyway until we have moved to Ubuntu 18.04 since currently we only have CMake 3.5 available on 16.04 and this package require 3.9.

@mjcarroll
Copy link
Contributor

What 3.9 features is this using?

@jpsamper2009
Copy link
Contributor Author

@mjcarroll The second commit is using the DISABLE property which is only available in 3.9 (#98 (comment)). As long as you have pclint installed, you should be able to use the first commit.

@dirk-thomas Ack

@dirk-thomas dirk-thomas mentioned this pull request May 1, 2018
31 tasks
@dirk-thomas dirk-thomas added in review Waiting for review (Kanban column) and removed in progress Actively being worked on (Kanban column) requires-changes labels May 1, 2018
@mikaelarguedas mikaelarguedas added in progress Actively being worked on (Kanban column) and removed in review Waiting for review (Kanban column) labels May 17, 2018
@mjcarroll
Copy link
Contributor

This has been moved back to in progress. Because we are going to try to keep some Xenial compatibility, we need to find a way to accomplish the DISABLE property without using cmake 3.9

@dirk-thomas
Copy link
Contributor

(or just skip creating the test)

@jpsamper2009
Copy link
Contributor Author

@mjcarroll @dirk-thomas I have added a check to skip the test creation and print a warning if the executable is not found: (c3eaaf4)

I have also put the CMake 3.9 solution into a different branch in case we want to go back to that version eventually: jpsamper2009@e293644

author_email='jp.samper@apex.ai',
maintainer='Juan Pablo Samper',
maintainer_email='jp.samper@apex.ai',
url='https://gitlab.com/ApexAI/ament_lint',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this point to this repo instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Fixed.

long_description="""\
The ability to perform static code analysis on C/C++ code using PCLint
and generate xUnit test result files.""",
license='Apex AI',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This information doesn't match the package manifest which declares Apache 2.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, too many places to change it :). Fixed now.

@mikaelarguedas mikaelarguedas added in review Waiting for review (Kanban column) and removed in progress Actively being worked on (Kanban column) labels May 24, 2018
@jpsamper2009
Copy link
Contributor Author

I've just pushed a change to one of the pclint config files. The file is available online ( http://www.gimpel.com/html/pub90/au-misra3.lnt), but the version we were using had a less permissive license. I've instead adjusted the configuration linked above.

@mjcarroll mjcarroll merged commit 33f30be into ament:master Jun 14, 2018
@mikaelarguedas mikaelarguedas removed the in review Waiting for review (Kanban column) label Jun 14, 2018
@dirk-thomas
Copy link
Contributor

@mjcarroll Please squash merge in the future.

@mjcarroll
Copy link
Contributor

@dirk-thomas Sorry about that, I must have misclicked.

@serge-nikulin
Copy link

@jpsamper2009,

FYI (unless you guys already fixed it)

I finally found the option to check local headers we missed back then: +libclass(angle). See Chapter 5 in the PCLint manual for details. Add this option to both c++.lnt and c99.lnt files.

Also, I use a forced check now for all headers even if they are not used by local sources (via creating throw-away trivial users). See below the relevant fragment from my main.py file.

# For each header file create a temp .cpp file that includes it via ""
# This way PCLint wil check all header files even for pure interface libraries
def prepare_source_files(source_files, include_files):
    tempdir = tempfile.TemporaryDirectory(prefix='ament_pclintsa_')
    for header_name in include_files:
        if header_name.startswith('include/'):
            # A hack which must be fixed
            # remove "include/" prefix
            header_name = header_name[8:]
        header_name = header_name.replace('\\', '/')
        src_name = os.path.join(tempdir.name, os.path.basename(header_name) + '.cpp')
        with open(src_name, 'w') as f:
            f.write('#include "' + header_name + '"\n')
        source_files.append(src_name)
    # return tempdir too so it will be deleted on the script's end
    return source_files, tempdir

@dirk-thomas
Copy link
Contributor

@serge-nikulin This ticket has been closed for almost a year. The chances your comment will get lost is very high. Please either create a pull request with your proposed fragment or open an issue with the information (with a strong preference for the former).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants