Skip to content
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
245 changes: 245 additions & 0 deletions .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
name: Build

# Builds HPTT on Linux, macOS and Windows, on both x86-64 and ARM64, and
# then consumes it from a separate project through
# `find_package(hptt CONFIG)` -- once against the build tree and once
# against an install prefix. The consumer transposes random tensors and
# compares against a naive reference, so a green job means the exported
# package links *and* the library computes correct results on that
# platform.

on:
# No `branches:` filter: a pull request targeting any branch is built,
# which is what makes stacked PRs (one PR based on another's branch)
# get checked too.
pull_request:
push:
branches:
- main
workflow_dispatch:

concurrency:
# One group per PR, so pushing a new commit cancels the runs still
# queued or in flight for the previous one. Pushes to main are keyed by
# run_id instead and never cancel each other -- every merged commit
# keeps its own result.
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
build:
name: ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
strategy:
# One platform failing should not hide the results of the others.
fail-fast: false
matrix:
# arch_flags selects HPTT's SIMD kernels. The ARM entries pass
# ENABLE_ARM=ON so the Neon micro-kernels in src/transpose.cpp are
# actually compiled and run, rather than silently falling back to
# the generic scalar path that the default configuration selects on
# those targets.
include:
- name: linux-x64
runner: ubuntu-latest
arch_flags: ""
- name: linux-arm64
runner: ubuntu-24.04-arm
Comment thread
IvanaGyro marked this conversation as resolved.
arch_flags: "-DENABLE_ARM=ON"
- name: macos-x64
runner: macos-15-intel
arch_flags: ""
- name: macos-arm64
runner: macos-latest
arch_flags: "-DENABLE_ARM=ON"
- name: windows-x64
runner: windows-latest
msvc_arch: x64
arch_flags: ""
- name: windows-arm64
runner: windows-11-arm
msvc_arch: arm64
arch_flags: "-DENABLE_ARM=ON"

defaults:
run:
# bash everywhere, including Windows, so one set of steps covers
# all six jobs. -e / -o pipefail restore the fail-fast behaviour
# that overriding `shell:` would otherwise drop.
shell: bash -eo pipefail {0}

env:
BUILD_DIR: ${{ github.workspace }}/build
INSTALL_DIR: ${{ github.workspace }}/install
CONSUMER_SRC_DIR: ${{ github.workspace }}/tests/downstream_find_package

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

# Re-running a PR job reuses the merge commit built when the run was
# first created, not a fresh merge with the current target branch.
# Merging here keeps the job honest about the branch as it stands now.
# A committer identity is required because a non-fast-forward merge
# creates a merge commit.
- name: Merge with latest target branch (pull_request only)
if: github.event_name == 'pull_request'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git fetch origin ${{ github.event.pull_request.base.ref }}
git merge --no-edit origin/${{ github.event.pull_request.base.ref }}

# AppleClang ships no OpenMP runtime, so find_package(OpenMP REQUIRED)
# fails on macOS without libomp. OpenMP_ROOT is passed at configure
# time below.
- name: Install libomp (macOS)
if: runner.os == 'macOS'
run: brew install libomp

# The runner images do not all ship Ninja, and the ones that do are
# not consistent about it across architectures.
- name: Install Ninja
run: |
if command -v ninja >/dev/null 2>&1; then
ninja --version
exit 0
fi
case "$RUNNER_OS" in
Linux) sudo apt-get update && sudo apt-get install -y ninja-build ;;
macOS) brew install ninja ;;
Windows) choco install ninja -y --no-progress ;;
esac
ninja --version

# Puts cl.exe, the Windows SDK headers and the import libraries on
# PATH/INCLUDE/LIB. The Ninja generator, unlike the Visual Studio
# one, does not locate the toolchain by itself. Ninja itself is
# preinstalled on the Windows runner images.
- name: Set up MSVC (Windows)
if: runner.os == 'Windows'
uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.msvc_arch }}

# Installs ccache for the current OS and restores/saves its cache.
# The key is per matrix job so the six platforms never share objects.
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ matrix.name }}
max-size: 500M

