Skip to content

Upgrade Sonarcloud Functionality #114

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

Merged
merged 23 commits into from
May 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .cmake-format.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ lint:
min_statement_spacing: 1
max_statement_spacing: 2
max_returns: 6
max_branches: 30 # Default target: 12
max_branches: 40 # Default target: 12
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 is some cmake linting which is failing due to SwiftTarget .cmake having too many if statements in the function.

max_arguments: 6 # Default target: 5
max_localvars: 15
max_statements: 110 # Default target: 50
max_statements: 120 # Default target: 50
encode:
emit_byteorder_mark: false
input_encoding: utf-8
Expand Down
18 changes: 14 additions & 4 deletions ListTargets.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,32 @@ function(swift_list_targets out_var)
set(all_targets)

foreach(target IN LISTS x_all_targets)
get_target_property(type ${target} TYPE)
if(x_TYPES)
get_target_property(type ${target} TYPE)
if(NOT ${type} IN_LIST x_TYPES)
continue()
endif()
endif()

if(x_SWIFT_TYPES)
get_target_property(type ${target} SWIFT_TYPE)
if(NOT ${type} IN_LIST x_SWIFT_TYPES)
if (type STREQUAL "INTERFACE_LIBRARY")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

previously if the target was an interface library (ex eigen) type, cmake would complain about not being able to obtain any properties that didn't prefix with INTERFACE.

get_target_property(swift_type ${target} INTERFACE_SWIFT_TYPE)
else()
get_target_property(swift_type ${target} SWIFT_TYPE)
endif()

if(NOT ${swift_type} IN_LIST x_SWIFT_TYPES)
continue()
endif()
endif()

if(x_ONLY_THIS_REPO)
get_target_property(target_dir ${target} SOURCE_DIR)
if (type STREQUAL "INTERFACE_LIBRARY")
get_target_property(target_dir ${target} INTERFACE_SOURCE_DIR)
else()
get_target_property(target_dir ${target} SOURCE_DIR)
endif()

# This replacement makes sure that we only filter out third_party subdirectories which actually exist in the root project source dir - ie, a git repo cloned in to a path
# which just so happens to contain third_party should not break this function
string(REPLACE ${CMAKE_SOURCE_DIR} "" target_dir ${target_dir})
Expand Down
215 changes: 215 additions & 0 deletions Sonarcloud.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
#
# Copyright (C) 2022 Swift Navigation Inc.
# Contact: Swift Navigation <[email protected]>
#
# This source is subject to the license found in the file 'LICENSE' which must
# be distributed together with this source. All other rights reserved.
#
# THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
#

#
# OVERVIEW
# ========
#
# Offers a way to generate the necessary Sonarcloud project file from the
# information available via cmake build system. User can call on
# "generate_sonarcloud_project_properties" to produce this project file which
# outlines to Sonarcloud what files/directories are deemed source files and
# which files are test files.
#
# This module will work in conjunction with "TestTargets" and "SwiftTargets",
# and "ListTargets" to identify all Swift production source/test code through.
# This means that only targets created via "swift_add_*" functions that are
# categorized as "production" code will be included in the analysis. No other
# none C/C++ source code will be included in the Sonarcloud project properties
# file (at the moment).
#
# USAGE
# =====
#
# To generate the Sonarcloud project file, simply call the following method at
# the end of your projects CMakeLists.txt file:
#
# generate_sonarcloud_project_properties(${CMAKE_BINARY_DIR}/sonar-project.properties)
#
# This will generate a sonar project file in your root build directory (please
# don't ever write this to your source directory). Prior to uploading the
# results to Sonarcloud, you will need to manually transform all absolute path
# references to relative paths. This manual step can be done easily with the
# following Linux command:
#
# sed -i -E "s|^(\s+)(${PWD}/)|\1|g" 'sonar-project.properties'
Copy link
Contributor

Choose a reason for hiding this comment

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

