Skip to content

Commit b99229c

Browse files
committed
🚸 add explicit target for template generation
Signed-off-by: burgholzer <burgholzer@me.com>
1 parent be88fb6 commit b99229c

File tree

5 files changed

+225
-80
lines changed

5 files changed

+225
-80
lines changed

‎.github/workflows/reusable-template-ci.yml‎

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,17 @@ jobs:
2020
- name: Configure template
2121
working-directory: qdmi
2222
run: >
23-
cmake -DCONFIGURE_TEMPLATE=ON \
23+
cmake -DQDMI_GENERATE_TEMPLATE=ON \
2424
-DTEMPLATE_PREFIX="MQSS" \
25-
-DTEMPLATE_PATH="$GITHUB_WORKSPACE/template" \
25+
-DTEMPLATE_PATH="$GITHUB_WORKSPACE" \
2626
-S . -B build
27+
- name: Generate template
28+
working-directory: qdmi
29+
run: cmake --build build --target qdmi-template
30+
- name: Delete source repository and build directory
31+
run: |
32+
rm -rf qdmi build
2733
- name: Initialize as git repository (with main as branch name) and commit
28-
working-directory: template
2934
run: |
3035
git config --global init.defaultBranch main
3136
git config --global user.name "GitHub Actions"
@@ -34,7 +39,6 @@ jobs:
3439
git add .
3540
git commit -m "🎉 Initial commit"
3641
- name: Build template
37-
working-directory: template
3842
run: |
3943
cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_MQSS_QDMI_TESTS=ON
4044
cmake --build build --config Release
@@ -43,15 +47,11 @@ jobs:
4347
with:
4448
version: "1.15.0"
4549
- name: Build docs
46-
working-directory: template
4750
run: |
4851
cmake -G Ninja -S . -B build_docs -DCMAKE_BUILD_TYPE=Release -DBUILD_MQSS_QDMI_DOCS=ON -DBUILD_MQSS_QDMI_TESTS=OFF
4952
cmake --build build_docs --config Release --target mqss_qdmi_device_docs
5053
- uses: pypa/cibuildwheel@63fd63b352a9a8bdcc24791c9dbee952ee9a8abc # v3.3.0
51-
with:
52-
package-dir: template
5354
- name: Verify clean directory
54-
working-directory: template
5555
run: git diff --exit-code
5656
- name: Upload wheels
5757
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
@@ -64,15 +64,12 @@ jobs:
6464
enable-cache: true
6565
save-cache: ${{ github.ref == 'refs/heads/main' }}
6666
- name: Build SDist
67-
working-directory: template
6867
run: uv build --sdist
6968
- name: Check metadata
70-
working-directory: template
7169
run: uvx twine check dist/*
7270
- name: Run check-sdist
73-
working-directory: template
7471
run: uvx check-sdist --inject-junk
7572
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
7673
with:
7774
name: ${{ github.event_name == 'pull_request' && 'dev-' || '' }}cibw-sdist
78-
path: template/dist/*.tar.gz
75+
path: dist/*.tar.gz

‎UPGRADING.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ In addition to these basic changes, the template has been extended to include:
4444

4545
The corresponding documentation has been updated to reflect these changes.
4646

47+
In addition, the template instantiation workflow has changed: the template files are no longer
48+
written as a side effect of the CMake configure step. Instead, configuration only defines the
49+
prefix and output path, and the actual file generation happens when the explicit build target
50+
`qdmi-template` is invoked (use `qdmi-template-force` to overwrite an existing output directory).
51+
4752
## [1.2.0] - 2025-12-01
4853

4954
Version 1.2.0 introduces several breaking changes, primarily related to type system improvements,
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# Copyright (c) 2024 - 2025 Munich Quantum Software Stack Project
2+
# All rights reserved.
3+
#
4+
# Licensed under the Apache License v2.0 with LLVM Exceptions (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# https://github.com/Munich-Quantum-Software-Stack/QDMI/blob/develop/LICENSE.md
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
# License for the specific language governing permissions and limitations under
14+
# the License.
15+
#
16+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
17+
18+
# Script-mode generator for the QDMI device template.
19+
#
20+
# Expected variables (passed via -D...): - QDMI_TEMPLATE_SOURCE_DIR: path to
21+
# templates/device - QDMI_TEMPLATE_OUTPUT_DIR: output directory to
22+
# create/populate - QDMI_TEMPLATE_PREFIX: uppercase prefix, e.g. "ABC" -
23+
# QDMI_TEMPLATE_FORCE: ON/OFF, overwrite destination if it already exists
24+
25+
cmake_minimum_required(VERSION 3.24)
26+
27+
foreach(var IN ITEMS QDMI_TEMPLATE_SOURCE_DIR QDMI_TEMPLATE_OUTPUT_DIR
28+
QDMI_TEMPLATE_PREFIX)
29+
if(NOT DEFINED ${var} OR "${${var}}" STREQUAL "")
30+
message(FATAL_ERROR "[qdmi-template] Missing required -D${var}=...")
31+
endif()
32+
endforeach()
33+
34+
set(QDMI_TEMPLATE_FORCE "${QDMI_TEMPLATE_FORCE}")
35+
if("${QDMI_TEMPLATE_FORCE}" STREQUAL "")
36+
set(QDMI_TEMPLATE_FORCE OFF)
37+
endif()
38+
39+
file(REAL_PATH "${QDMI_TEMPLATE_SOURCE_DIR}" QDMI_TEMPLATE_SOURCE_DIR)
40+
file(REAL_PATH "${QDMI_TEMPLATE_OUTPUT_DIR}" QDMI_TEMPLATE_OUTPUT_DIR)
41+
42+
string(TOLOWER "${QDMI_TEMPLATE_PREFIX}" QDMI_TEMPLATE_prefix)
43+
44+
if(NOT EXISTS "${QDMI_TEMPLATE_SOURCE_DIR}")
45+
message(
46+
FATAL_ERROR
47+
"[qdmi-template] Source directory does not exist: ${QDMI_TEMPLATE_SOURCE_DIR}"
48+
)
49+
endif()
50+
51+
# Destination handling
52+
if(EXISTS "${QDMI_TEMPLATE_OUTPUT_DIR}")
53+
if(QDMI_TEMPLATE_FORCE)
54+
message(
55+
STATUS
56+
"[qdmi-template] Removing existing output dir: ${QDMI_TEMPLATE_OUTPUT_DIR}"
57+
)
58+
file(REMOVE_RECURSE "${QDMI_TEMPLATE_OUTPUT_DIR}")
59+
else()
60+
message(
61+
FATAL_ERROR
62+
"[qdmi-template] Output directory already exists: ${QDMI_TEMPLATE_OUTPUT_DIR}. "
63+
"Re-run with -DQDMI_TEMPLATE_FORCE=ON to overwrite.")
64+
endif()
65+
endif()
66+
67+
message(
68+
STATUS
69+
"[qdmi-template] Generating device template for prefix '${QDMI_TEMPLATE_PREFIX}' in '${QDMI_TEMPLATE_OUTPUT_DIR}'"
70+
)
71+
72+
# Copy directory skeleton first
73+
file(MAKE_DIRECTORY "${QDMI_TEMPLATE_OUTPUT_DIR}")
74+
75+
# Collect all files under the template source dir, but only regular files.
76+
file(
77+
GLOB_RECURSE _files
78+
LIST_DIRECTORIES false
79+
"${QDMI_TEMPLATE_SOURCE_DIR}/*")
80+
81+
# Apply substitutions file-by-file.
82+
foreach(_src IN LISTS _files)
83+
file(RELATIVE_PATH _rel "${QDMI_TEMPLATE_SOURCE_DIR}" "${_src}")
84+
get_filename_component(_rel_dir "${_rel}" DIRECTORY)
85+
get_filename_component(_name "${_rel}" NAME)
86+
87+
# Rename path segments / filenames similar to the previous configure-time
88+
# logic.
89+
set(_dest_rel_dir "${_rel_dir}")
90+
set(_dest_name "${_name}")
91+
92+
# Directory-level rename: python/my -> python/<prefix>
93+
string(REPLACE "python/my" "python/${QDMI_TEMPLATE_prefix}" _dest_rel_dir
94+
"${_dest_rel_dir}")
95+
96+
# Filename renames that include the placeholder
97+
string(REPLACE "my_" "${QDMI_TEMPLATE_prefix}_" _dest_name "${_dest_name}")
98+
string(REPLACE "my-" "${QDMI_TEMPLATE_prefix}-" _dest_name "${_dest_name}")
99+
string(REPLACE "my.qdmi" "${QDMI_TEMPLATE_prefix}.qdmi" _dest_name
100+
"${_dest_name}")
101+
102+
set(_dest "${QDMI_TEMPLATE_OUTPUT_DIR}/${_dest_rel_dir}/${_dest_name}")
103+
get_filename_component(_dest_dir "${_dest}" DIRECTORY)
104+
file(MAKE_DIRECTORY "${_dest_dir}")
105+
106+
# Read content and replace placeholders.
107+
file(READ "${_src}" _content)
108+
109+
# Be conservative: these are all specific placeholders used by the shipped
110+
# template.
111+
string(
112+
REGEX
113+
REPLACE "set\\(QDMI_PREFIX \"MY\"\\)"
114+
"set(QDMI_PREFIX \"${QDMI_TEMPLATE_PREFIX}\")" _content
115+
"${_content}")
116+
string(REPLACE "MY_" "${QDMI_TEMPLATE_PREFIX}_" _content "${_content}")
117+
string(REPLACE "MY QDMI" "${QDMI_TEMPLATE_PREFIX} QDMI" _content
118+
"${_content}")
119+
string(REPLACE "my_" "${QDMI_TEMPLATE_prefix}_" _content "${_content}")
120+
string(REPLACE "my-" "${QDMI_TEMPLATE_prefix}-" _content "${_content}")
121+
string(REPLACE "python/my" "python/${QDMI_TEMPLATE_prefix}" _content
122+
"${_content}")
123+
string(REPLACE "my/qdmi" "${QDMI_TEMPLATE_prefix}/qdmi" _content
124+
"${_content}")
125+
string(REPLACE "my-qdmi" "${QDMI_TEMPLATE_prefix}-qdmi" _content
126+
"${_content}")
127+
string(REPLACE "my.qdmi" "${QDMI_TEMPLATE_prefix}.qdmi" _content
128+
"${_content}")
129+
string(REPLACE "\"my\"" "\"${QDMI_TEMPLATE_PREFIX}\"" _content "${_content}")
130+
131+
file(WRITE "${_dest}" "${_content}")
132+
endforeach()
133+
134+
# Explicit folder/file renames that can't be expressed purely via filename
135+
# mapping above. (These are safe no-ops if the file doesn't exist in future
136+
# template revisions.)
137+
if(EXISTS "${QDMI_TEMPLATE_OUTPUT_DIR}/python/my")
138+
file(RENAME "${QDMI_TEMPLATE_OUTPUT_DIR}/python/my"
139+
"${QDMI_TEMPLATE_OUTPUT_DIR}/python/${QDMI_TEMPLATE_prefix}")
140+
endif()
141+
142+
foreach(
143+
_p IN
144+
ITEMS
145+
"src/my_device.cpp;src/${QDMI_TEMPLATE_prefix}_device.cpp"
146+
"test/test_my_device.cpp;test/test_${QDMI_TEMPLATE_prefix}_device.cpp"
147+
"cmake/my-qdmi-device-config.cmake.in;cmake/${QDMI_TEMPLATE_prefix}-qdmi-device-config.cmake.in"
148+
"docs/_static/my_logo.svg;docs/_static/${QDMI_TEMPLATE_prefix}_logo.svg"
149+
"docs/_static/my_logo_dark.svg;docs/_static/${QDMI_TEMPLATE_prefix}_logo_dark.svg"
150+
)
151+
string(REPLACE ";" ";" _p "${_p}")
152+
list(GET _p 0 _from)
153+
list(GET _p 1 _to)
154+
if(EXISTS "${QDMI_TEMPLATE_OUTPUT_DIR}/${_from}")
155+
file(RENAME "${QDMI_TEMPLATE_OUTPUT_DIR}/${_from}"
156+
"${QDMI_TEMPLATE_OUTPUT_DIR}/${_to}")
157+
endif()
158+
endforeach()
159+
160+
message(STATUS "[qdmi-template] Generation done")

‎docs/templates.md‎

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,31 @@ The following sections describe how to set up and use the template.
1212
## Creating a new Project {#template-create}
1313

1414
The code for the template is contained in the `template/` directory of the QDMI repository. To start
15-
a new project based on the template, perform the following CMake command from the main project
16-
directory. Keep in mind to replace the prefix and the path as desired. To follow the naming
17-
conventions in QDMI, pick a prefix with uppercase letters.
15+
a new project based on the template, configure QDMI once to define the prefix and output path, then
16+
explicitly build the `qdmi-template` target that writes the files.
1817

1918
\note An internet connection is needed for this step as the QDMI repository will be fetched from
2019
GitHub.
2120

2221
```sh
23-
cmake -DCONFIGURE_TEMPLATE=ON \ # activate template creation
24-
-DTEMPLATE_PREFIX="PREFIX" \ # set your prefix here
25-
-DTEMPLATE_PATH="path/to/dir" \ # set the path to your project
22+
cmake -DQDMI_GENERATE_TEMPLATE=ON \
23+
-DTEMPLATE_PREFIX="PREFIX" \
24+
-DTEMPLATE_PATH="path/to/dir" \
2625
-S . -B build
26+
27+
# actually write the template files
28+
cmake --build build --target qdmi-template
29+
```
30+
31+
If the option `TEMPLATE_PATH` is not given it will be placed in `PREFIX_qdmi_device` relative to the
32+
parent directory where QDMI was cloned in.
33+
34+
If you want to regenerate into an existing directory, use:
35+
36+
```sh
37+
cmake --build build --target qdmi-template-force
2738
```
2839

29-
This command adds a `build/` directory to your project that stores all the build files. The
30-
configure step above only needs to be performed once. If the option `TEMPLATE_PATH` is not given it
31-
will be placed in `PREFIX_qdmi_device` relative to the parent directory where QDMI was cloned in.
3240
After this step you can directly start implementing your device in C++.
3341
Example implementations are provided in the `examples/` directory. See
3442
[Examples](examples.md) for more information.

‎templates/CMakeLists.txt‎

Lines changed: 34 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -15,73 +15,48 @@
1515
#
1616
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
1717

18-
option(CONFIGURE_TEMPLATE "Configure the template" OFF)
18+
# Template instantiation should be an explicit action (build target), not a
19+
# configure-time side effect.
20+
option(QDMI_GENERATE_TEMPLATE
21+
"Enable targets to generate a prefixed device template" OFF)
1922

20-
if(CONFIGURE_TEMPLATE)
23+
if(QDMI_GENERATE_TEMPLATE)
2124
set(TEMPLATE_PREFIX
2225
"MY"
2326
CACHE STRING "Prefix for the template")
24-
string(TOLOWER ${TEMPLATE_PREFIX} TEMPLATE_prefix)
27+
string(TOLOWER "${TEMPLATE_PREFIX}" TEMPLATE_prefix)
28+
2529
set(TEMPLATE_PATH
2630
"${CMAKE_SOURCE_DIR}/../${TEMPLATE_prefix}_qdmi_device"
27-
CACHE STRING "Path to the template")
28-
file(REAL_PATH ${TEMPLATE_PATH} TEMPLATE_PATH)
29-
message(
30-
STATUS
31-
"[qdmi] Configuring template for prefix \"${TEMPLATE_PREFIX}\" in '${TEMPLATE_PATH}'"
32-
)
33-
file(GLOB_RECURSE QDMI_TEMPLATE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/device/**.*)
34-
foreach(file ${QDMI_TEMPLATE_FILES})
35-
file(RELATIVE_PATH rel_file ${CMAKE_CURRENT_SOURCE_DIR}/device ${file})
36-
get_filename_component(rel_dir ${rel_file} DIRECTORY)
37-
get_filename_component(file_name ${rel_file} NAME)
38-
file(MAKE_DIRECTORY ${TEMPLATE_PATH}/${rel_dir})
39-
file(READ ${file} file_content)
40-
string(
41-
REGEX
42-
REPLACE "set\\(QDMI_PREFIX \"MY\"\\)"
43-
"set(QDMI_PREFIX \"${TEMPLATE_PREFIX}\")" file_content
44-
"${file_content}")
45-
string(REGEX REPLACE "MY_" "${TEMPLATE_PREFIX}_" file_content
46-
"${file_content}")
47-
string(REGEX REPLACE "MY QDMI" "${TEMPLATE_PREFIX} QDMI" file_content
48-
"${file_content}")
49-
string(REGEX REPLACE "my_" "${TEMPLATE_prefix}_" file_content
50-
"${file_content}")
51-
string(REGEX REPLACE "my-" "${TEMPLATE_prefix}-" file_content
52-
"${file_content}")
53-
string(REGEX REPLACE "my_" "${TEMPLATE_prefix}_" file_content
54-
"${file_content}")
55-
string(REGEX REPLACE "python/my" "python/${TEMPLATE_prefix}" file_content
56-
"${file_content}")
57-
string(REGEX REPLACE "my/qdmi" "${TEMPLATE_prefix}/qdmi" file_content
58-
"${file_content}")
59-
string(REGEX REPLACE "my-qdmi" "${TEMPLATE_prefix}-qdmi" file_content
60-
"${file_content}")
61-
string(REGEX REPLACE "my.qdmi" "${TEMPLATE_prefix}.qdmi" file_content
62-
"${file_content}")
63-
string(REGEX REPLACE "\"my\"" "\"${TEMPLATE_PREFIX}\"" file_content
64-
"${file_content}")
65-
file(WRITE ${TEMPLATE_PATH}/${rel_dir}/${file_name} "${file_content}")
66-
endforeach()
31+
CACHE PATH "Path where the instantiated template should be generated")
32+
file(REAL_PATH "${TEMPLATE_PATH}" TEMPLATE_PATH)
33+
34+
set(QDMI_TEMPLATE_GENERATOR_SCRIPT
35+
"${CMAKE_SOURCE_DIR}/cmake/GenerateTemplate.cmake")
6736

68-
file(RENAME "${TEMPLATE_PATH}/python/my"
69-
"${TEMPLATE_PATH}/python/${TEMPLATE_prefix}")
70-
file(RENAME "${TEMPLATE_PATH}/src/my_device.cpp"
71-
"${TEMPLATE_PATH}/src/${TEMPLATE_prefix}_device.cpp")
72-
file(RENAME "${TEMPLATE_PATH}/test/test_my_device.cpp"
73-
"${TEMPLATE_PATH}/test/test_${TEMPLATE_prefix}_device.cpp")
74-
file(RENAME "${TEMPLATE_PATH}/cmake/my-qdmi-device-config.cmake.in"
75-
"${TEMPLATE_PATH}/cmake/${TEMPLATE_prefix}-qdmi-device-config.cmake.in")
76-
file(RENAME "${TEMPLATE_PATH}/docs/_static/my_logo.svg"
77-
"${TEMPLATE_PATH}/docs/_static/${TEMPLATE_prefix}_logo.svg")
78-
file(RENAME "${TEMPLATE_PATH}/docs/_static/my_logo_dark.svg"
79-
"${TEMPLATE_PATH}/docs/_static/${TEMPLATE_prefix}_logo_dark.svg")
37+
add_custom_target(
38+
qdmi-template
39+
COMMAND
40+
"${CMAKE_COMMAND}"
41+
-DQDMI_TEMPLATE_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/device
42+
-DQDMI_TEMPLATE_OUTPUT_DIR=${TEMPLATE_PATH}
43+
-DQDMI_TEMPLATE_PREFIX=${TEMPLATE_PREFIX} -DQDMI_TEMPLATE_FORCE=OFF -P
44+
"${QDMI_TEMPLATE_GENERATOR_SCRIPT}"
45+
COMMENT
46+
"Generate QDMI device template in ${TEMPLATE_PATH} (prefix: ${TEMPLATE_PREFIX})"
47+
VERBATIM)
8048

81-
message(
82-
STATUS
83-
"[qdmi] Configuring template for prefix \"${TEMPLATE_PREFIX}\" in '${TEMPLATE_PATH}' - done"
84-
)
49+
add_custom_target(
50+
qdmi-template-force
51+
COMMAND
52+
"${CMAKE_COMMAND}"
53+
-DQDMI_TEMPLATE_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/device
54+
-DQDMI_TEMPLATE_OUTPUT_DIR=${TEMPLATE_PATH}
55+
-DQDMI_TEMPLATE_PREFIX=${TEMPLATE_PREFIX} -DQDMI_TEMPLATE_FORCE=ON -P
56+
"${QDMI_TEMPLATE_GENERATOR_SCRIPT}"
57+
COMMENT
58+
"Force-regenerate QDMI device template in ${TEMPLATE_PATH} (overwrites)"
59+
VERBATIM)
8560
endif()
8661

8762
add_subdirectory(device)

0 commit comments

Comments
 (0)