- name: Configure HPTT
# ccache is wired in through CMake's compiler-launcher hook rather
# than by shadowing the compiler on PATH, which keeps working with
# the absolute compiler paths CMake records.
#
# CMP0141 + MSVC_DEBUG_INFORMATION_FORMAT=Embedded put debug info
# in the object files (/Z7). ccache cannot cache compilations that
# write to a shared .pdb, so without this every Windows compile is
# a cache miss.
run: |
EXTRA=()
if [ "$RUNNER_OS" = "macOS" ]; then
EXTRA+=("-DOpenMP_ROOT=$(brew --prefix libomp)")
fi
if [ "$RUNNER_OS" = "Windows" ]; then
EXTRA+=(-DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl)
EXTRA+=(-DCMAKE_POLICY_DEFAULT_CMP0141=NEW)
EXTRA+=(-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=Embedded)
fi
cmake -S . -B "$BUILD_DIR" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$INSTALL_DIR" \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
${{ matrix.arch_flags }} \
"${EXTRA[@]}"

- name: Build HPTT
run: cmake --build "$BUILD_DIR"

# --- Downstream consumer: build tree ------------------------------
# export(TARGETS ...) plus the build-dir hpttConfig.cmake make the
# package resolvable before anything is installed; hptt_DIR points
# find_package straight at the build directory.
- name: Consumer against the BUILD TREE
# OpenMP_ROOT is needed here as well as when building HPTT:
# hpttConfig.cmake re-runs find_dependency(OpenMP) in the
# consumer's scope, because OpenMP::OpenMP_CXX is a PUBLIC link
# dependency of the hptt targets. On AppleClang that resolves only
# with a pointer at the brew libomp.
run: |
EXTRA=()
[ "$RUNNER_OS" = "Windows" ] && EXTRA+=(-DCMAKE_CXX_COMPILER=cl)
[ "$RUNNER_OS" = "macOS" ] && EXTRA+=("-DOpenMP_ROOT=$(brew --prefix libomp)")
cmake -S "$CONSUMER_SRC_DIR" -B "${{ github.workspace }}/consumer-build-tree" \
-G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-Dhptt_DIR="$BUILD_DIR" "${EXTRA[@]}"
Comment thread
IvanaGyro marked this conversation as resolved.
cmake --build "${{ github.workspace }}/consumer-build-tree"

- name: Run BUILD TREE consumer
run: |
cd "${{ github.workspace }}/consumer-build-tree"
./consumer
./consumer_dyn

# --- Downstream consumer: install tree ----------------------------
- name: Install HPTT
run: cmake --install "$BUILD_DIR"

- name: Consumer against the INSTALL TREE
# A fresh build directory resolving the package from the install
# prefix through CMAKE_PREFIX_PATH, the way a real downstream user
# would find an installed HPTT.
run: |
EXTRA=()
[ "$RUNNER_OS" = "Windows" ] && EXTRA+=(-DCMAKE_CXX_COMPILER=cl)
[ "$RUNNER_OS" = "macOS" ] && EXTRA+=("-DOpenMP_ROOT=$(brew --prefix libomp)")
cmake -S "$CONSUMER_SRC_DIR" -B "${{ github.workspace }}/consumer-install-tree" \
-G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_PREFIX_PATH="$INSTALL_DIR" "${EXTRA[@]}"
cmake --build "${{ github.workspace }}/consumer-install-tree"

- name: Run INSTALL TREE consumer
run: |
cd "${{ github.workspace }}/consumer-install-tree"
./consumer
./consumer_dyn

# Guards the artifact-naming rule the Windows build depends on: the
# static library and the DLL's import library must never claim the
# same path. The Ninja generator only rejects the collision when both
# targets are enabled, which is the configuration built above.
- name: Check Windows artifact names are distinct
if: runner.os == 'Windows'
run: |
test -f "$INSTALL_DIR/lib/hptt.lib"
test -f "$INSTALL_DIR/lib/hptt_dyn.lib"
test -f "$INSTALL_DIR/bin/hptt.dll"

