-
Notifications
You must be signed in to change notification settings - Fork 230
395 lines (331 loc) · 16.8 KB
/
incremental-build.yml
File metadata and controls
395 lines (331 loc) · 16.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
name: Build and Test (Ubuntu 22.04)
on:
push:
branches: [ incremental_build_debug ]
pull_request:
branches: [ develop, os-4.0-dev ]
workflow_dispatch:
inputs:
aggressive_clean:
description: 'Aggressive cleaning (remove all conan packages and build artifacts)'
required: false
type: boolean
default: false
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build:
if: github.event_name == 'workflow_dispatch' || github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'Pull Request - Ready for CI')
runs-on: linux-openstudio-2
permissions:
contents: read
issues: read
checks: write
pull-requests: write
actions: read
env:
MAX_SAFE_THREADS: $(( ($(nproc) * 90 + 50) / 100 ))
CMAKE_CXX_COMPILER_LAUNCHER: ccache
MAKEFLAGS: "-j$(( ($(nproc) * 90 + 50) / 100 ))"
NODE_TLS_REJECT_UNAUTHORIZED: 0
DOCKER_ROOT: /github/home
OPENSTUDIO_DOCKER_VOLUME: /github/home/Ubuntu
OPENSTUDIO_SOURCE_NAME: OpenStudio
OPENSTUDIO_BUILD_NAME: OS-build
EXCLUDED_TESTS: "BCLFixture.RemoteBCL_BCLSearchResult"
container: # Define the Docker container for the job. All subsequent steps run inside it.
image: nrel/openstudio-cmake-tools:jammy-main
options: --privileged -u root -e "LANG=en_US.UTF-8" # These options are passed to the 'docker run' command internally
volumes: # envs don't work in volume definition for containers
- "/srv/data/jenkins/docker-volumes/conan-data/.conan2:/github/home/.conan2" # Conan cache
- "/srv/data/jenkins/docker-volumes/ubuntu-2204:/github/home/Ubuntu"
defaults:
run:
shell: bash
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Prepare workspace
run: |
cd ${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}
# make safe repository
git config --global --add safe.directory "*"
- name: Aggressive clean
if: inputs.aggressive_clean
run: |
BUILD_DIR="${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}"
echo "Aggressive clean: removing entire build directory"
rm -rf "$BUILD_DIR"
echo "Aggressive clean: removing all conan packages to ensure clean state"
# Force remove all to ensure rebuild with correct ABI
conan remove "*" -c || true
CONAN_PROFILE_DEFAULT="${{ env.DOCKER_ROOT }}/.conan2/profiles/default"
if [ -f "$CONAN_PROFILE_DEFAULT" ]; then
echo "Aggressive clean: removing conan profile to ensure correct detection and rebuild"
conan profile show
rm -Rf "$CONAN_PROFILE_DEFAULT"
fi
- name: Remove old artifacts
run: |
BUILD_DIR="${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}"
if [ -d "$BUILD_DIR" ]; then
echo "Cleaning up old artifacts in $BUILD_DIR"
find "$BUILD_DIR" -maxdepth 1 -name "*.deb" -delete
rm -rf "$BUILD_DIR/_CPack_Packages"
fi
- name: Cache ccache
uses: actions/cache@v4
with:
path: ${{ env.DOCKER_ROOT }}/.ccache
key: ${{ runner.os }}-ccache-${{ github.sha }}
restore-keys: |
${{ runner.os }}-ccache-
- name: Configure ccache
run: |
ccache --max-size=2G
ccache --set-config=compression=true
ccache --set-config=compression_level=1
ccache --show-stats
- name: Git Setup
run: |
# Set up git and fetch PR head, then detect conan profile and install dependencies
cd ${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}
git config --global --add safe.directory "*" && \
git config user.email "cicommercialbuilding@gmail.com" && \
git config user.name "ci-commercialbuildings" && \
git fetch origin && \
git fetch origin +refs/pull/*/head:refs/remotes/origin/pr/* && \
if [ "${{ github.event_name }}" = "pull_request" ]; then
git checkout origin/pr/${{ github.event.pull_request.number }}
else
git checkout ${{ github.ref_name }}
fi
- name: Install conan dependencies
working-directory: ${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}
run: |
conan remote add conancenter --force https://center2.conan.io
conan remote update conancenter --insecure
conan remote add nrel-v2 --index 0 --force https://conan.openstudio.net/artifactory/api/conan/conan-v2
conan remote update nrel-v2 --insecure
CONAN_PROFILE_DEFAULT="${{ env.DOCKER_ROOT }}/.conan2/profiles/default"
if [ ! -f "$CONAN_PROFILE_DEFAULT" ]; then
conan profile detect
fi
sed -i 's/cppstd=.*$/cppstd=20/g' $CONAN_PROFILE_DEFAULT
conan profile show
conan install . --output-folder=${{ env.OPENSTUDIO_BUILD_NAME }} --build=missing -c tools.cmake.cmaketoolchain:generator=Ninja -s compiler.cppstd=20 -s build_type=Release
- name: Locate Ruby
run: |
ruby_path=$(command -v ruby)
echo "SYSTEM_RUBY_PATH=$ruby_path" >> $GITHUB_ENV
# wrap cmake with ccache using a flag or environment variable
- name: Configure with CMake
working-directory: ${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}
run: |
cmake --preset conan-release \
-DSYSTEM_RUBY_EXECUTABLE="$SYSTEM_RUBY_PATH"
- name: Verify build state
working-directory: ${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}
run: |
if [ -f "build.ninja" ]; then
echo "Ninja build file found - checking what needs to be built"
ninja -n -j 1 package | head -20 || true
else
echo "No build.ninja found - full reconfiguration will be needed"
fi
- name: Build with Ninja
working-directory: ${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}
run: |
. ./conanbuild.sh
ninja -j ${{ env.MAX_SAFE_THREADS }} package
- name: Run CTests with enhanced error handling
working-directory: ${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}
run: |
set +e
mkdir -p Testing/run{1,2,3}
begin_group() { echo -e "::group::\033[93m$1\033[0m"; }
begin_group "Run 1 - Initial with all tests"
ctest -j ${{ env.MAX_SAFE_THREADS }} --no-compress-output --output-junit Testing/run1/results.xml -E "${{ env.EXCLUDED_TESTS }}" 2>&1 | tee /tmp/run1.log
RESULT1=${PIPESTATUS[0]}
echo "::endgroup::"
if [ $RESULT1 -ne 0 ]; then
begin_group "Run 2 - Retrying failed tests"
echo "Retrying failed tests..."
ctest -j ${{ env.MAX_SAFE_THREADS }} --rerun-failed --no-compress-output --output-junit Testing/run2/results.xml -E "${{ env.EXCLUDED_TESTS }}" 2>&1 | tee /tmp/run2.log
RESULT2=${PIPESTATUS[0]}
echo "::endgroup::"
if [ $RESULT2 -ne 0 ]; then
# Final attempt with verbose output for failing tests
begin_group "Run 3 - Final attempt with verbose output..."
ctest -j ${{ env.MAX_SAFE_THREADS }} --rerun-failed --no-compress-output --output-junit Testing/run3/results.xml -E "${{ env.EXCLUDED_TESTS }}" --output-on-failure 2>&1 | tee /tmp/run3.log
RESULT3=${PIPESTATUS[0]}
echo "::endgroup::"
else
RESULT3=0
echo "Tests passed on retry"
fi
else
echo "All tests passed"
RESULT2=0
RESULT3=0
fi
echo "Test Results: Run1=$RESULT1 Run2=$RESULT2 Run3=$RESULT3"
if [ $RESULT1 -eq 0 ] || [ $RESULT2 -eq 0 ] || [ $RESULT3 -eq 0 ]; then
exit 0
else
exit 1
fi
- name: Test Summary
uses: test-summary/action@v2
with:
paths: "${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}/Testing/run*/results.xml" # Path to your JUnit output file
output: "${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}/Testing/test-summary.md"
if: always()
- name: Upload test summary
uses: actions/upload-artifact@v4
with:
name: test-summary
path: ${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}/Testing/test-summary.md
if: always()
- name: Generate test results dashboard
if: always()
run: |
mkdir -p ${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}/Testing/dashboard
# Create comprehensive test dashboard
cat > ${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}/Testing/dashboard/test-dashboard.md << 'EOF'
# 🧪 Test Results Dashboard
## Summary
EOF
# Process JUnit XML files and extract test information
python3 << 'PYTHON_EOF'
import xml.etree.ElementTree as ET
import os
import glob
from datetime import datetime
build_dir = "${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}"
dashboard_file = f"{build_dir}/Testing/dashboard/test-dashboard.md"
# Find all JUnit XML files
xml_files = glob.glob(f"{build_dir}/Testing/run*/results.xml")
total_tests = 0
total_failures = 0
total_errors = 0
total_skipped = 0
failed_tests = []
# Parse XML files
for xml_file in xml_files:
if os.path.exists(xml_file):
try:
tree = ET.parse(xml_file)
root = tree.getroot()
# Handle different JUnit XML formats
if root.tag == 'testsuites':
testsuites = root.findall('testsuite')
else:
testsuites = [root]
for testsuite in testsuites:
suite_name = testsuite.get('name', 'Unknown')
tests = int(testsuite.get('tests', 0))
failures = int(testsuite.get('failures', 0))
errors = int(testsuite.get('errors', 0))
skipped = int(testsuite.get('skipped', 0))
total_tests += tests
total_failures += failures
total_errors += errors
total_skipped += skipped
# Get failed test details
for testcase in testsuite.findall('testcase'):
test_name = testcase.get('name', 'Unknown')
classname = testcase.get('classname', suite_name)
failure = testcase.find('failure')
error = testcase.find('error')
if failure is not None or error is not None:
failure_info = failure if failure is not None else error
message = failure_info.get('message', 'No message')
details = failure_info.text or 'No details available'
failed_tests.append({
'suite': suite_name,
'class': classname,
'name': test_name,
'message': message,
'details': details,
'run': os.path.basename(os.path.dirname(xml_file))
})
except Exception as e:
print(f"Error parsing {xml_file}: {e}")
# Generate dashboard content
with open(dashboard_file, 'a') as f:
# Summary section
success_rate = ((total_tests - total_failures - total_errors) / total_tests * 100) if total_tests > 0 else 0
f.write(f"| Metric | Value |\n")
f.write(f"|--------|-------|\n")
f.write(f"| **Total Tests** | {total_tests} |\n")
f.write(f"| **Passed** | {total_tests - total_failures - total_errors} |\n")
f.write(f"| **Failed** | {total_failures} |\n")
f.write(f"| **Errors** | {total_errors} |\n")
f.write(f"| **Skipped** | {total_skipped} |\n")
f.write(f"| **Success Rate** | {success_rate:.1f}% |\n")
f.write(f"| **Generated** | {datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC')} |\n\n")
if success_rate >= 100:
f.write("## ✅ All Tests Passed!\n\n")
elif success_rate >= 95:
f.write("## ⚠️ Minor Issues Detected\n\n")
else:
f.write("## ❌ Significant Test Failures\n\n")
# Failed tests section
if failed_tests:
f.write(f"## 🔍 Failed Tests ({len(failed_tests)} failures)\n\n")
# Group by test suite
suites = {}
for test in failed_tests:
suite = test['suite']
if suite not in suites:
suites[suite] = []
suites[suite].append(test)
for suite_name, suite_tests in suites.items():
f.write(f"### {suite_name} ({len(suite_tests)} failures)\n\n")
for test in suite_tests:
f.write(f"<details>\n")
f.write(f"<summary><strong>{test['class']}.{test['name']}</strong> ({test['run']})</summary>\n\n")
f.write(f"**Error Message:**\n")
f.write(f"```\n{test['message']}\n```\n\n")
f.write(f"**Full Details:**\n")
f.write(f"```\n{test['details']}\n```\n\n")
f.write(f"</details>\n\n")
# Test run information
f.write("## 📊 Test Run Information\n\n")
f.write("| Run | XML File | Status |\n")
f.write("|-----|----------|--------|\n")
for i, xml_file in enumerate(xml_files, 1):
status = "✅ Found" if os.path.exists(xml_file) else "❌ Missing"
run_name = os.path.basename(os.path.dirname(xml_file))
f.write(f"| {run_name} | `{os.path.basename(xml_file)}` | {status} |\n")
if not xml_files:
f.write("| - | No XML files found | ❌ Missing |\n")
print(f"Dashboard generated with {total_tests} tests, {total_failures + total_errors} failures")
PYTHON_EOF
- name: Publish test results to PR
if: always() && github.event_name == 'pull_request'
uses: marocchino/sticky-pull-request-comment@v2
with:
header: test-results
path: ${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}/Testing/dashboard/test-dashboard.md
- name: Upload comprehensive test results
uses: actions/upload-artifact@v4
with:
name: test-results-dashboard
path: |
${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}/Testing/dashboard/
${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}/Testing/run*/
if: always()
- name: Upload build artifacts with metadata
uses: actions/upload-artifact@v4
with:
name: ubuntu-2204-${{ github.head_ref || github.ref_name }}-${{ github.sha }}
path: |
${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}/*.deb
${{ env.OPENSTUDIO_DOCKER_VOLUME }}/${{ env.OPENSTUDIO_SOURCE_NAME }}/${{ env.OPENSTUDIO_BUILD_NAME }}/_CPack_Packages/Linux/TGZ/*.tar.gz
retention-days: 30
if: always()