-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaction.yml
317 lines (279 loc) · 12.5 KB
/
action.yml
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
name: 'Build Optimized Rust Binary'
description: 'Builds a min sized Rust binary or library. With self-built std, abort on panic, PGO and cross-compilation support.'
branding:
icon: 'check'
color: 'red'
inputs:
rust-project-path:
description: 'Path to the Rust project'
required: false
default: '.'
pgo-project-path:
description: 'Path to the Rust project used for gathering PGO data. Can be same or separate project.'
required: false
default: '.'
crate-name:
description: 'Name of the Rust crate/package. This can be used to select a crate within a workspace.'
required: true
target:
description: 'The target platform for the Rust compiler'
required: true
features:
description: 'Comma-separated list of features to include in the build'
required: false
default: ''
no-default-features:
description: 'Do not include default features in the build'
required: false
default: "false"
use-pgo:
description: 'Use Profile-Guided Optimization [PGO] to build the artifact.'
required: false
default: "false"
pgo-benchmark-name:
description: 'Benchmark name to use with PGO.'
required: false
default: 'my_benchmark'
use-cross:
description: 'Use cross-rs for building. If false, use cargo.'
required: false
default: "false"
additional-rustflags:
description: 'Additional RUSTFLAGS to pass to the Rust compiler'
required: false
default: ''
upload-artifacts:
description: 'Upload the built artifacts as a GitHub Actions artifact'
required: false
default: "true"
abort-on-panic:
description: 'Abort immediately on panic. If false, the default panic handler is used.'
required: false
default: "true"
build-library:
description: 'Build a library instead of a binary. Requires `crate-type = ["cdylib", "staticlib"]` in Cargo.toml.'
required: false
default: "false"
run-tests-and-coverage:
description: 'Run tests and coverage using the `devops-rust-test-and-coverage` action.'
required: false
default: "false"
upload-coverage-to-codecov:
description: 'Uploads coverage to codecov if `run-tests-and-coverage` is enabled.'
required: false
default: "true"
codecov-token:
description: 'Codecov token for uploading coverage'
required: false
size-optimized-std:
description: 'Enable size optimization for std by adding `optimize_for_size` to build-std-features.'
required: false
default: "false"
additional-std-features:
description: 'Additional build-std features to include (comma-separated).'
required: false
default: ''
use-tarpaulin:
description: 'Use tarpaulin for code coverage. If false, code coverage will be disabled.'
required: false
default: "true"
workspace-path:
description: 'Path to the workspace root. Defaults to rust-project-path if not specified.'
required: false
default: ''
runs:
using: 'composite'
steps:
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
matcher: false
cache: false
toolchain: nightly
- name: Determine workspace path
shell: bash
run: |
WORKSPACE_PATH="${{ inputs.workspace-path }}"
if [ -z "$WORKSPACE_PATH" ]; then
WORKSPACE_PATH="${{ inputs.rust-project-path }}"
fi
echo "WORKSPACE_PATH=${WORKSPACE_PATH}" >> $GITHUB_ENV
- name: Setup artifact directory
shell: bash
run: |
# Create a unique output directory based on crate, target and features
# Sanitize features string to be filesystem safe
SAFE_FEATURES=$(echo "${{ inputs.features }}" | tr -dc '[:alnum:],_-' | tr ',' '-')
if [ -z "$SAFE_FEATURES" ]; then
SAFE_FEATURES="no-features"
fi
# Sanitize workspace path - convert '.' to empty string
WORKSPACE_PATH=$(echo "${{ env.WORKSPACE_PATH }}" | sed 's/^\.$//')
# Construct base path, adding slash only if WORKSPACE_PATH is not empty
if [ -n "$WORKSPACE_PATH" ]; then
BASE_PATH="${GITHUB_WORKSPACE}/${WORKSPACE_PATH}/"
else
BASE_PATH="${GITHUB_WORKSPACE}/"
fi
# Note: This directory has been crafted to be unique, and within 'target' for caching.
# Unique such that different project builds within the same workspace generate valid artifacts
ARTIFACT_OUT_DIR="${BASE_PATH}artifact-out/${{ inputs.crate-name }}/${{ inputs.target }}/${SAFE_FEATURES}"
mkdir -p "$ARTIFACT_OUT_DIR"
echo "ARTIFACT_OUT_DIR=${ARTIFACT_OUT_DIR}" >> $GITHUB_ENV
- name: Setup Rust Caching
uses: Swatinem/rust-cache@v2
with:
key: workspace-${{ env.WORKSPACE_PATH }}+${{ inputs.target }}
cache-on-failure: true
workspaces: |
${{ env.WORKSPACE_PATH }} -> target
- name: Install gcc-multilib (if on Ubuntu based System, and Target is not Host)
if: inputs.use-cross == 'false' && runner.os == 'Linux'
shell: bash
run: |
# Get host triple
HOST_TARGET=$(rustc -vV | sed -n 's|host: ||p')
echo "Host target: $HOST_TARGET"
echo "Target: ${{ inputs.target }}"
if [ "$HOST_TARGET" != "${{ inputs.target }}" ]; then
echo "Target is different from host. Installing gcc-multilib..."
sudo apt-get update || true
sudo apt-get install -y gcc-multilib || true
else
echo "Target is the same as host. Skipping gcc-multilib installation."
fi
- name: Setup Rust Toolchain
shell: bash
working-directory: ${{ env.WORKSPACE_PATH }}
run: |
rustup target add ${{ inputs.target }}
rustup component add rust-src --toolchain nightly
rustup toolchain install nightly-${{ inputs.target }} --force-non-host
# Install cross-rs if needed
if [ "${{ inputs.use-cross }}" == "true" ]; then
RUSTFLAGS="" cargo install cross --git https://github.com/cross-rs/cross
fi
- name: Build Rust Project
shell: bash
run: |
# Setup build variables
BUILD_CMD="cargo"
if [ "${{ inputs.use-cross }}" == "true" ]; then
BUILD_CMD="cross"
fi
BUILD_FEATURES_ARGS="${{ inputs.features }}"
NO_DEFAULT_FEATURES=""
if [ "${{ inputs.no-default-features }}" == "true" ]; then
NO_DEFAULT_FEATURES="--no-default-features"
fi
CRATE_TYPES=""
if [ "${{ inputs.build-library }}" == "true" ]; then
CRATE_TYPES="--crate-type cdylib --crate-type staticlib"
fi
# Initialize RUSTFLAGS
export RUSTFLAGS="${{ inputs.additional-rustflags }} -Zlocation-detail=none -C lto=fat"
# Initialize build-std and build-std-features
BUILD_STD_ARGS="-Z build-std=std"
BUILD_STD_FEATURES=()
if [ "${{ inputs.abort-on-panic }}" == "true" ]; then
export RUSTFLAGS="$RUSTFLAGS -C panic=abort"
BUILD_STD_ARGS="$BUILD_STD_ARGS,panic_abort"
BUILD_STD_FEATURES+=("panic_immediate_abort")
fi
if [ "${{ inputs.size-optimized-std }}" == "true" ]; then
BUILD_STD_FEATURES+=("optimize_for_size")
fi
# Build build-std-features string.
if [ "${{ inputs.additional-std-features }}" != "" ]; then
IFS=',' read -ra ADDITIONAL_FEATURES <<< "${{ inputs.additional-std-features }}"
for feature in "${ADDITIONAL_FEATURES[@]}"; do
BUILD_STD_FEATURES+=("$feature")
done
fi
if [ ${#BUILD_STD_FEATURES[@]} -gt 0 ]; then
BUILD_STD_ARGS="$BUILD_STD_ARGS -Z build-std-features=$(IFS=, ; echo "${BUILD_STD_FEATURES[*]}")"
fi
# Handle PGO
if [ "${{ inputs.use-pgo }}" == "true" ]; then
rustup component add llvm-tools-preview
# Normalize pgo-project-path for PGO, because it doesn't handle double slashes
# (user error) well.
PGO_PROJECT_PATH="${{ inputs.pgo-project-path }}"
PGO_PROJECT_PATH="${PGO_PROJECT_PATH%/}" # Remove trailing forward slash
PGO_PROJECT_PATH="${PGO_PROJECT_PATH%\\}" # Remove trailing backslash
# Generate PGO profiling data
RUSTFLAGS_BACKUP="$RUSTFLAGS"
PGO_PROFILES_FOLDER="${GITHUB_WORKSPACE}/${PGO_PROJECT_PATH}/target/pgo-profiles"
export RUSTFLAGS="$RUSTFLAGS -Cprofile-generate=$PGO_PROFILES_FOLDER"
cd "${GITHUB_WORKSPACE}/${PGO_PROJECT_PATH}"
echo "Flags: $RUSTFLAGS"
echo "$BUILD_CMD +nightly rustc --release $BUILD_STD_ARGS --target ${{ inputs.target }} $CRATE_TYPES --features \"$BUILD_FEATURES_ARGS,pgo\" $NO_DEFAULT_FEATURES"
$BUILD_CMD +nightly test --bench ${{ inputs.pgo-benchmark-name }} --release --target ${{ inputs.target }} --features "$BUILD_FEATURES_ARGS,pgo" $NO_DEFAULT_FEATURES
# Check if .profraw files were created
echo "Profiles Folder: $PGO_PROFILES_FOLDER"
ls "$PGO_PROFILES_FOLDER"
if [ ! -f "$PGO_PROFILES_FOLDER/"*.profraw ]; then
echo "::error::No .profraw files were created during the PGO run. PGO data generation failed."
exit 1
fi
# Merge the PGO profiles
# This is tricky, llvm-profdata isn't in $PATH, so we need to find it ourselves.
target_libdir=$(rustc --print target-libdir)
bin_dir=$(dirname "$target_libdir")/bin
llvm_profdata_path=$(find "$bin_dir" -name "llvm-profdata*" | head -n 1)
MERGED_PGO_PROFILE=$PGO_PROFILES_FOLDER/merged.profdata
$llvm_profdata_path merge -o "$MERGED_PGO_PROFILE" "$PGO_PROFILES_FOLDER"
# Assert merged.profdata is not empty
echo "Merged Profiles Folder: $PGO_PROFILES_FOLDER"
ls "$PGO_PROFILES_FOLDER"
if [ ! -s "$MERGED_PGO_PROFILE" ]; then
# Note: RUSTFLAGS can't handle spaces, so we're kinda screwed if the path up to here
# has spaces in it.
echo "::error::merged.profdata is empty. PGO data generation failed."
exit 1
fi
# Use the generated PGO profiling data to perform an optimized build
export RUSTFLAGS="$RUSTFLAGS_BACKUP -C llvm-args=-pgo-warn-missing-function -C profile-use=$MERGED_PGO_PROFILE"
fi
# Build the final project
cd "${GITHUB_WORKSPACE}/${{ inputs.rust-project-path }}"
## Create Reference File for Date Comparison
mkdir -p "${{ env.ARTIFACT_OUT_DIR }}"
touch "${{ env.ARTIFACT_OUT_DIR }}/build_start"
## Build
echo "Flags: $RUSTFLAGS"
echo "$BUILD_CMD +nightly rustc --release $BUILD_STD_ARGS --target ${{ inputs.target }} $CRATE_TYPES --features \"$BUILD_FEATURES_ARGS\" $NO_DEFAULT_FEATURES"
$BUILD_CMD +nightly rustc --release $BUILD_STD_ARGS --target ${{ inputs.target }} $CRATE_TYPES --features "$BUILD_FEATURES_ARGS" $NO_DEFAULT_FEATURES
## Copy all new files (no directories) from release dir to artifact dir
find "${GITHUB_WORKSPACE}/${{ env.WORKSPACE_PATH }}/target/${{ inputs.target }}/release" -type f -newer "${{ env.ARTIFACT_OUT_DIR }}/build_start" -maxdepth 1 -exec cp {} "${{ env.ARTIFACT_OUT_DIR }}/" \;
rm "${{ env.ARTIFACT_OUT_DIR }}/build_start"
echo "Artifacts directory contents:"
ls "${{ env.ARTIFACT_OUT_DIR }}"
- name: Upload binary artifacts
if: inputs.upload-artifacts == 'true' && inputs.build-library != 'true'
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.crate-name }}-${{ inputs.target }}-${{ inputs.features }}
path: |
${{ env.ARTIFACT_OUT_DIR }}
!${{ env.ARTIFACT_OUT_DIR }}/*.d
- name: Upload library artifacts
if: inputs.upload-artifacts == 'true' && inputs.build-library == 'true'
uses: actions/upload-artifact@v4
with:
name: C-Library-${{ inputs.crate-name }}-${{ inputs.target }}-${{ inputs.features }}
path: ${{ env.ARTIFACT_OUT_DIR }}
- name: Run Tests and Coverage
if: inputs.run-tests-and-coverage == 'true'
uses: Reloaded-Project/devops-rust-test-and-coverage@v1
with:
rust-project-path: ${{ env.WORKSPACE_PATH }}
rust-toolchain: nightly
target: ${{ inputs.target }}
install-rust-toolchain: false
setup-rust-cache: false
upload-coverage: ${{ inputs.upload-coverage-to-codecov }}
codecov-token: ${{ inputs.codecov-token }}
features: ${{ inputs.features }}
no-default-features: ${{ inputs.no-default-features }}
use-cross: ${{ inputs.use-cross }}
use-tarpaulin: ${{ inputs.use-tarpaulin }}