I know this is a one liner but I think it would be good to make it available as a bash script, and have pipelines use the bash script to fix up the paths rather than writing this snippet directly into pipeline scripts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This functionality is already incorporated into our Sonarcloud upload script (see here). You can see here how we use it.

#
# The reason why this extra step is required and was not taken care by cmake,
# is because targets have generator expressions to them which can only be
# evaluated at the end of the configuration stage. At that point there is no
# find/replace functionality.
#

include(ListTargets)

set(_sonarcloud_newline "\\\n ")

function(_transform_sonarcloud_source_files output_variable target)
#
# Based off of https://cmake.org/cmake/help/latest/prop_tgt/SOURCES.html we
# need to correctly interpret the SOURCES properties to be able to transform
# them into what sonarcloud project properties is comfortable with (ie.
# relative path from project source directory).
#
get_target_property(target_binary_dir ${target} BUILD_DIR)
get_target_property(target_source_dir ${target} SOURCE_DIR)

unset(source_files)
foreach (source_file IN LISTS ARGN)
get_source_file_property(is_build_generated_file ${target_binary_dir}/${source_file} GENERATED)

if(IS_ABSOLUTE ${source_file})
list(APPEND source_files ${source_file})
continue()
endif()

if (is_build_generated_file)
list(APPEND source_files ${target_binary_dir}/${source_file})
continue()
endif()

if (EXISTS ${target_source_dir}/${source_file})
list(APPEND source_files ${target_source_dir}/${source_file})
continue()
endif()

if (source_file MATCHES "^\\$<.+>$")
list(APPEND source_files "$<$<BOOL:${source_file}>:$<JOIN:${source_file},$<COMMA>${_sonarcloud_newline}>>")
continue()
endif()

message(WARNING "Sonarcloud is missing source file \"${source_file}\" for target ${target}")
endforeach()

set(${output_variable} ${source_files} PARENT_SCOPE)
endfunction()

function(_transform_sonarcloud_include_directories output_variable target)
unset(include_directories)

foreach (include_directory IN LISTS ARGN)
if(IS_ABSOLUTE ${include_directory})
list(APPEND include_directories ${include_directory})
continue()
endif()

if (include_directory MATCHES "^\\$<INSTALL_INTERFACE:.+>$")
# ignoring installation interfaces
continue()
endif()

if (include_directory MATCHES "^\\$<.+>$")
list(APPEND include_directories "$<$<BOOL:${include_directory}>:$<JOIN:${include_directory},$<COMMA>${_sonarcloud_newline}>>")
continue()
endif()

message(WARNING "Sonarcloud is missing include directory \"${include_directory}\" for target ${target}")
endforeach()

set(${output_variable} ${include_directories} PARENT_SCOPE)
endfunction()

function(_extract_sonarcloud_project_files output_project_source_files output_project_include_directories)
unset(project_source_files)
unset(project_include_directories)

foreach (target IN LISTS ARGN)
get_target_property(target_type ${target} TYPE)

unset(target_source_files)
unset(target_include_directories)

if (NOT target_type STREQUAL "INTERFACE_LIBRARY")
get_target_property(target_source_files ${target} SOURCES)
if (target_source_files)
_transform_sonarcloud_source_files(target_source_files ${target} ${target_source_files})
else()
unset(target_source_files)
endif()

get_target_property(target_include_directories ${target} INCLUDE_DIRECTORIES)
if (target_include_directories)
_transform_sonarcloud_include_directories(target_include_directories ${target} ${target_include_directories})
else()
unset(target_include_directories)
endif()
endif()

get_target_property(target_interface_include_directories ${target} INTERFACE_INCLUDE_DIRECTORIES)
if (target_interface_include_directories)
_transform_sonarcloud_include_directories(target_interface_include_directories ${target} ${target_interface_include_directories})
else()
unset(target_interface_include_directories)
endif()

