mirror of
https://github.com/nolen777/eagle0.git
synced 2026-07-28 23:15:41 +00:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8e5346209 | ||
|
|
c128531662 | ||
|
|
ee6189a9d7 | ||
|
|
e0304125b8 | ||
|
|
11eac42e30 | ||
|
|
d0a63f1c61 | ||
|
|
97df984189 | ||
|
|
6c771df84f | ||
|
|
7e40420fe1 |
@@ -3,14 +3,14 @@ name: Build Linux Sysroot
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
create_release:
|
||||
description: 'Create a GitHub release with the sysroot'
|
||||
version:
|
||||
description: 'Sysroot version (e.g., v2, v3)'
|
||||
required: true
|
||||
default: 'true'
|
||||
type: boolean
|
||||
default: 'v2'
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-sysroot:
|
||||
@@ -28,23 +28,39 @@ jobs:
|
||||
name: ubuntu-noble-sysroot
|
||||
path: tools/sysroot/output/
|
||||
|
||||
- name: Create Release
|
||||
if: ${{ inputs.create_release }}
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: sysroot-noble-v1
|
||||
name: Ubuntu 24.04 Noble Sysroot
|
||||
body: |
|
||||
Ubuntu 24.04 (Noble) sysroot for cross-compilation from macOS to Linux.
|
||||
- name: Install AWS CLI
|
||||
run: |
|
||||
if ! command -v aws &> /dev/null; then
|
||||
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
||||
unzip -q awscliv2.zip
|
||||
sudo ./aws/install
|
||||
fi
|
||||
|
||||
Contains:
|
||||
- libstdc++-13 (C++23 support)
|
||||
- libc6-dev
|
||||
- linux-libc-dev
|
||||
- zlib1g-dev
|
||||
- libssl-dev
|
||||
- name: Upload to DigitalOcean Spaces
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.SECRET_KEY }}
|
||||
run: |
|
||||
# Upload sysroot tarball to DO Spaces (using eagle0-windows bucket, same as other workflows)
|
||||
aws s3 cp tools/sysroot/output/ubuntu_noble_amd64_sysroot.tar.xz \
|
||||
s3://eagle0-windows/sysroot/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.tar.xz \
|
||||
--endpoint-url https://sfo3.digitaloceanspaces.com \
|
||||
--acl public-read
|
||||
|
||||
Use with toolchains_llvm sysroot rule.
|
||||
files: |
|
||||
tools/sysroot/output/ubuntu_noble_amd64_sysroot.tar.xz
|
||||
tools/sysroot/output/ubuntu_noble_amd64_sysroot.sha256
|
||||
# Upload sha256 file
|
||||
aws s3 cp tools/sysroot/output/ubuntu_noble_amd64_sysroot.sha256 \
|
||||
s3://eagle0-windows/sysroot/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.sha256 \
|
||||
--endpoint-url https://sfo3.digitaloceanspaces.com \
|
||||
--acl public-read
|
||||
|
||||
echo ""
|
||||
echo "=== Sysroot uploaded ==="
|
||||
echo "URL: https://eagle0-windows.sfo3.digitaloceanspaces.com/sysroot/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.tar.xz"
|
||||
echo "SHA256: $(cat tools/sysroot/output/ubuntu_noble_amd64_sysroot.sha256)"
|
||||
echo ""
|
||||
echo "Update MODULE.bazel with:"
|
||||
echo "sysroot("
|
||||
echo " name = \"linux_sysroot\","
|
||||
echo " sha256 = \"$(cat tools/sysroot/output/ubuntu_noble_amd64_sysroot.sha256)\","
|
||||
echo " urls = [\"https://eagle0-windows.sfo3.digitaloceanspaces.com/sysroot/${{ inputs.version }}/ubuntu_noble_amd64_sysroot.tar.xz\"],"
|
||||
echo ")"
|
||||
|
||||
@@ -42,9 +42,14 @@ jobs:
|
||||
mkdir -p ~/.docker
|
||||
AUTH=$(echo -n "${DO_TOKEN}:${DO_TOKEN}" | base64)
|
||||
echo "{\"auths\":{\"registry.digitalocean.com\":{\"auth\":\"${AUTH}\"}}}" > ~/.docker/config.json
|
||||
# Also set for current directory in case Bazel uses different home
|
||||
mkdir -p .docker
|
||||
cp ~/.docker/config.json .docker/
|
||||
|
||||
- name: Push Eagle image to DO registry
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
||||
env:
|
||||
DOCKER_CONFIG: ${{ github.workspace }}/.docker
|
||||
run: bazel run //ci:eagle_server_push
|
||||
|
||||
build-shardok:
|
||||
@@ -66,7 +71,12 @@ jobs:
|
||||
mkdir -p ~/.docker
|
||||
AUTH=$(echo -n "${DO_TOKEN}:${DO_TOKEN}" | base64)
|
||||
echo "{\"auths\":{\"registry.digitalocean.com\":{\"auth\":\"${AUTH}\"}}}" > ~/.docker/config.json
|
||||
# Also set for current directory in case Bazel uses different home
|
||||
mkdir -p .docker
|
||||
cp ~/.docker/config.json .docker/
|
||||
|
||||
- name: Push Shardok image to DO registry
|
||||
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true')
|
||||
env:
|
||||
DOCKER_CONFIG: ${{ github.workspace }}/.docker
|
||||
run: bazel run --platforms=//:linux_x86_64 //ci:shardok_server_push
|
||||
|
||||
+3
-3
@@ -68,12 +68,12 @@ use_repo(llvm, "llvm_toolchain", "llvm_toolchain_linux")
|
||||
|
||||
# Download the Linux sysroot (Ubuntu 24.04 Noble for C++23 support)
|
||||
# Built by: .github/workflows/build_sysroot.yml
|
||||
# To rebuild: Run the "Build Linux Sysroot" workflow and update sha256
|
||||
# To rebuild: Run the "Build Linux Sysroot" workflow with a new version, then update sha256 and URL
|
||||
sysroot = use_repo_rule("@toolchains_llvm//toolchain:sysroot.bzl", "sysroot")
|
||||
sysroot(
|
||||
name = "linux_sysroot",
|
||||
sha256 = "3812d376beba78a301eb64dfe2ad01d5896500826054ebb6aa4e00607fa0e347",
|
||||
urls = ["https://github.com/nolen777/eagle0/releases/download/sysroot-noble-v1/ubuntu_noble_amd64_sysroot.tar.xz"],
|
||||
sha256 = "aadb60a2e2c776ac000bb2e29f3039fd81b5b031500cf4a1651b365712819d1f",
|
||||
urls = ["https://eagle0-windows.sfo3.digitaloceanspaces.com/sysroot/v2/ubuntu_noble_amd64_sysroot.tar.xz"],
|
||||
)
|
||||
|
||||
#
|
||||
|
||||
+268
-30
@@ -3,10 +3,25 @@
|
||||
# generate_changelog.sh
|
||||
#
|
||||
# Generates a weekly changelog from merged PRs, uses Claude to create a synopsis,
|
||||
# and opens an email draft in Mac Mail.
|
||||
# and sends an HTML email via Fastmail JMAP API.
|
||||
#
|
||||
# Usage: ./scripts/generate_changelog.sh [--dry-run]
|
||||
#
|
||||
# Configuration files (in ~/.config/eagle0/):
|
||||
# fastmail_token - API token (required)
|
||||
# changelog_recipient - Email addresses, one per line (optional, defaults to sender)
|
||||
#
|
||||
# To set up:
|
||||
# mkdir -p ~/.config/eagle0
|
||||
# echo 'your-token' > ~/.config/eagle0/fastmail_token
|
||||
# chmod 600 ~/.config/eagle0/fastmail_token
|
||||
#
|
||||
# # Optional: configure recipients (one per line, # for comments)
|
||||
# cat > ~/.config/eagle0/changelog_recipient << EOF
|
||||
# alice@example.com
|
||||
# bob@example.com
|
||||
# EOF
|
||||
#
|
||||
# The script tracks its last run using a git tag 'changelog-last-run'.
|
||||
# On first run (no tag), it defaults to the previous Friday at 4pm.
|
||||
|
||||
@@ -19,6 +34,56 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
TAG_NAME="changelog-last-run"
|
||||
DRY_RUN=false
|
||||
FASTMAIL_API="https://api.fastmail.com/jmap/api/"
|
||||
CONFIG_DIR="$HOME/.config/eagle0"
|
||||
TOKEN_FILE="$CONFIG_DIR/fastmail_token"
|
||||
RECIPIENT_FILE="$CONFIG_DIR/changelog_recipient"
|
||||
|
||||
# Load API token from file or environment
|
||||
load_api_token() {
|
||||
# Environment variable takes precedence
|
||||
if [[ -n "${FASTMAIL_API_TOKEN:-}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Try loading from config file
|
||||
if [[ -f "$TOKEN_FILE" ]]; then
|
||||
FASTMAIL_API_TOKEN=$(cat "$TOKEN_FILE" | tr -d '[:space:]')
|
||||
if [[ -n "$FASTMAIL_API_TOKEN" ]]; then
|
||||
echo "Loaded API token from $TOKEN_FILE"
|
||||
export FASTMAIL_API_TOKEN
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Load recipient emails from config file (one per line)
|
||||
# Returns JSON array fragment like: {"email": "a@b.com"}, {"email": "c@d.com"}
|
||||
load_recipients_json() {
|
||||
local recipients=""
|
||||
if [[ -f "$RECIPIENT_FILE" ]]; then
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
# Skip empty lines and comments
|
||||
line=$(echo "$line" | tr -d '[:space:]')
|
||||
[[ -z "$line" || "$line" == \#* ]] && continue
|
||||
|
||||
if [[ -n "$recipients" ]]; then
|
||||
recipients="$recipients, "
|
||||
fi
|
||||
recipients="$recipients{\"email\": \"$line\"}"
|
||||
done < "$RECIPIENT_FILE"
|
||||
fi
|
||||
echo "$recipients"
|
||||
}
|
||||
|
||||
# Get human-readable list of recipients
|
||||
load_recipients_display() {
|
||||
if [[ -f "$RECIPIENT_FILE" ]]; then
|
||||
grep -v '^#' "$RECIPIENT_FILE" | grep -v '^[[:space:]]*$' | tr '\n' ', ' | sed 's/, $//'
|
||||
fi
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
@@ -118,23 +183,30 @@ generate_synopsis() {
|
||||
|
||||
# Create a prompt file to avoid shell escaping issues
|
||||
local prompt_file="/tmp/eagle0_prompt_$$.txt"
|
||||
cat > "$prompt_file" <<'PROMPT_HEADER'
|
||||
# Get repo URL for PR links
|
||||
local repo_url=$(gh repo view --json url -q '.url')
|
||||
|
||||
cat > "$prompt_file" <<PROMPT_HEADER
|
||||
You are summarizing changes for a weekly engineering update email.
|
||||
|
||||
Read the following list of merged PRs and create a concise synopsis grouped by theme/feature/area of the codebase.
|
||||
|
||||
Guidelines:
|
||||
- Group related changes together under clear headings (ALL CAPS with a blank line before)
|
||||
- Use "- " bullet points for individual changes
|
||||
Structure:
|
||||
1. <h1> title (e.g., "Eagle0 Weekly Update")
|
||||
2. <h2>BLUF</h2> (Bottom Line Up Front) - A short prose paragraph (2-4 sentences) highlighting the 1-3 most important changes this week and what to look for when testing. This should be conversational and help readers quickly understand what matters most.
|
||||
3. Synopsis sections (<h2> headings with bullet point summaries)
|
||||
4. <hr> divider
|
||||
5. <h2>PR Details</h2> with the same groupings, but smaller (<h3> headings) and listing PR links
|
||||
- Format each PR as: <a href="${repo_url}/pull/NUMBER">#NUMBER</a>: Title
|
||||
|
||||
Guidelines for the SYNOPSIS sections:
|
||||
- Group related changes together under clear headings (use <h2> tags)
|
||||
- Use bullet points (<ul><li>) for individual changes
|
||||
- Highlight any significant new features, breaking changes, or important fixes
|
||||
- Keep the tone professional but accessible
|
||||
- Aim for a summary that takes 1-2 minutes to read
|
||||
- Don't include PR numbers in the summary - focus on what changed and why it matters
|
||||
- Don't include PR numbers in the synopsis - focus on what changed and why it matters
|
||||
|
||||
IMPORTANT: Output plain text only. No markdown, no HTML. Use simple formatting:
|
||||
- ALL CAPS for section headers
|
||||
- Dashes (-) for bullet points
|
||||
- Blank lines between sections
|
||||
IMPORTANT: Output valid HTML that can be used directly in an email body. Do NOT wrap in \`\`\`html code blocks - just output the raw HTML.
|
||||
|
||||
Here are the merged PRs:
|
||||
|
||||
@@ -144,32 +216,161 @@ PROMPT_HEADER
|
||||
echo "" >> "$prompt_file"
|
||||
echo "Generate the synopsis now:" >> "$prompt_file"
|
||||
|
||||
# Use Claude CLI to generate the synopsis (read from stdin)
|
||||
cat "$prompt_file" | claude --print > "$output_file"
|
||||
# Use Claude CLI to generate the synopsis, wrapped in proper HTML with charset
|
||||
local raw_output="/tmp/eagle0_raw_$$.html"
|
||||
cat "$prompt_file" | claude --print > "$raw_output"
|
||||
|
||||
rm -f "$prompt_file"
|
||||
# Wrap in HTML document with UTF-8 charset
|
||||
cat > "$output_file" <<'HTML_HEAD'
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
HTML_HEAD
|
||||
cat "$raw_output" >> "$output_file"
|
||||
echo "</body></html>" >> "$output_file"
|
||||
|
||||
rm -f "$prompt_file" "$raw_output"
|
||||
echo "Synopsis generated at: $output_file"
|
||||
}
|
||||
|
||||
# Create email in Mac Mail
|
||||
create_email() {
|
||||
# Get Fastmail session info (account ID, identity ID, drafts mailbox ID)
|
||||
get_fastmail_session() {
|
||||
echo "Fetching Fastmail session info..." >&2
|
||||
|
||||
# Get session
|
||||
local session=$(curl -s \
|
||||
-H "Authorization: Bearer $FASTMAIL_API_TOKEN" \
|
||||
"https://api.fastmail.com/jmap/session")
|
||||
|
||||
# Extract account ID (first account)
|
||||
FASTMAIL_ACCOUNT_ID=$(echo "$session" | jq -r '.primaryAccounts["urn:ietf:params:jmap:mail"]')
|
||||
|
||||
if [[ -z "$FASTMAIL_ACCOUNT_ID" || "$FASTMAIL_ACCOUNT_ID" == "null" ]]; then
|
||||
echo "Error: Could not get Fastmail account ID. Check your API token." >&2
|
||||
return 1
|
||||
fi
|
||||
echo "Account ID: $FASTMAIL_ACCOUNT_ID" >&2
|
||||
|
||||
# Get identity ID
|
||||
local identity_response=$(curl -s \
|
||||
-H "Authorization: Bearer $FASTMAIL_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-X POST \
|
||||
-d "{
|
||||
\"using\": [\"urn:ietf:params:jmap:core\", \"urn:ietf:params:jmap:mail\", \"urn:ietf:params:jmap:submission\"],
|
||||
\"methodCalls\": [
|
||||
[\"Identity/get\", {\"accountId\": \"$FASTMAIL_ACCOUNT_ID\"}, \"0\"]
|
||||
]
|
||||
}" \
|
||||
"$FASTMAIL_API")
|
||||
|
||||
FASTMAIL_IDENTITY_ID=$(echo "$identity_response" | jq -r '.methodResponses[0][1].list[0].id')
|
||||
FASTMAIL_FROM_EMAIL=$(echo "$identity_response" | jq -r '.methodResponses[0][1].list[0].email')
|
||||
|
||||
if [[ -z "$FASTMAIL_IDENTITY_ID" || "$FASTMAIL_IDENTITY_ID" == "null" ]]; then
|
||||
echo "Error: Could not get Fastmail identity ID." >&2
|
||||
return 1
|
||||
fi
|
||||
echo "Identity ID: $FASTMAIL_IDENTITY_ID (${FASTMAIL_FROM_EMAIL})" >&2
|
||||
|
||||
# Get drafts mailbox ID
|
||||
local mailbox_response=$(curl -s \
|
||||
-H "Authorization: Bearer $FASTMAIL_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-X POST \
|
||||
-d "{
|
||||
\"using\": [\"urn:ietf:params:jmap:core\", \"urn:ietf:params:jmap:mail\"],
|
||||
\"methodCalls\": [
|
||||
[\"Mailbox/query\", {\"accountId\": \"$FASTMAIL_ACCOUNT_ID\", \"filter\": {\"role\": \"drafts\"}}, \"0\"]
|
||||
]
|
||||
}" \
|
||||
"$FASTMAIL_API")
|
||||
|
||||
FASTMAIL_DRAFTS_ID=$(echo "$mailbox_response" | jq -r '.methodResponses[0][1].ids[0]')
|
||||
|
||||
if [[ -z "$FASTMAIL_DRAFTS_ID" || "$FASTMAIL_DRAFTS_ID" == "null" ]]; then
|
||||
echo "Error: Could not get Fastmail drafts mailbox ID." >&2
|
||||
return 1
|
||||
fi
|
||||
echo "Drafts mailbox ID: $FASTMAIL_DRAFTS_ID" >&2
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Send email via Fastmail JMAP API
|
||||
send_email_fastmail() {
|
||||
local synopsis_file="$1"
|
||||
local since_date="$2"
|
||||
local recipients_json="$2" # JSON array fragment: {"email": "a@b.com"}, {"email": "c@d.com"}
|
||||
|
||||
local subject="Eagle0 Weekly Changelog - $(date +%Y-%m-%d)"
|
||||
local html_body=$(cat "$synopsis_file" | jq -Rs .)
|
||||
|
||||
echo "Creating email draft in Mac Mail..."
|
||||
echo "Sending email via Fastmail JMAP API..."
|
||||
|
||||
osascript <<EOF
|
||||
set textContent to read POSIX file "$synopsis_file" as «class utf8»
|
||||
# Create the email and send it in one request
|
||||
local response=$(curl -s \
|
||||
-H "Authorization: Bearer $FASTMAIL_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-X POST \
|
||||
-d "{
|
||||
\"using\": [
|
||||
\"urn:ietf:params:jmap:core\",
|
||||
\"urn:ietf:params:jmap:mail\",
|
||||
\"urn:ietf:params:jmap:submission\"
|
||||
],
|
||||
\"methodCalls\": [
|
||||
[\"Email/set\", {
|
||||
\"accountId\": \"$FASTMAIL_ACCOUNT_ID\",
|
||||
\"create\": {
|
||||
\"draft\": {
|
||||
\"from\": [{\"email\": \"$FASTMAIL_FROM_EMAIL\"}],
|
||||
\"to\": [$recipients_json],
|
||||
\"subject\": \"$subject\",
|
||||
\"mailboxIds\": {\"$FASTMAIL_DRAFTS_ID\": true},
|
||||
\"keywords\": {\"\$draft\": true},
|
||||
\"htmlBody\": [{\"partId\": \"body\", \"type\": \"text/html\"}],
|
||||
\"bodyValues\": {
|
||||
\"body\": {
|
||||
\"charset\": \"utf-8\",
|
||||
\"value\": $html_body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, \"0\"],
|
||||
[\"EmailSubmission/set\", {
|
||||
\"accountId\": \"$FASTMAIL_ACCOUNT_ID\",
|
||||
\"onSuccessDestroyEmail\": [\"#sendIt\"],
|
||||
\"create\": {
|
||||
\"sendIt\": {
|
||||
\"emailId\": \"#draft\",
|
||||
\"identityId\": \"$FASTMAIL_IDENTITY_ID\"
|
||||
}
|
||||
}
|
||||
}, \"1\"]
|
||||
]
|
||||
}" \
|
||||
"$FASTMAIL_API")
|
||||
|
||||
tell application "Mail"
|
||||
set newMessage to make new outgoing message with properties {subject:"$subject", content:textContent, visible:true}
|
||||
activate
|
||||
end tell
|
||||
EOF
|
||||
# Check for errors
|
||||
local error=$(echo "$response" | jq -r '.methodResponses[0][1].notCreated.draft.description // empty')
|
||||
if [[ -n "$error" ]]; then
|
||||
echo "Error creating email: $error" >&2
|
||||
echo "Full response: $response" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Email draft created"
|
||||
local send_error=$(echo "$response" | jq -r '.methodResponses[1][1].notCreated.sendIt.description // empty')
|
||||
if [[ -n "$send_error" ]]; then
|
||||
echo "Error sending email: $send_error" >&2
|
||||
echo "Full response: $response" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Email sent successfully"
|
||||
}
|
||||
|
||||
# Update the tag to mark this run
|
||||
@@ -192,13 +393,34 @@ main() {
|
||||
echo "=== Eagle0 Weekly Changelog Generator ==="
|
||||
echo ""
|
||||
|
||||
# Load API token (only required for actual send)
|
||||
if [[ "$DRY_RUN" != "true" ]]; then
|
||||
if ! load_api_token; then
|
||||
echo "Error: No Fastmail API token found."
|
||||
echo ""
|
||||
echo "To create a token:"
|
||||
echo "1. Go to Fastmail Settings -> Password & Security -> API tokens"
|
||||
echo "2. Create a new token with 'Email submission' scope"
|
||||
echo "3. Save it using one of these methods:"
|
||||
echo ""
|
||||
echo " Option A (recommended): Store in config file"
|
||||
echo " mkdir -p ~/.config/eagle0"
|
||||
echo " echo 'your-token' > ~/.config/eagle0/fastmail_token"
|
||||
echo " chmod 600 ~/.config/eagle0/fastmail_token"
|
||||
echo ""
|
||||
echo " Option B: Set environment variable"
|
||||
echo " export FASTMAIL_API_TOKEN='your-token'"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Get cutoff date
|
||||
local cutoff_date=$(get_cutoff_date)
|
||||
echo "Cutoff date: $cutoff_date"
|
||||
|
||||
# Create temp files
|
||||
local pr_file="/tmp/eagle0_prs_$(date +%s).md"
|
||||
local synopsis_file="/tmp/eagle0_synopsis_$(date +%s).txt"
|
||||
local synopsis_file="/tmp/eagle0_synopsis_$(date +%s).html"
|
||||
|
||||
# Fetch PRs
|
||||
if ! fetch_merged_prs "$cutoff_date" "$pr_file"; then
|
||||
@@ -217,10 +439,26 @@ main() {
|
||||
echo "=== DRY RUN - Synopsis content ==="
|
||||
cat "$synopsis_file"
|
||||
echo ""
|
||||
echo "=== DRY RUN - Skipping email creation and tag update ==="
|
||||
echo "=== DRY RUN - Skipping email send and tag update ==="
|
||||
else
|
||||
# Create email
|
||||
create_email "$synopsis_file" "$cutoff_date"
|
||||
# Get Fastmail session info
|
||||
if ! get_fastmail_session; then
|
||||
echo "Failed to get Fastmail session info. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine recipients (from config file, or default to sender)
|
||||
local recipients_json=$(load_recipients_json)
|
||||
if [[ -z "$recipients_json" ]]; then
|
||||
recipients_json="{\"email\": \"$FASTMAIL_FROM_EMAIL\"}"
|
||||
echo "No recipients configured, sending to self ($FASTMAIL_FROM_EMAIL)"
|
||||
else
|
||||
local recipients_display=$(load_recipients_display)
|
||||
echo "Sending to: $recipients_display"
|
||||
fi
|
||||
|
||||
# Send email
|
||||
send_email_fastmail "$synopsis_file" "$recipients_json"
|
||||
|
||||
# Update tag for next run
|
||||
update_tag
|
||||
|
||||
@@ -58,4 +58,8 @@ message Hero {
|
||||
repeated EventForHeroBackstory new_backstory_events = 29;
|
||||
|
||||
.net.eagle0.eagle.common.Gender pronoun_gender = 27;
|
||||
|
||||
// The name of the faction this hero would lead, if they led one.
|
||||
// Populated for "great person" heroes from the heroes TSV; empty for others.
|
||||
string led_faction_name = 30;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,8 @@ object HeroConverter {
|
||||
imagePath: String,
|
||||
personalityWords: Vector[String],
|
||||
backstoryVersions: Vector[BackstoryVersion],
|
||||
backstoryEvents: Vector[EventForHeroBackstoryT]
|
||||
backstoryEvents: Vector[EventForHeroBackstoryT],
|
||||
ledFactionName: Option[String]
|
||||
) =>
|
||||
HeroProto(
|
||||
id = id,
|
||||
@@ -67,7 +68,8 @@ object HeroConverter {
|
||||
case BackstoryVersion(textId, date) =>
|
||||
BackstoryVersionProto(textId, Some(DateConverter.toProto(date)))
|
||||
},
|
||||
newBackstoryEvents = backstoryEvents.map(EventForHeroBackstoryConverter.toProto)
|
||||
newBackstoryEvents = backstoryEvents.map(EventForHeroBackstoryConverter.toProto),
|
||||
ledFactionName = ledFactionName.getOrElse("")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -99,6 +101,7 @@ object HeroConverter {
|
||||
backstoryVersions,
|
||||
newBackstoryEvents,
|
||||
pronounGender,
|
||||
ledFactionName,
|
||||
_ /* unknownFields */
|
||||
) =>
|
||||
HeroC(
|
||||
@@ -132,7 +135,8 @@ object HeroConverter {
|
||||
}.toVector,
|
||||
backstoryEvents = newBackstoryEvents
|
||||
.map(EventForHeroBackstoryConverter.fromProto)
|
||||
.toVector
|
||||
.toVector,
|
||||
ledFactionName = Option(ledFactionName).filter(_.nonEmpty)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,4 +40,10 @@ trait HeroT {
|
||||
final def backstoryTextId: ClientTextId = backstoryVersions.last.textId
|
||||
|
||||
def backstoryEvents: Vector[EventForHeroBackstoryT]
|
||||
|
||||
/**
|
||||
* The name of the faction this hero would lead, if they led one. Populated for "great person" heroes from the heroes
|
||||
* TSV; None for others.
|
||||
*/
|
||||
def ledFactionName: Option[String]
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ case class HeroC(
|
||||
imagePath: String = "",
|
||||
personalityWords: Vector[String] = Vector(),
|
||||
backstoryVersions: Vector[BackstoryVersion] = Vector(),
|
||||
backstoryEvents: Vector[EventForHeroBackstoryT] = Vector()
|
||||
backstoryEvents: Vector[EventForHeroBackstoryT] = Vector(),
|
||||
ledFactionName: Option[String] = None
|
||||
) extends HeroT {
|
||||
def withProfession(profession: Profession): HeroC =
|
||||
copy(profession = profession)
|
||||
|
||||
@@ -59,7 +59,8 @@ object FixedHeroes {
|
||||
private def personalityWords(commaDelimited: String): Vector[String] =
|
||||
commaDelimited.split(", ").toVector
|
||||
|
||||
private def heroFromMap(map: Map[String, String]): LoadedHero =
|
||||
private def heroFromMap(map: Map[String, String]): LoadedHero = {
|
||||
val factionName = map.getOrElse("faction_name", "").trim
|
||||
LoadedHero(
|
||||
id = -1, // Placeholder ID, will be set later
|
||||
backstory = map.getOrElse("backstory", ""),
|
||||
@@ -90,6 +91,8 @@ object FixedHeroes {
|
||||
case "other" => Gender.Other
|
||||
case _ => throw new EagleInternalException("unspecified gender")
|
||||
},
|
||||
personalityWords = personalityWords(map.getOrElse("personality", ""))
|
||||
personalityWords = personalityWords(map.getOrElse("personality", "")),
|
||||
ledFactionName = Option(factionName).filter(_.nonEmpty)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+8
-4
@@ -38,7 +38,8 @@ object LoadedHeroConversion {
|
||||
profession: Profession,
|
||||
imagePath: String,
|
||||
pronounGender: Gender,
|
||||
personalityWords: Vector[String]
|
||||
personalityWords: Vector[String],
|
||||
ledFactionName: Option[String]
|
||||
) =>
|
||||
HeroC(
|
||||
nameTextId = loadedHero.nameTextId,
|
||||
@@ -63,7 +64,8 @@ object LoadedHeroConversion {
|
||||
profession = profession,
|
||||
imagePath = imagePath,
|
||||
pronounGender = pronounGender,
|
||||
personalityWords = personalityWords
|
||||
personalityWords = personalityWords,
|
||||
ledFactionName = ledFactionName
|
||||
)
|
||||
}
|
||||
|
||||
@@ -86,7 +88,8 @@ object LoadedHeroConversion {
|
||||
profession: Profession,
|
||||
imagePath: String,
|
||||
pronounGender: Gender,
|
||||
personalityWords: Vector[String]
|
||||
personalityWords: Vector[String],
|
||||
ledFactionName: Option[String]
|
||||
) =>
|
||||
Hero(
|
||||
nameTextId = loadedHero.nameTextId,
|
||||
@@ -111,7 +114,8 @@ object LoadedHeroConversion {
|
||||
profession = ProfessionConverter.toProto(profession),
|
||||
imagePath = imagePath,
|
||||
pronounGender = GenderConverter.toProto(pronounGender),
|
||||
personalityWords = personalityWords
|
||||
personalityWords = personalityWords,
|
||||
ledFactionName = ledFactionName.getOrElse("")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ case class LoadedHero(
|
||||
profession: Profession = Profession.NoProfession,
|
||||
imagePath: String = "",
|
||||
pronounGender: Gender = Gender.Other,
|
||||
personalityWords: Vector[String] = Vector.empty
|
||||
personalityWords: Vector[String] = Vector.empty,
|
||||
ledFactionName: Option[String] = None
|
||||
) {
|
||||
def withId(newId: HeroId): LoadedHero = copy(id = newId)
|
||||
def withFactionId(newFactionId: FactionId): LoadedHero =
|
||||
|
||||
@@ -70,7 +70,8 @@ class FixedHeroesTest extends AnyFlatSpec with Matchers {
|
||||
imagePath = "fixed/eagle.png",
|
||||
pronounGender = Gender.Male,
|
||||
personalityWords = Vector("one", "two", "three"),
|
||||
backstory = "The Eagle's backstory"
|
||||
backstory = "The Eagle's backstory",
|
||||
ledFactionName = Some("The Eagle's Faction")
|
||||
)
|
||||
)
|
||||
fh.greatPeople.head.greatPerson.factionName shouldBe "The Eagle's Faction"
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
#
|
||||
# This creates a minimal sysroot containing headers and libraries needed
|
||||
# to cross-compile C++ code targeting Linux x86_64.
|
||||
#
|
||||
# Key requirement: Clang needs to find GCC's installation to locate libstdc++
|
||||
# headers. It looks for /usr/lib/gcc/<triple>/<version>/ in the sysroot.
|
||||
|
||||
FROM ubuntu:24.04
|
||||
|
||||
@@ -10,6 +13,9 @@ FROM ubuntu:24.04
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
# C++ standard library with C++23 support (GCC 13)
|
||||
libstdc++-13-dev \
|
||||
# GCC 13 (needed for Clang to find C++ headers via GCC detection)
|
||||
gcc-13 \
|
||||
g++-13 \
|
||||
# C library
|
||||
libc6-dev \
|
||||
# Linux kernel headers
|
||||
@@ -20,11 +26,19 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
# Clean up
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create a script to extract the sysroot
|
||||
# Create sysroot directory
|
||||
RUN mkdir -p /sysroot
|
||||
|
||||
# Copy the essential directories for cross-compilation
|
||||
# Note: We preserve the exact directory structure that clang expects
|
||||
RUN cp -a /usr/include /sysroot/ && \
|
||||
cp -a /usr/lib/x86_64-linux-gnu /sysroot/usr_lib && \
|
||||
mkdir -p /sysroot/lib && \
|
||||
cp -a /lib/x86_64-linux-gnu /sysroot/lib/
|
||||
cp -a /lib/x86_64-linux-gnu /sysroot/lib/ && \
|
||||
# Copy GCC installation directory - Clang uses this to find libstdc++ headers
|
||||
# Only copy headers and static libs, exclude plugins (.so files) that Bazel can't handle
|
||||
mkdir -p /sysroot/usr/lib/gcc/x86_64-linux-gnu/13 && \
|
||||
cp -a /usr/lib/gcc/x86_64-linux-gnu/13/include /sysroot/usr/lib/gcc/x86_64-linux-gnu/13/ && \
|
||||
cp -a /usr/lib/gcc/x86_64-linux-gnu/13/include-fixed /sysroot/usr/lib/gcc/x86_64-linux-gnu/13/ 2>/dev/null || true && \
|
||||
cp /usr/lib/gcc/x86_64-linux-gnu/13/*.a /sysroot/usr/lib/gcc/x86_64-linux-gnu/13/ 2>/dev/null || true && \
|
||||
cp /usr/lib/gcc/x86_64-linux-gnu/13/*.o /sysroot/usr/lib/gcc/x86_64-linux-gnu/13/ 2>/dev/null || true
|
||||
|
||||
@@ -30,6 +30,7 @@ echo "Extracting sysroot..."
|
||||
CONTAINER_ID=$(docker create sysroot-builder)
|
||||
|
||||
# Create the sysroot structure expected by toolchains_llvm
|
||||
# The tarball should extract to create a 'sysroot' directory at the root
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
SYSROOT_DIR="${TEMP_DIR}/sysroot"
|
||||
mkdir -p "${SYSROOT_DIR}/usr/lib" "${SYSROOT_DIR}/lib"
|
||||
@@ -39,14 +40,22 @@ docker cp "${CONTAINER_ID}:/sysroot/include" "${SYSROOT_DIR}/usr/"
|
||||
docker cp "${CONTAINER_ID}:/sysroot/usr_lib" "${SYSROOT_DIR}/usr/lib/x86_64-linux-gnu"
|
||||
docker cp "${CONTAINER_ID}:/sysroot/lib/x86_64-linux-gnu" "${SYSROOT_DIR}/lib/"
|
||||
|
||||
# Copy GCC installation directory - Clang uses this to find libstdc++ headers
|
||||
# Only copy what the Dockerfile prepared (headers and static libs, no .so plugins)
|
||||
docker cp "${CONTAINER_ID}:/sysroot/usr/lib/gcc" "${SYSROOT_DIR}/usr/lib/"
|
||||
|
||||
# Also need lib64 symlink for some builds
|
||||
ln -sf lib "${SYSROOT_DIR}/lib64"
|
||||
|
||||
# Create the tarball
|
||||
# Create the tarball - use strip-components-friendly structure
|
||||
# The tarball extracts directly, MODULE.bazel label points to //sysroot
|
||||
echo "Creating tarball..."
|
||||
cd "${TEMP_DIR}"
|
||||
tar -cJf "${OUTPUT_DIR}/${SYSROOT_NAME}.tar.xz" sysroot
|
||||
|
||||
echo "Verifying tarball structure..."
|
||||
tar -tf "${OUTPUT_DIR}/${SYSROOT_NAME}.tar.xz" | head -20
|
||||
|
||||
# Compute SHA256
|
||||
echo "Computing SHA256..."
|
||||
SHA256=$(shasum -a 256 "${OUTPUT_DIR}/${SYSROOT_NAME}.tar.xz" | cut -d' ' -f1)
|
||||
|
||||
Reference in New Issue
Block a user