Compare commits

...

21 Commits

Author SHA1 Message Date
Palash Tyagi
2716b95fb6
Merge d42da5ad7bee847acfb11628df0ff5026dcaa665 into fc508bb1a99f8b95b4dea8027677e25ac101aa25 2025-05-04 01:08:10 +01:00
d42da5ad7b
Merge branch 'main' into date_utils 2025-05-04 01:08:08 +01:00
fc508bb1a9
Merge pull request #37 from Magnus167/wkflow-update
Run workflows on self-hosted x86 and ARM runners
2025-05-04 01:01:21 +01:00
Palash Tyagi
c7f3cc6ef9 testing runners 2025-05-04 00:58:27 +01:00
Palash Tyagi
eb28b93781 reorganized GH runner setups 2025-05-04 00:53:19 +01:00
Palash Tyagi
33d021072e Refactor unit test steps for clarity and organization 2025-05-04 00:47:13 +01:00
Palash Tyagi
8ba39f8e87 Reorder steps in unit tests workflow to clarify coverage generation process 2025-05-04 00:42:34 +01:00
Palash Tyagi
cae230783e Enhance output messages for code coverage and documentation steps in unit tests workflow 2025-05-04 00:39:23 +01:00
Palash Tyagi
101ee8c1c7 Fix description for github-token input in runner fallback action 2025-05-04 00:35:07 +01:00
Palash Tyagi
b25c6d358e Add checkout step to runner selection in workflows 2025-05-04 00:33:28 +01:00
Palash Tyagi
aaac6bfc3b Update runner fallback action 2025-05-04 00:31:41 +01:00
Palash Tyagi
6076e0c8e3 Implement runner fallback mechanism for docs and unit test workflows 2025-05-04 00:29:06 +01:00
Palash Tyagi
d15aaf437f Add runner fallback action to select self-hosted or fallback GitHub-hosted runners 2025-05-04 00:28:58 +01:00
3b67d6e477
Merge branch 'main' into wkflow-update 2025-05-04 00:06:50 +01:00
Palash Tyagi
f084eae72c Add Docker setup files for ARM GitHub Actions self-hosted runner 2025-05-03 23:53:23 +01:00
Palash Tyagi
860cd4d081 Update workflows to support self-hosted runners 2025-05-03 23:17:47 +01:00
e2db5eb315
Merge branch 'main' into date_utils 2025-05-03 01:32:47 +01:00
bdef7f1732
Merge branch 'main' into date_utils 2025-05-02 23:38:53 +01:00
2b4ef8a371
Merge branch 'main' into date_utils 2025-05-01 01:03:32 +01:00
Palash Tyagi
1213d588ec Refactor date frequency handling in DatesList to improve clarity and reduce code duplication 2025-04-29 23:43:12 +01:00
Palash Tyagi
a76963ec2e move bdates and dates modules into dateutils module 2025-04-29 00:03:06 +01:00
17 changed files with 261 additions and 104 deletions

View File