list(APPEND project_source_files ${target_source_files})
list(APPEND project_include_directories ${target_include_directories})
list(APPEND project_include_directories ${target_interface_include_directories})
endforeach()

if (project_source_files)
list(SORT project_source_files)
list(REMOVE_DUPLICATES project_source_files)
endif()

if (project_include_directories)
list(SORT project_include_directories)
list(REMOVE_DUPLICATES project_include_directories)
endif()

set(${output_project_source_files} ${project_source_files} PARENT_SCOPE)
set(${output_project_include_directories} ${project_include_directories} PARENT_SCOPE)
endfunction()

function(generate_sonarcloud_project_properties sonarcloud_project_properties_path)
if (NOT IS_ABSOLUTE ${sonarcloud_project_properties_path})
message(FATAL_ERROR "Function \"generate_sonarcloud_project_properties\""
"only accepts absolute paths to avoid ambiguity")
endif()

if (NOT ${PROJECT_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR})
return()
endif()

swift_list_targets(source_targets
ONLY_THIS_REPO
SWIFT_TYPES
executable
library
)

swift_list_targets(test_targets
ONLY_THIS_REPO
SWIFT_TYPES
test
test_library
)

_extract_sonarcloud_project_files(source_source_files source_include_directories ${source_targets})
_extract_sonarcloud_project_files(test_source_files test_include_directories ${test_targets})

set(sonarcloud_project_properties_content "sonar.sourceEncoding=UTF-8\n")

set(source_files ${source_source_files} ${source_include_directories})
list(JOIN source_files ",${_sonarcloud_newline}" sonar_sources)
string(APPEND sonarcloud_project_properties_content "sonar.inclusions=${_sonarcloud_newline}${sonar_sources}\n")

set(test_files ${test_source_files})
list(JOIN test_files ",${_sonarcloud_newline}" sonar_tests)
string(APPEND sonarcloud_project_properties_content "sonar.tests.inclusions=${_sonarcloud_newline}${sonar_tests}\n")

file(GENERATE
OUTPUT "${sonarcloud_project_properties_path}"
CONTENT "${sonarcloud_project_properties_content}"
)

endfunction()
61 changes: 58 additions & 3 deletions SwiftTargets.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@
# C_EXTENSIONS_ON: C extensions are disabled by default, adding this keyword
# enables C extensions.
#
# SKIP_COMPILE_OPTIONS: will forgo invoking the swift_set_compile_options on
# the target.
#
# SINGLE VALUE KEYWORDS
#
# C_STANDARD: allow uses to override the default C standard used in a target,
Expand Down Expand Up @@ -156,6 +159,31 @@ define_property(TARGET
BRIEF_DOCS "Swift target type"
FULL_DOCS "For use by other modules in this repository which need to know the classification of target. One of executable, library, tool, tool_library, test, test_library")

define_property(TARGET
PROPERTY INTERFACE_SWIFT_TYPE
BRIEF_DOCS "Swift target type"
FULL_DOCS "Identical use as SWIFT_TYPE except that this applies to ALL target types, including INTERFACE")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

SWIFT_TYPE wasn't including interface libraries, this is a work around solution. See https://stackoverflow.com/questions/68502038/custom-properties-for-interface-libraries


define_property(TARGET
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 currently not using this property at the moment.

I originally implemented (accidentally) an alternative solution to swift_list_targets (only after reviewing my code today did I realize that that function already existed) which used a GLOBAL property to accumulate out all registered Swift executables/libraries/etc (see this.) I used this target property to filter the results for specific projects, which in this case did much what ONLY_THIS_REPO did, without using the third_party folder approach. Either way, I believe this might serve some use in the future and might not be bad for reporting purposes so I kept this around.

PROPERTY SWIFT_PROJECT
BRIEF_DOCS "Swift project name"
FULL_DOCS "For use by other modules in this repository which need to know the project which this target belongs to")

