Skip to content
Open
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
117 changes: 117 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,123 @@ If you want to be looped into these syncs, please reach out to project leadershi

---

## Project Versioning

Project versioning will adhere to the principles of [Semantic Versioning (semver)](https://semver.org/):

> Given a version number MAJOR.MINOR.PATCH, increment the:
>
> 1. MAJOR version when you make incompatible API changes.
> 2. MINOR version when you add functionality in a backward compatible manner.
> 3. PATCH version when you make backward compatible bug fixes.
>
> Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.

### Symbol Visibility

Every software project should use the following CMake code in the top-level CMakeLists.txt after the first `project(...)` call before
any CMake build targets are created:

```cmake
set(CMAKE_C_VISIBILITY_PRESET "hidden")
set(CMAKE_CXX_VISIBILITY_PRESET "hidden")
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON)
```

This ensures that all shared object symbols have hidden visibility by default.
Any public API functions must then be explicitly exported. It is recommended for projects
to defined a project-specific preprocessor definition of the form: `<PROJECT_NAME>_API`.
Here is a [simplified sample derived from rocprofiler-sdk](projects/rocprofiler-sdk/source/include/rocprofiler-sdk/defines.h):

```cpp
// macro for attribute, useful since Windows uses __declspec
#define ROCPROFILER_SDK_ATTRIBUTE(...) __attribute__((__VA_ARGS__))

// always defined for export
#define ROCPROFILER_SDK_PUBLIC_API ROCPROFILER_SDK_ATTRIBUTE(visibility("default"))

// always defined for suppressing export
#define ROCPROFILER_SDK_HIDDEN_API ROCPROFILER_SDK_ATTRIBUTE(visibility("hidden"))

// See CMake code below for rocprofiler_sdk_EXPORTS
#if defined(rocprofiler_sdk_EXPORTS)
# define ROCPROFILER_SDK_API ROCPROFILER_SDK_PUBLIC_API
#else
# define ROCPROFILER_SDK_API
#endif
```

In the above code, we defined a `ROCPROFILER_SDK_PUBLIC_API` for exporting a symbol. However, we only
want these symbols to be marked as exported when _building_ the shared library -- when a user of the
API is including the project's headers and linking to the shared library, the symbol should not be marked as
exported[^1]. Thus, we define `rocprofiler_sdk_EXPORTS` when building the shared library -- with the expectation
that consumer software linking to the shared library will not define `rocprofiler_sdk_EXPORTS`.
CMake, by default, when building shared libraries, defines `<target-name>_EXPORTS`. It can be overridden via
the `DEFINE_SYMBOL` target property (which is useful overriding the output library name via the `OUTPUT_NAME` target property):

```cmake
set_target_properties(
rocprofiler-sdk-shared-library
PROPERTIES
OUTPUT_NAME rocprofiler-sdk
DEFINE_SYMBOL rocprofiler_sdk_EXPORTS
)
```

Alternatively, one can simply define it explicitly via target compile definitions (with special emphasis on the use of `PRIVATE`)
as needed:

```cmake
target_compile_definitions(
rocprofiler-sdk-object-library
PRIVATE
rocprofiler_sdk_EXPORTS=1
)
```

However, the `DEFINE_SYMBOL` approach is the preferred method since there is no chance of using `PUBLIC` or `INTERFACE` instead of `PRIVATE`.

[^1]: This is mostly a best practice on Linux, but is quite important on Windows, which requires symbols
to be marked as imported when defined in a external shared library.

#### Project Versioning File Structure

- `projects/<project-name>/VERSION`
- This file contains the major, minor, and patch version of the project on the first line.
- The VERSION in this file should be the sole source of truth regarding the version. Anything needing the version number should read from this file.
- It also contains, on the second line, `# hash: <md5sum>` which is a hash of the files at the time of the last version bump. More on this later.
- This file should be installed to `<prefix>/share/<project-name>/VERSION`
- `projects/<project-name>/versioning.yml`
- Every `VERSION` file should be accompanied by a `versioning.yml` file.
- This file contains the information needed for performing versioning checks and updating the version
- In general it has three sections per project: `source-tree`, `build-tree`, and `install-tree`.
- Each one of these sections defines the source files for generating the md5sum hash (`sources` and `headers`), the public API headers (`headers`), and which binary files should be checked for ABI and API changes (`abi-check`).
- This file should be installed to `<prefix>/share/<project-name>/VERSION`

#### Useful Utilities

- `cmake/rocm_versioning.cmake`
- This is a CMake module with various functions to assist with compliance to our versioning standards.
- `scripts/abi-guard.py`
- This is a Python3 command-line tool for generating/updating the project's VERSION file, generating a versioning YAML spec, listing the files included or excluded from API/ABI checks, executing API/ABI checks, etc.
A quick start guide is provided in [scripts/abi-guard-README.md](scripts/abi-guard-README.md).

#### Versioning Workflows

A reusable workflow is provided in `.github/workflows/abi-guard.yml`.
This workflow should be used to quickly integrate checks for versioning compliance.
In general, this workflow automates using [libabigail](https://man7.org/linux/man-pages/man7/libabigail.7.html) to detect changes in the API/ABI of a project.

- If this workflow detects added/removed functions/variables in the public API, it fails without at least a minor version bump.
- If this workflow detects changed functions in the public API, it strongly recommends an alternative approach since this breaks the ABI and/or ABI and, in general, a minor version bumps for a breaking change is not [Semantic Versioning (semver)](https://semver.org/) compliant.
- If this workflow detects no API/ABI breaks but the version remains unchanged, it recommends a patch version bump.
- Note: we probably want to be able to avoid this failing for documentation changes.

Usage of this workflow is added on a per-project basis at present. Ideally, this workflow will be automatically applied in the future.
[Sample using the abi-guard.yml workflow on rocprofiler-sdk project](.github/workflows/rocprofiler-sdk-abi-guard.yml).

---

## Integration with TheRock

[TheRock](https://github.com/rocm/therock) is our new open-source build system for ROCm. It is designed to significantly enhance our support and scalability for ROCm 7.0 and beyond, and it is actively welcoming community contributions. TheRock currently supports a subset of AMD GPU targets, with ongoing efforts from our team and the community to expand this further, as detailed in TheRock [roadmap](https://github.com/ROCm/TheRock/blob/main/ROADMAP.md).
Expand Down
148 changes: 148 additions & 0 deletions cmake/rocm_versioning.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
##########################################################################################
#
# Handles the versioning settings
#
##########################################################################################

include_guard(DIRECTORY)

##########################################################################################
#
# Standardizes the reading of version files
#
##########################################################################################

function(rocm_versioning_read_version_file PROJECT_NAME PROJECT_VERSION_PREFIX
VERSION_FILE VERSIONING_SPEC_FILE)
if(NOT EXISTS "${VERSION_FILE}" AND NOT EXISTS
"${CMAKE_CURRENT_LIST_DIR}/${VERSION_FILE}")
message(FATAL_ERROR "[${PROJECT_NAME}] Version file '${VERSION_FILE}' not found.")
elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${VERSION_FILE}")
set(VERSION_FILE "${CMAKE_CURRENT_LIST_DIR}/${VERSION_FILE}")
endif()

if(NOT EXISTS "${VERSIONING_SPEC_FILE}"
AND NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/${VERSIONING_SPEC_FILE}")
message(
FATAL_ERROR
"[${PROJECT_NAME}] Versioning YAML spec file '${VERSIONING_SPEC_FILE}' not found."
)
elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${VERSIONING_SPEC_FILE}")
set(VERSIONING_SPEC_FILE "${CMAKE_CURRENT_LIST_DIR}/${VERSIONING_SPEC_FILE}")
endif()

file(READ "${VERSIONING_SPEC_FILE}" FULL_VERSIONING_SPEC_STRING)

string(STRIP "${FULL_VERSIONING_SPEC_STRING}" FULL_VERSIONING_SPEC_STRING)
string(REGEX REPLACE ".*(name:)([ \t\"]*)([a-zA-Z0-9_-]+)([ \t\"]*)(.*)" "\\3"
PARSED_PROJECT_NAME "${FULL_VERSIONING_SPEC_STRING}")

if(NOT PARSED_PROJECT_NAME STREQUAL PROJECT_NAME)
message(
FATAL_ERROR
"[${PROJECT_NAME}] Mismatched project name in versioning spec file '${VERSIONING_SPEC_FILE}': "
"expected '${PROJECT_NAME}', got '${PARSED_PROJECT_NAME}'")
endif()

macro(_rocm_versioning_set _VAR _VALUE)
if("GLOBAL" IN_LIST ARGN)
set(${_VAR}
"${_VALUE}"
CACHE STRING "version variable for project ${PROJECT_NAME}" FORCE)
else()
set(${_VAR}
"${_VALUE}"
PARENT_SCOPE)
endif()
endmacro()

file(READ "${VERSION_FILE}" FULL_VERSION_STRING)
string(STRIP "${FULL_VERSION_STRING}" FULL_VERSION_STRING)
string(REGEX REPLACE "([0-9]+)\.([0-9]+)\.([0-9]+)(.*)" "\\1.\\2.\\3"
PARSED_FULL_VERSION_STRING "${FULL_VERSION_STRING}")
string(REGEX REPLACE "([0-9]+)\.([0-9]+)\.([0-9]+)(.*)" "\\1"
PARSED_MAJOR_VERSION_STRING "${FULL_VERSION_STRING}")
string(REGEX REPLACE "([0-9]+)\.([0-9]+)\.([0-9]+)(.*)" "\\2"
PARSED_MINOR_VERSION_STRING "${FULL_VERSION_STRING}")
string(REGEX REPLACE "([0-9]+)\.([0-9]+)\.([0-9]+)(.*)" "\\3"
PARSED_PATCH_VERSION_STRING "${FULL_VERSION_STRING}")

_rocm_versioning_set(${PROJECT_VERSION_PREFIX}_VERSION_MAJOR
"${PARSED_MAJOR_VERSION_STRING}")
_rocm_versioning_set(${PROJECT_VERSION_PREFIX}_VERSION_MINOR
"${PARSED_MINOR_VERSION_STRING}")
_rocm_versioning_set(${PROJECT_VERSION_PREFIX}_VERSION_PATCH
"${PARSED_PATCH_VERSION_STRING}")
_rocm_versioning_set(
${PROJECT_VERSION_PREFIX}_VERSION
"${PARSED_MAJOR_VERSION_STRING}.${PARSED_MINOR_VERSION_STRING}.${PARSED_PATCH_VERSION_STRING}"
)

if(FULL_VERSION_STRING MATCHES
"([0-9]+)\.([0-9]+)\.([0-9]+)([a-zA-Z0-9\\.\\-]+)(\n|\r)(.*)")
string(REGEX REPLACE "([0-9]+)\.([0-9]+)\.([0-9]+)([a-zA-Z0-9\\.\\-]+)(\n|\r)(.*)"
"\\4" PARSED_BUILD_VERSION_STRING "${FULL_VERSION_STRING}")

if(NOT PARSED_BUILD_VERSION_STRING STREQUAL "")
string(REGEX REPLACE "^(\\.|\\-)(.*)" "\\2" PARSED_BUILD_VERSION_STRING
"${PARSED_BUILD_VERSION_STRING}")
endif()
else()
_rocm_versioning_set(PARSED_BUILD_VERSION_STRING "")
endif()

if(FULL_VERSION_STRING MATCHES "# hash: ([a-fA-F0-9]+)")
string(REGEX REPLACE "(.*)# hash: ([a-fA-F0-9]+)(.*)" "\\2"
PARSED_HASH_VERSION_STRING "${FULL_VERSION_STRING}")
else()
_rocm_versioning_set(PARSED_HASH_VERSION_STRING "")
endif()

_rocm_versioning_set(${PROJECT_VERSION_PREFIX}_VERSION_BUILD
"${PARSED_BUILD_VERSION_STRING}")

_rocm_versioning_set(
${PROJECT_VERSION_PREFIX}_FULL_VERSION_STRING
"${PARSED_MAJOR_VERSION_STRING}.${PARSED_MINOR_VERSION_STRING}.${PARSED_PATCH_VERSION_STRING}${PARSED_BUILD_VERSION_STRING}"
)

_rocm_versioning_set(${PROJECT_VERSION_PREFIX}_VERSION_HASH
"${PARSED_HASH_VERSION_STRING}")
endfunction()

##########################################################################################
#
# Copies the version files to the build directory
#
##########################################################################################

function(rocm_versioning_configure_version_files)
cmake_parse_arguments(_ARG "" "DESTINATION" "FILES" ${ARGN})

if(NOT _ARG_DESTINATION)
set(_ARG_DESTINATION "${PROJECT_BINARY_DIR}")
endif()

foreach(_FILE ${_ARG_FILES})
configure_file("${_FILE}" "${_ARG_DESTINATION}/${_FILE}" COPYONLY)
endforeach()
endfunction()

##########################################################################################
#
# Ensures the installation of version files
#
##########################################################################################

function(rocm_versioning_install_version_files PROJECT_NAME)
if(NOT CMAKE_INSTALL_DATAROOTDIR)
include(GNUInstallDirs)
endif()

cmake_parse_arguments(_ARG "" "COMPONENT" "FILES" ${ARGN})

install(
FILES ${_ARG_FILES}
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}"
COMPONENT ${_ARG_COMPONENT})
endfunction()
27 changes: 19 additions & 8 deletions projects/rocprofiler-sdk/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#
cmake_minimum_required(VERSION 3.21 FATAL_ERROR)

if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR AND CMAKE_CURRENT_SOURCE_DIR STREQUAL
Expand All @@ -9,11 +10,6 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR AND CMAKE_CURRENT_SOURCE_DIR STREQ
message(AUTHOR_WARNING "In-source build")
endif()

file(READ "${CMAKE_CURRENT_SOURCE_DIR}/VERSION" FULL_VERSION_STRING LIMIT_COUNT 1)
string(REGEX REPLACE "(\n|\r)" "" FULL_VERSION_STRING "${FULL_VERSION_STRING}")
string(REGEX REPLACE "([0-9]+)\.([0-9]+)\.([0-9]+)(.*)" "\\1.\\2.\\3"
ROCPROFILER_SDK_VERSION "${FULL_VERSION_STRING}")

foreach(_LANG C CXX)
set(CMAKE_${_LANG}_FLAGS_COVERAGE_INIT
"-Og -g3 -fno-omit-frame-pointer -fprofile-abs-path -fprofile-arcs -ftest-coverage --coverage -DCODECOV=1"
Expand All @@ -23,6 +19,13 @@ foreach(_LANG C CXX)
CACHE STRING "${_LANG} flags for code coverage builds")
endforeach()

set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/../../cmake ${CMAKE_MODULE_PATH})
include(rocm_versioning)

rocm_versioning_read_version_file(
rocprofiler-sdk ROCPROFILER_SDK ${CMAKE_CURRENT_LIST_DIR}/VERSION
${CMAKE_CURRENT_LIST_DIR}/versioning.yml)

project(
rocprofiler-sdk
LANGUAGES C CXX
Expand Down Expand Up @@ -67,15 +70,23 @@ else()
set(ROCPROFILER_SDK_GIT_REVISION "")
endif()

# for backward compatibility
set(FULL_VERSION_STRING "${ROCPROFILER_SDK_FULL_VERSION_STRING}")
# for SOVERSION
set(ROCPROFILER_SDK_SOVERSION "${ROCPROFILER_SDK_VERSION_MAJOR}")

# make sure that cmake re-runs when version file changes
configure_file(${PROJECT_SOURCE_DIR}/VERSION ${PROJECT_BINARY_DIR}/VERSION COPYONLY)
rocm_versioning_configure_version_files(FILES ${PROJECT_SOURCE_DIR}/VERSION
${PROJECT_SOURCE_DIR}/versioning.yml)

message(
STATUS
"[${PROJECT_NAME}] version ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH} (${FULL_VERSION_STRING})"
)
message(STATUS "[${PROJECT_NAME}] git revision: ${ROCPROFILER_SDK_GIT_REVISION}")
message(STATUS "[${PROJECT_NAME}] git describe: ${ROCPROFILER_SDK_GIT_DESCRIBE}")
message(STATUS "[${PROJECT_NAME}] git revision: ${ROCPROFILER_SDK_GIT_REVISION}")
message(STATUS "[${PROJECT_NAME}] git describe: ${ROCPROFILER_SDK_GIT_DESCRIBE}")
message(STATUS "[${PROJECT_NAME}] build version: ${ROCPROFILER_SDK_VERSION_BUILD}")
message(STATUS "[${PROJECT_NAME}] version hash: ${ROCPROFILER_SDK_VERSION_HASH}")
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake ${PROJECT_SOURCE_DIR}/cmake/Modules
${CMAKE_MODULE_PATH})

Expand Down
1 change: 1 addition & 0 deletions projects/rocprofiler-sdk/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
1.1.0
# hash: 0c31e408a478f5550ab521e89fe5b2e7
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ rocprofiler_install_env_setup_files(
INSTALL_DIR ${CMAKE_INSTALL_DATAROOTDIR}
COMPONENT development)

rocm_versioning_install_version_files(rocprofiler-sdk FILES ${PROJECT_SOURCE_DIR}/VERSION
${PROJECT_SOURCE_DIR}/versioning.yml COMPONENT core)

function(compute_rocprofiler_sdk_version _VAR)
string(REGEX REPLACE "([0-9]+)\\\.([0-9]+)\\\.(.*)" "\\1.\\2" _TMP "${${_VAR}}")
set(PACKAGE_${_VAR}
Expand Down
Loading
Loading