@ -0,0 +1,61 @@
# actions/runner-fallback/action.yml
name: "Runner Fallback"
description: |
Chooses a self-hosted runner when one with the required labels is online,
otherwise returns a fallback GitHub-hosted label.
inputs:
primary-runner:
description: 'Comma-separated label list for the preferred self-hosted runner (e.g. "self-hosted,linux")'
required: true
fallback-runner:
description: 'Comma-separated label list or single label for the fallback (e.g. "ubuntu-latest")'
required: true
github-token:
description: "PAT or GITHUB_TOKEN with `repo` scope"
required: true
outputs:
use-runner:
description: "JSON array of labels you can feed straight into runs-on"
value: ${{ steps.pick.outputs.use-runner }}
runs:
using: "composite"
steps:
- name: Check self-hosted fleet
id: pick
shell: bash
env:
TOKEN: ${{ inputs.github-token }}
PRIMARY: ${{ inputs.primary-runner }}
FALLBACK: ${{ inputs.fallback-runner }}
run: |
# -------- helper -----------
to_json_array () {
local list="$1"; IFS=',' read -ra L <<<"$list"
printf '['; printf '"%s",' "${L[@]}"; printf ']'
}
# -------- query API ---------
repo="${{ github.repository }}"
runners=$(curl -s -H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$repo/actions/runners?per_page=100")
# Split wanted labels
IFS=',' read -ra WANT <<<"$PRIMARY"
online_found=0
while read -r row; do
labels=$(jq -r '.labels[].name' <<<"$row")
ok=1
for w in "${WANT[@]}"; do
grep -Fxq "$w" <<<"$labels" || { ok=0; break; }
done
[ "$ok" -eq 1 ] && { online_found=1; break; }
done < <(jq -c '.runners[] | select(.status=="online")' <<<"$runners")
if [ "$online_found" -eq 1 ]; then
echo "✅ Self-hosted runner online."
echo "use-runner=$(to_json_array "$PRIMARY")" >>"$GITHUB_OUTPUT"
else
echo "❌ No matching self-hosted runner online - using fallback."
echo "use-runner=$(to_json_array "$FALLBACK")" >>"$GITHUB_OUTPUT"
fi

30
.github/runners/runner-arm/Dockerfile vendored Normal file
View File

@ -0,0 +1,30 @@
FROM ubuntu:latest
ARG RUNNER_VERSION="2.323.0"
# Prevents installdependencies.sh from prompting the user and blocking the image creation
ARG DEBIAN_FRONTEND=noninteractive
RUN apt update -y && apt upgrade -y && useradd -m docker
RUN apt install -y --no-install-recommends \
curl jq build-essential libssl-dev libffi-dev python3 python3-venv python3-dev python3-pip \
# dot net core dependencies
libicu74 libssl3 libkrb5-3 zlib1g libcurl4
RUN cd /home/docker && mkdir actions-runner && cd actions-runner \
&& curl -O -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz \
&& tar xzf ./actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz
RUN chown -R docker ~docker && /home/docker/actions-runner/bin/installdependencies.sh
COPY entrypoint.sh entrypoint.sh
# make the script executable
RUN chmod +x entrypoint.sh
# since the config and run script for actions are not allowed to be run by root,
# set the user to "docker" so all subsequent commands are run as the docker user
USER docker
ENTRYPOINT ["./entrypoint.sh"]

View File

@ -0,0 +1,18 @@
# docker-compose.yml
services:
github-runner:
build:
context: .
args:
RUNNER_VERSION: 2.323.0
# container_name commented to allow for multiple runners
# container_name: github-runner
env_file:
- .env
volumes:
- runner-work:/home/runner/actions-runner/_work
restart: unless-stopped
volumes:
runner-work:

View File

@ -0,0 +1,24 @@
#!/bin/bash
REPOSITORY=$REPO
ACCESS_TOKEN=$GH_TOKEN
LABELS=$RUNNER_LABELS
# echo "REPO ${REPOSITORY}"
# echo "ACCESS_TOKEN ${ACCESS_TOKEN}"
REG_TOKEN=$(curl -X POST -H "Authorization: token ${ACCESS_TOKEN}" -H "Accept: application/vnd.github+json" https://api.github.com/repos/${REPOSITORY}/actions/runners/registration-token | jq .token --raw-output)
cd /home/docker/actions-runner
./config.sh --url https://github.com/${REPOSITORY} --token ${REG_TOKEN} --labels ${LABELS}
cleanup() {
echo "Removing runner..."
./config.sh remove --unattended --token ${REG_TOKEN}
}
trap 'cleanup; exit 130' INT
trap 'cleanup; exit 143' TERM
./run.sh & wait $!

View File

@ -0,0 +1,9 @@
# Repository name
REPO="Magnus167/rustframe"
# GitHub runner token
GH_TOKEN="some_token_here"
# Labels for the runner
RUNNER_LABELS=self-hosted-linux,linux

4
.github/runners/runner-x64/start.sh vendored Normal file
View File

@ -0,0 +1,4 @@
docker compose up -d --build
# docker compose up -d --build --scale github-runner=2

View File

@ -17,8 +17,23 @@ permissions:
pages: write
jobs:
docs-and-testcov:
pick-runner:
runs-on: ubuntu-latest
outputs:
runner: ${{ steps.choose.outputs.use-runner }}
steps:
- uses: actions/checkout@v4
- id: choose
uses: ./.github/actions/runner-fallback
with:
primary-runner: "self-hosted,ubuntu-latest"
fallback-runner: "ubuntu-latest"
github-token: ${{ secrets.GITHUB_TOKEN }}
docs-and-testcov:
needs: pick-runner
runs-on: ${{ fromJson(needs.pick-runner.outputs.runner) }}
steps:
- uses: actions/checkout@v4
@ -28,10 +43,10 @@ jobs:
with:
toolchain: stable
override: true
- name: Build documentation
run: cargo doc --no-deps --release
- name: Prepare documentation for Pages
run: |
@ -45,28 +60,26 @@ jobs:
run: |
mkdir -p testcov
cargo tarpaulin --engine llvm --out Html --out Json
- name: Check for tarpaulin-report.html
run: |
if [ ! -f tarpaulin-report.html ]; then
echo "tarpaulin-report.html not found!"
exit 1
fi
- name: Export tarpaulin coverage badge JSON
# extract raw coverage and round to 2 decimal places
run: |
# extract raw coverage
coverage=$(jq '.coverage' tarpaulin-report.json)
# round to 2 decimal places
formatted=$(printf "%.2f" "$coverage")
# build the badge JSON using the pre-formatted string
jq --arg message "$formatted" \
'{schemaVersion:1,
label:"tarpaulin-report",
message:$message,
color:"blue"}' \
tarpaulin-report.json \
> tarpaulin-badge.json
coverage=$(jq '.coverage' tarpaulin-report.json)
formatted=$(printf "%.2f" "$coverage")
jq --arg message "$formatted" \
'{schemaVersion:1,
label:"tarpaulin-report",
message:$message,
color:"blue"}' \
tarpaulin-report.json \
> tarpaulin-badge.json
- name: Save last commit date JSON
run: |
@ -78,7 +91,7 @@ jobs:
color:"blue"}' \
<(echo '{}') \
> last-commit-date.json
- name: Copy files to output directory
run: |
mkdir output