# HPTT_BUILD_SHARED=OFF has to keep configuring, building and
# exporting on its own -- it is how a parent project that links only
# the static archive embeds HPTT.
- name: Build with HPTT_BUILD_SHARED=OFF
run: |
EXTRA=()
if [ "$RUNNER_OS" = "macOS" ]; then
EXTRA+=("-DOpenMP_ROOT=$(brew --prefix libomp)")
fi
if [ "$RUNNER_OS" = "Windows" ]; then
EXTRA+=(-DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl)
EXTRA+=(-DCMAKE_POLICY_DEFAULT_CMP0141=NEW)
EXTRA+=(-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=Embedded)
fi
cmake -S . -B "${{ github.workspace }}/build-static-only" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DHPTT_BUILD_SHARED=OFF \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
"${EXTRA[@]}"
cmake --build "${{ github.workspace }}/build-static-only"
18 changes: 13 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
elseif(NOT ENABLE_IBM)
if(FINE_TUNE)
list(APPEND HPTT_PRIVATE_COMPILE_OPTIONS -march=native -mtune=native)
else()
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64|i[3-6]86)$")
# `x86-64` is the baseline x86 -march value. It is not a valid
# -march anywhere else -- on aarch64 GCC rejects it outright
# with "unknown value 'x86-64' for '-march'" -- so other
# architectures take the compiler's own default.
list(APPEND HPTT_PRIVATE_COMPILE_OPTIONS -march=x86-64 -mtune=generic)
endif()
endif()
Expand All @@ -82,10 +86,14 @@ if(ENABLE_AVX)
endif()
list(APPEND HPTT_PUBLIC_COMPILE_DEFINITIONS HPTT_ARCH_AVX)
elseif(ENABLE_ARM)
# MSVC targeting ARM64 needs no opt-in flag: Advanced SIMD (Neon) is
# part of the baseline ISA there, and cl's /arch: only selects newer
# extensions.
if(NOT MSVC)
# `-mfpu=neon` is a GCC/Clang spelling, and it exists only for 32-bit
# ARM targets. Both halves of the condition are needed, for separate
# reasons: MSVC has no `-mfpu` option on any target, and a 64-bit ARM
# target has Advanced SIMD in its baseline ISA with no `-mfpu` to
# select it (GCC on aarch64 errors with "unknown value 'neon'").
# HPTT_ARCH_ARM below is what actually selects the Neon kernels.
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang"
AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "(aarch64|arm64|ARM64)")
list(APPEND HPTT_PRIVATE_COMPILE_OPTIONS -mfpu=neon)
endif()
list(APPEND HPTT_PUBLIC_COMPILE_DEFINITIONS HPTT_ARCH_ARM)
Expand Down
31 changes: 31 additions & 0 deletions tests/downstream_find_package/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Standalone consumer used by .github/workflows/ci-build.yml.
#
# It is deliberately *not* part of the top-level HPTT build: it resolves
# HPTT the way a real downstream project does, through
# `find_package(hptt CONFIG)` and the `hptt::` imported targets, so it
# exercises the generated hpttConfig.cmake / hpttTargets.cmake rather than
# the in-tree target names. The workflow configures it twice, once against
# the build tree (via hptt_DIR) and once against an install prefix (via
# CMAKE_PREFIX_PATH).
cmake_minimum_required(VERSION 3.21)
project(hptt_downstream_consumer LANGUAGES CXX)

find_package(hptt CONFIG REQUIRED)

add_executable(consumer consumer.cpp)
target_link_libraries(consumer PRIVATE hptt::hptt_static)

# hptt::hptt_dyn only exists when HPTT was built with HPTT_BUILD_SHARED=ON,
# so a static-only HPTT still configures here.
if(TARGET hptt::hptt_dyn)
add_executable(consumer_dyn consumer.cpp)
target_link_libraries(consumer_dyn PRIVATE hptt::hptt_dyn)

# On Windows the DLL has to sit next to the executable to be found at
# run time. TARGET_RUNTIME_DLLS is empty on platforms that use rpath,
# so the command is skipped there rather than guarded on WIN32.
add_custom_command(TARGET consumer_dyn POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<BOOL:$<TARGET_RUNTIME_DLLS:consumer_dyn>>,copy,true>
$<TARGET_RUNTIME_DLLS:consumer_dyn> $<TARGET_FILE_DIR:consumer_dyn>
COMMAND_EXPAND_LISTS)
endif()
Loading
Loading