define_property(TARGET
PROPERTY INTERFACE_SWIFT_PROJECT
BRIEF_DOCS "Swift project name"
FULL_DOCS "Identical use as SWIFT_PROJECT except that this applies to ALL target types, including INTERFACE")

define_property(TARGET
PROPERTY SWIFT_TEST_TYPE
BRIEF_DOCS "Swift test type"
FULL_DOCS "When target's SWIFT_PROJECT property is \"test\", this option, if set, will identify what type of test it is. Currently support \"unit\" or \"integration\"")

define_property(TARGET
PROPERTY INTERFACE_SOURCE_DIR
BRIEF_DOCS "Target's source directory"
FULL_DOCS "Identical use as SOURCE_DIR except that this applies to ALL target types, including INTERFACE")

macro(swift_collate_arguments prefix name)
set(exclusion_list ${ARGN})
set(${name}_args "")
Expand All @@ -182,7 +210,7 @@ macro(swift_collate_arguments prefix name)
endmacro()

function(swift_add_target target type)
set(this_option INTERFACE OBJECT STATIC SHARED MODULE)
set(this_option INTERFACE OBJECT STATIC SHARED MODULE SKIP_COMPILE_OPTIONS)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This flag here is introduced to allow Orion + friends to use this function. To allow us to slowly transition all their code to use these swift_add_* function. Currently they are not as pedantic about things as we are in Starling

set(this_single "")
set(this_multi SOURCES)

Expand Down Expand Up @@ -288,11 +316,38 @@ function(swift_add_target target type)
message(FATAL_ERROR "Unknown Swift target type ${type}")
endif()

#
# This edge case is needed for cmake version < 3.19.0 where INTERFACE
# classes cannot contain any property other than those prefixed with
# "INTERFACE_".
#
# see: https://stackoverflow.com/questions/68502038/custom-properties-for-interface-libraries
#
# Until we migrate the cmake scripts to require 3.19.0, we should use the
# "INTERFACE_*" properties. If you want to go the extra mile, make sure to
# check both `INTERFACE_` and non `INTERFACE_` properties, later on we can
# delete the `INTERFACE_` once this illogical constraint is removed.
#
set_target_properties(${target}
PROPERTIES
INTERFACE_SWIFT_PROJECT ${PROJECT_NAME}
INTERFACE_SWIFT_TYPE ${type}
INTERFACE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}
)

if (NOT x_INTERFACE)
set_target_properties(${target} PROPERTIES SWIFT_TYPE ${type})
swift_set_compile_options(${target} ${compile_options_args} EXTRA_FLAGS ${extra_flags})
set_target_properties(${target}
PROPERTIES
SWIFT_PROJECT ${PROJECT_NAME}
SWIFT_TYPE ${type}
)

swift_set_language_standards(${target} ${language_standards_args})
target_code_coverage(${target} NO_RUN)

if (NOT x_SKIP_COMPILE_OPTIONS)
swift_set_compile_options(${target} ${compile_options_args} EXTRA_FLAGS ${extra_flags})
endif()
endif()
endfunction()

Expand Down
16 changes: 16 additions & 0 deletions TestTargets.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,18 @@ function(swift_add_test target)
add_dependencies(do-all-tests do-${target})
endif()

set_target_properties(${target}
PROPERTIES
SWIFT_PROJECT ${PROJECT_NAME}
INTERFACE_SWIFT_PROJECT ${PROJECT_NAME}
)

if (x_INTEGRATION_TEST)
set_target_properties(${target}
PROPERTIES
SWIFT_TEST_TYPE integration
)

if (NOT TARGET do-all-integration-tests)
add_custom_target(do-all-integration-tests)
endif()
Expand All @@ -345,6 +356,11 @@ function(swift_add_test target)
endif()

if (x_UNIT_TEST)
set_target_properties(${target}
PROPERTIES
SWIFT_TEST_TYPE unit
)

if (NOT TARGET do-all-unit-tests)
add_custom_target(do-all-unit-tests)
endif()
Expand Down