Compare commits

...
Author SHA1 Message Date
adminandClaude Opus 4.5 b8e5346209 Fix sysroot .so plugins and registry auth
- Exclude GCC plugin .so files from sysroot (Bazel can't handle them)
- Only copy GCC headers and static libs needed for cross-compilation
- Add tarball structure verification to build script
- Fix DO registry auth by setting DOCKER_CONFIG env var for Bazel

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 08:50:44 -08:00
adminandClaude Opus 4.5 c128531662 Update sysroot sha256 for v2 with GCC directories
The v2 sysroot includes GCC installation directories that clang needs
to find libstdc++ headers for cross-compilation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 08:34:47 -08:00
ee6189a9d7 Fix AWS CLI and use correct bucket for sysroot upload (#4780)
- Skip AWS CLI install if already present
- Use eagle0-windows bucket (same as other workflows, credentials have access)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 08:30:55 -08:00
e0304125b8 Add ledFactionName field to Hero for faction renaming (#4781)
Add a new field to the Hero proto and Scala models to store the name of the
faction that a hero would lead, if they became a faction leader. This is
populated from the "faction_name" column in the heroes TSV for "great person"
heroes, and is empty (or None in Scala) for other heroes.

This will be used in the future to rename factions when a great person
becomes the leader of a different faction.

Changes:
- Add led_faction_name (string) to Hero proto
- Add ledFactionName: Option[String] to HeroT trait and HeroC case class
- Add ledFactionName to LoadedHero intermediate type
- Update HeroConverter, LoadedHeroConversion, and FixedHeroes to handle the new field
- Update FixedHeroesTest to include the new field

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 08:23:04 -08:00
11eac42e30 Fix sysroot workflow secret names (#4779)
Use existing ACCESS_KEY_ID and SECRET_KEY secrets instead of
non-existent DO_SPACES_KEY and DO_SPACES_SECRET.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 08:12:19 -08:00
d0a63f1c61 Fix sysroot for cross-compilation (#4778)
Issues fixed:
1. Sysroot hosted on GitHub releases but repo is private, causing 404s
2. Sysroot missing GCC directories that clang needs to find libstdc++ headers

Changes:
- Add GCC installation directories to sysroot (clang uses these to locate C++ headers)
- Update workflow to upload sysroot to DO Spaces instead of GitHub releases
- Versioned sysroot paths (v2, v3, etc.) for easier updates

NOTE: After merging, run the "Build Linux Sysroot" workflow with version "v2",
then update MODULE.bazel with the sha256 from the workflow output.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 07:34:02 -08:00
97df984189 Add BLUF section to changelog emails (#4777)
- Add a "Bottom Line Up Front" section after the title with a short prose
  paragraph highlighting the most important changes and what to look for
  when testing
- Wrap HTML output in proper document with UTF-8 charset declaration to
  fix Unicode character rendering (em-dashes, etc.)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 07:30:20 -08:00
6c771df84f Add PR links section to changelog emails (#4776)
Update the Claude prompt to generate a "PR Details" section after the synopsis,
with the same thematic groupings but listing actual PR numbers, titles, and
clickable GitHub links.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 07:23:32 -08:00
7e40420fe1 Update changelog script to use Fastmail JMAP API for HTML emails (#4774)
- Switch from Mac Mail AppleScript to Fastmail JMAP API
- Generate HTML synopsis instead of plain text for better formatting
- Auto-fetch account ID, identity ID, and drafts mailbox from API
- Support config files in ~/.config/eagle0/:
  - fastmail_token: API token (required)
  - changelog_recipient: Email recipients, one per line (optional)
- Support multiple recipients (one email address per line, # for comments)
- Fall back to FASTMAIL_API_TOKEN environment variable for token
- Only require token when not in --dry-run mode

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-23 07:16:22 -08:00
14 changed files with 382 additions and 71 deletions
+39 -23
View File
@@ -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 ")"
+10
View File
@@ -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
View File
@@ -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
View File
@@ -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)
)
}
}
@@ -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"
+16 -2
View File
@@ -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
+10 -1
View File
@@ -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)