summaryrefslogtreecommitdiff
path: root/dist/macos/update_appcast_tag.py
blob: 2cb20dd5d51e67c596442ff7966867d3294b52e1 (plain)
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
"""
This script is used to update the appcast.xml file for tagged
Ghostty releases.

This expects the following files in the current directory:
    - sign_update.txt - contains the output from "sign_update" in the Sparkle
      framework for the current build.
    - appcast.xml - the existing appcast file.

And the following environment variables to be set:
    - GHOSTTY_VERSION - the version number (X.Y.Z format)
    - GHOSTTY_BUILD - the build number
    - GHOSTTY_COMMIT - the commit hash

The script will output a new appcast file called appcast_new.xml.
"""

import os
import xml.etree.ElementTree as ET
from datetime import datetime, timezone

now = datetime.now(timezone.utc)
version = os.environ["GHOSTTY_VERSION"]
version_dash = version.replace('.', '-')
build = os.environ["GHOSTTY_BUILD"]
commit = os.environ["GHOSTTY_COMMIT"]
commit_long = os.environ["GHOSTTY_COMMIT_LONG"]
repo = "https://github.com/ghostty-org/ghostty"

# Read our sign_update output
with open("sign_update.txt", "r") as f:
    # format is a=b b=c etc. create a map of this. values may contain equal
    # signs, so we can't just split on equal signs.
    attrs = {}
    for pair in f.read().split(" "):
        key, value = pair.split("=", 1)
        value = value.strip()
        if value[0] == '"':
            value = value[1:-1]
        attrs[key] = value

# We need to register our namespaces before reading or writing any files.
namespaces = { "sparkle": "http://www.andymatuschak.org/xml-namespaces/sparkle" }
for prefix, uri in namespaces.items():
    ET.register_namespace(prefix, uri)

# Open our existing appcast and find the channel element. This is where
# we'll add our new item.
et = ET.parse('appcast.xml')
channel = et.find("channel")

# Remove any items with the same version. If we have multiple items with
# the same version, Sparkle will report invalid signatures if it picks
# the wrong one when updating.
for item in channel.findall("item"):
    sparkle_version = item.find("sparkle:version", namespaces)
    if sparkle_version is not None and sparkle_version.text == build:
        channel.remove(item)

    # We also remove any item that doesn't have a pubDate. This should
    # never happen but it prevents us from having to deal with it later.
    if item.find("pubDate") is None:
        channel.remove(item)

# Prune the oldest items if we have more than a limit.
prune_amount = 15
pubdate_format = "%a, %d %b %Y %H:%M:%S %z"
items = channel.findall("item")
items.sort(key=lambda item: datetime.strptime(item.find("pubDate").text, pubdate_format))
if len(items) > prune_amount:
    for item in items[:-prune_amount]:
        channel.remove(item)

# Create the item using some absolutely terrible XML manipulation.
item = ET.SubElement(channel, "item")
elem = ET.SubElement(item, "title")
elem.text = f"Build {build}"
elem = ET.SubElement(item, "pubDate")
elem.text = now.strftime(pubdate_format)
elem = ET.SubElement(item, "sparkle:version")
elem.text = build
elem = ET.SubElement(item, "sparkle:shortVersionString")
elem.text = f"{version}"
elem = ET.SubElement(item, "sparkle:minimumSystemVersion")
elem.text = "13.0.0"
elem = ET.SubElement(item, "sparkle:fullReleaseNotesLink")
elem.text = f"https://ghostty.org/docs/install/release-notes/{version_dash}"
elem = ET.SubElement(item, "description")
elem.text = f"""
<h1>Ghostty v{version}</h1>
<p>
This release was built from commit <code><a href="{repo}/commits/{commit_long}">{commit}</a></code>
on {now.strftime('%Y-%m-%d')}.
</p>
<p>
We don't currently generate release notes for auto-updates.
You can view the complete changelog and release notes
at <a href="https://ghostty.org/docs/install/release-notes/{version_dash}">ghostty.org/docs/install/release-notes/{version_dash}</a>.
</p>
"""
elem = ET.SubElement(item, "enclosure")
elem.set("url", f"https://release.files.ghostty.org/{version}/Ghostty.dmg")
elem.set("type", "application/octet-stream")
for key, value in attrs.items():
    elem.set(key, value)

# Output the new appcast.
et.write("appcast_new.xml", xml_declaration=True, encoding="utf-8")