View File

@ -11,10 +11,24 @@ concurrency:
cancel-in-progress: true
jobs:
pick-runner:
runs-on: ubuntu-latest
outputs:
runner: ${{ steps.choose.outputs.use-runner }}
steps:
- uses: actions/checkout@v4
- id: choose
uses: ./.github/actions/runner-fallback
with:
primary-runner: "self-hosted,ubuntu-latest"
fallback-runner: "ubuntu-latest"
github-token: ${{ secrets.GITHUB_TOKEN }}
run-unit-tests:
needs: pick-runner
if: github.event.pull_request.draft == false
name: run-unit-tests
runs-on: ubuntu-latest
runs-on: ${{ fromJson(needs.pick-runner.outputs.runner) }}
env:
CARGO_TERM_COLOR: always
@ -24,12 +38,16 @@ jobs:
run: rustup update stable
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Generate code coverage
run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info
- name: Run doc-tests
- name: Run doctests
run: cargo test --doc --all-features --workspace --release
- name: Run unit tests with code coverage
run: cargo llvm-cov --all-features --release --workspace --lcov --output-path lcov.info
- name: Test docs generation
run: cargo doc --no-deps --release
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:

View File

@ -648,8 +648,13 @@ fn collect_monthly(
let mut year = start_date.year();
let mut month = start_date.month();
let next_month =
|(yr, mo): (i32, u32)| -> (i32, u32) { if mo == 12 { (yr + 1, 1) } else { (yr, mo + 1) } };
let next_month = |(yr, mo): (i32, u32)| -> (i32, u32) {
if mo == 12 {
(yr + 1, 1)
} else {
(yr, mo + 1)
}
};
loop {
let candidate = if want_first_day {
@ -873,6 +878,25 @@ fn last_day_of_year(year: i32) -> Result<NaiveDate, Box<dyn Error>> {
// --- Generator Helper Functions ---
fn get_first_date_helper(freq: DateFreq) -> fn(i32, u32) -> Result<NaiveDate, Box<dyn Error>> {
if matches!(
freq,
DateFreq::Daily | DateFreq::WeeklyMonday | DateFreq::WeeklyFriday
) {
panic!("Daily, WeeklyMonday, and WeeklyFriday frequencies are not supported here");
}
match freq {
DateFreq::MonthStart => first_day_of_month,
DateFreq::MonthEnd => last_day_of_month,
DateFreq::QuarterStart => first_day_of_quarter,
DateFreq::QuarterEnd => last_day_of_quarter,
DateFreq::YearStart => |year, _| first_day_of_year(year),
DateFreq::YearEnd => |year, _| last_day_of_year(year),
_ => unreachable!(),
}
}
/// Finds the *first* valid date according to the frequency,
/// starting the search *on or after* the given `start_date`.
fn find_first_date_on_or_after(
@ -883,69 +907,42 @@ fn find_first_date_on_or_after(
DateFreq::Daily => Ok(start_date), // The first daily date is the start date itself
DateFreq::WeeklyMonday => move_to_day_of_week_on_or_after(start_date, Weekday::Mon),
DateFreq::WeeklyFriday => move_to_day_of_week_on_or_after(start_date, Weekday::Fri),
DateFreq::MonthStart => {
let mut candidate = first_day_of_month(start_date.year(), start_date.month())?;
DateFreq::MonthStart | DateFreq::MonthEnd => {
// let mut candidate = first_day_of_month(start_date.year(), start_date.month())?;
let get_cand_func = get_first_date_helper(freq);
let mut candidate = get_cand_func(start_date.year(), start_date.month())?;
if candidate < start_date {
let (next_y, next_m) = if start_date.month() == 12 {
(start_date.year().checked_add(1).ok_or("Year overflow")?, 1)
} else {
(start_date.year(), start_date.month() + 1)
};
candidate = first_day_of_month(next_y, next_m)?;
candidate = get_cand_func(next_y, next_m)?;
}
Ok(candidate)
}
DateFreq::MonthEnd => {
let mut candidate = last_day_of_month(start_date.year(), start_date.month())?;
if candidate < start_date {
let (next_y, next_m) = if start_date.month() == 12 {
(start_date.year().checked_add(1).ok_or("Year overflow")?, 1)
} else {
(start_date.year(), start_date.month() + 1)
};
candidate = last_day_of_month(next_y, next_m)?;
}
Ok(candidate)
}
DateFreq::QuarterStart => {
DateFreq::QuarterStart | DateFreq::QuarterEnd => {
let current_q = month_to_quarter(start_date.month());
let mut candidate = first_day_of_quarter(start_date.year(), current_q)?;
let get_cand_func = get_first_date_helper(freq);
let mut candidate = get_cand_func(start_date.year(), current_q)?;
if candidate < start_date {
let (next_y, next_q) = if current_q == 4 {
(start_date.year().checked_add(1).ok_or("Year overflow")?, 1)
} else {
(start_date.year(), current_q + 1)
};
candidate = first_day_of_quarter(next_y, next_q)?;
candidate = get_cand_func(next_y, next_q)?;
}
Ok(candidate)
}
DateFreq::QuarterEnd => {
let current_q = month_to_quarter(start_date.month());
let mut candidate = last_day_of_quarter(start_date.year(), current_q)?;
if candidate < start_date {
let (next_y, next_q) = if current_q == 4 {
(start_date.year().checked_add(1).ok_or("Year overflow")?, 1)
} else {
(start_date.year(), current_q + 1)
};
candidate = last_day_of_quarter(next_y, next_q)?;
}
Ok(candidate)
}
DateFreq::YearStart => {
let mut candidate = first_day_of_year(start_date.year())?;
DateFreq::YearStart | DateFreq::YearEnd => {
let get_cand_func = get_first_date_helper(freq);
let mut candidate = get_cand_func(start_date.year(), 0)?;
if candidate < start_date {
candidate =
first_day_of_year(start_date.year().checked_add(1).ok_or("Year overflow")?)?;
}
Ok(candidate)
}
DateFreq::YearEnd => {
let mut candidate = last_day_of_year(start_date.year())?;
if candidate < start_date {
candidate =
last_day_of_year(start_date.year().checked_add(1).ok_or("Year overflow")?)?;
get_cand_func(start_date.year().checked_add(1).ok_or("Year overflow")?, 0)?;
}
Ok(candidate)
}
@ -962,7 +959,8 @@ fn find_next_date(current_date: NaiveDate, freq: DateFreq) -> Result<NaiveDate,
DateFreq::WeeklyMonday | DateFreq::WeeklyFriday => current_date
.checked_add_signed(Duration::days(7))
.ok_or_else(|| "Date overflow adding 7 days".into()),
DateFreq::MonthStart => {
DateFreq::MonthStart | DateFreq::MonthEnd => {
let get_cand_func = get_first_date_helper(freq);
let (next_y, next_m) = if current_date.month() == 12 {
(
current_date.year().checked_add(1).ok_or("Year overflow")?,
@ -971,21 +969,11 @@ fn find_next_date(current_date: NaiveDate, freq: DateFreq) -> Result<NaiveDate,
} else {
(current_date.year(), current_date.month() + 1)
};
first_day_of_month(next_y, next_m)
get_cand_func(next_y, next_m)
}
DateFreq::MonthEnd => {
let (next_y, next_m) = if current_date.month() == 12 {
(
current_date.year().checked_add(1).ok_or("Year overflow")?,
1,
)
} else {
(current_date.year(), current_date.month() + 1)
};
last_day_of_month(next_y, next_m)
}
DateFreq::QuarterStart => {
DateFreq::QuarterStart | DateFreq::QuarterEnd => {
let current_q = month_to_quarter(current_date.month());
let get_cand_func = get_first_date_helper(freq);
let (next_y, next_q) = if current_q == 4 {
(
current_date.year().checked_add(1).ok_or("Year overflow")?,
@ -994,25 +982,14 @@ fn find_next_date(current_date: NaiveDate, freq: DateFreq) -> Result<NaiveDate,
} else {
(current_date.year(), current_q + 1)
};
first_day_of_quarter(next_y, next_q)
get_cand_func(next_y, next_q)
}
DateFreq::QuarterEnd => {
let current_q = month_to_quarter(current_date.month());
let (next_y, next_q) = if current_q == 4 {
(
current_date.year().checked_add(1).ok_or("Year overflow")?,
1,
)
} else {
(current_date.year(), current_q + 1)
};
last_day_of_quarter(next_y, next_q)
}
DateFreq::YearStart => {
first_day_of_year(current_date.year().checked_add(1).ok_or("Year overflow")?)
}
DateFreq::YearEnd => {
last_day_of_year(current_date.year().checked_add(1).ok_or("Year overflow")?)
DateFreq::YearStart | DateFreq::YearEnd => {
let get_cand_func = get_first_date_helper(freq);
get_cand_func(
current_date.year().checked_add(1).ok_or("Year overflow")?,
0,
)
}
}
}

View File

@ -0,0 +1,6 @@
pub mod bdates;
pub use bdates::{BDateFreq, BDatesList, BDatesGenerator};
pub mod dates;
pub use dates::{DateFreq, DatesList, DatesGenerator};

View File

@ -1,6 +1,3 @@
pub mod bdates;
pub use bdates::{BDateFreq, BDatesList, BDatesGenerator};
pub mod dates;
pub use dates::{DateFreq, DatesList, DatesGenerator};
pub mod dateutils;
pub use dateutils::{BDateFreq, BDatesGenerator, BDatesList};
pub use dateutils::{DateFreq, DatesGenerator, DatesList};