pxsteamdl 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. pxsteamdl-0.4.0/.clang-format +8 -0
  2. pxsteamdl-0.4.0/.clang-tidy +29 -0
  3. pxsteamdl-0.4.0/.github/scripts/update-ca-bundle.sh +55 -0
  4. pxsteamdl-0.4.0/.github/smoke_test.py +66 -0
  5. pxsteamdl-0.4.0/.github/workflows/ci.yml +140 -0
  6. pxsteamdl-0.4.0/.github/workflows/release.yml +226 -0
  7. pxsteamdl-0.4.0/.github/workflows/update-ca-bundle.yml +50 -0
  8. pxsteamdl-0.4.0/.gitignore +23 -0
  9. pxsteamdl-0.4.0/CHANGELOG.md +118 -0
  10. pxsteamdl-0.4.0/CMakeLists.txt +380 -0
  11. pxsteamdl-0.4.0/CONTRIBUTING.md +101 -0
  12. pxsteamdl-0.4.0/COPYING +674 -0
  13. pxsteamdl-0.4.0/COPYING.LESSER +165 -0
  14. pxsteamdl-0.4.0/PKG-INFO +259 -0
  15. pxsteamdl-0.4.0/README.md +246 -0
  16. pxsteamdl-0.4.0/cli/main.cpp +117 -0
  17. pxsteamdl-0.4.0/cli/utf8.manifest +10 -0
  18. pxsteamdl-0.4.0/cmake/CPM.cmake +28 -0
  19. pxsteamdl-0.4.0/cmake/MergeArchives.cmake +37 -0
  20. pxsteamdl-0.4.0/cmake/PxSteamDLConfig.cmake.in +18 -0
  21. pxsteamdl-0.4.0/cmake/PxSteamDLInstall.cmake +115 -0
  22. pxsteamdl-0.4.0/cmake/mbedtls/pxsteamdl_mbedtls_config.h +10 -0
  23. pxsteamdl-0.4.0/cmake/mbedtls/threading_alt.h +7 -0
  24. pxsteamdl-0.4.0/cmake/version.hpp.in +8 -0
  25. pxsteamdl-0.4.0/examples/download.cpp +104 -0
  26. pxsteamdl-0.4.0/examples/download.py +80 -0
  27. pxsteamdl-0.4.0/examples/download_async.py +56 -0
  28. pxsteamdl-0.4.0/include/pxsteamdl/error.hpp +48 -0
  29. pxsteamdl-0.4.0/include/pxsteamdl/pxsteamdl.hpp +140 -0
  30. pxsteamdl-0.4.0/pyproject.toml +53 -0
  31. pxsteamdl-0.4.0/python/pxsteamdl/__init__.py +30 -0
  32. pxsteamdl-0.4.0/python/pxsteamdl/_async.py +87 -0
  33. pxsteamdl-0.4.0/python/pxsteamdl/py.typed +0 -0
  34. pxsteamdl-0.4.0/python/pxsteamdl.cpp +224 -0
  35. pxsteamdl-0.4.0/src/cdn.cpp +62 -0
  36. pxsteamdl-0.4.0/src/cdn.hpp +61 -0
  37. pxsteamdl-0.4.0/src/chunk.cpp +104 -0
  38. pxsteamdl-0.4.0/src/chunk.hpp +14 -0
  39. pxsteamdl-0.4.0/src/client.cpp +120 -0
  40. pxsteamdl-0.4.0/src/cm_packet.cpp +153 -0
  41. pxsteamdl-0.4.0/src/cm_packet.hpp +52 -0
  42. pxsteamdl-0.4.0/src/common.hpp +74 -0
  43. pxsteamdl-0.4.0/src/crypto.cpp +61 -0
  44. pxsteamdl-0.4.0/src/crypto.hpp +25 -0
  45. pxsteamdl-0.4.0/src/directory_lock.cpp +183 -0
  46. pxsteamdl-0.4.0/src/directory_lock.hpp +26 -0
  47. pxsteamdl-0.4.0/src/directory_update.cpp +109 -0
  48. pxsteamdl-0.4.0/src/directory_update.hpp +40 -0
  49. pxsteamdl-0.4.0/src/downloader.cpp +506 -0
  50. pxsteamdl-0.4.0/src/downloader.hpp +65 -0
  51. pxsteamdl-0.4.0/src/eresult.cpp +162 -0
  52. pxsteamdl-0.4.0/src/eresult.hpp +86 -0
  53. pxsteamdl-0.4.0/src/error.cpp +28 -0
  54. pxsteamdl-0.4.0/src/http.cpp +137 -0
  55. pxsteamdl-0.4.0/src/http.hpp +50 -0
  56. pxsteamdl-0.4.0/src/manifest.cpp +243 -0
  57. pxsteamdl-0.4.0/src/manifest.hpp +56 -0
  58. pxsteamdl-0.4.0/src/paths.cpp +68 -0
  59. pxsteamdl-0.4.0/src/paths.hpp +30 -0
  60. pxsteamdl-0.4.0/src/pending_file.cpp +244 -0
  61. pxsteamdl-0.4.0/src/pending_file.hpp +59 -0
  62. pxsteamdl-0.4.0/src/proto.cpp +111 -0
  63. pxsteamdl-0.4.0/src/proto.hpp +58 -0
  64. pxsteamdl-0.4.0/src/retry.cpp +25 -0
  65. pxsteamdl-0.4.0/src/retry.hpp +36 -0
  66. pxsteamdl-0.4.0/src/session.cpp +464 -0
  67. pxsteamdl-0.4.0/src/session.hpp +42 -0
  68. pxsteamdl-0.4.0/src/sleep.cpp +21 -0
  69. pxsteamdl-0.4.0/src/sleep.hpp +13 -0
  70. pxsteamdl-0.4.0/src/version.cpp +8 -0
  71. pxsteamdl-0.4.0/src/workshop.cpp +154 -0
  72. pxsteamdl-0.4.0/src/workshop.hpp +36 -0
  73. pxsteamdl-0.4.0/src/zip.cpp +118 -0
  74. pxsteamdl-0.4.0/src/zip.hpp +12 -0
  75. pxsteamdl-0.4.0/tests/chunk_test.cpp +86 -0
  76. pxsteamdl-0.4.0/tests/client_test.cpp +1187 -0
  77. pxsteamdl-0.4.0/tests/cm_packet_test.cpp +93 -0
  78. pxsteamdl-0.4.0/tests/crypto_test.cpp +43 -0
  79. pxsteamdl-0.4.0/tests/depot_builder.hpp +68 -0
  80. pxsteamdl-0.4.0/tests/directory_lock_test.cpp +259 -0
  81. pxsteamdl-0.4.0/tests/directory_update_test.cpp +84 -0
  82. pxsteamdl-0.4.0/tests/eresult_test.cpp +52 -0
  83. pxsteamdl-0.4.0/tests/error_test.cpp +80 -0
  84. pxsteamdl-0.4.0/tests/fake_steam.cpp +310 -0
  85. pxsteamdl-0.4.0/tests/fake_steam.hpp +127 -0
  86. pxsteamdl-0.4.0/tests/fakes/fake_http.cpp +48 -0
  87. pxsteamdl-0.4.0/tests/fakes/fake_http.hpp +24 -0
  88. pxsteamdl-0.4.0/tests/fakes/fake_session.cpp +67 -0
  89. pxsteamdl-0.4.0/tests/fakes/fake_session.hpp +26 -0
  90. pxsteamdl-0.4.0/tests/fakes/fake_sleep.cpp +36 -0
  91. pxsteamdl-0.4.0/tests/fakes/fake_sleep.hpp +16 -0
  92. pxsteamdl-0.4.0/tests/install_consumer/CMakeLists.txt +11 -0
  93. pxsteamdl-0.4.0/tests/install_consumer/main.cpp +25 -0
  94. pxsteamdl-0.4.0/tests/lock_holder.cpp +24 -0
  95. pxsteamdl-0.4.0/tests/manifest_test.cpp +126 -0
  96. pxsteamdl-0.4.0/tests/paths_test.cpp +50 -0
  97. pxsteamdl-0.4.0/tests/proto_test.cpp +69 -0
  98. pxsteamdl-0.4.0/tests/retry_test.cpp +153 -0
  99. pxsteamdl-0.4.0/tests/sleep_test.cpp +49 -0
  100. pxsteamdl-0.4.0/tests/test_util.hpp +135 -0
  101. pxsteamdl-0.4.0/tests/version_test.cpp +19 -0
  102. pxsteamdl-0.4.0/tests/zip_test.cpp +48 -0
@@ -0,0 +1,8 @@
1
+ # Google C++ style, lightened: 2-space indent, 120 columns (see CONTRIBUTING.md).
2
+ BasedOnStyle: Google
3
+ IndentWidth: 2
4
+ ColumnLimit: 120
5
+ AccessModifierOffset: -1
6
+ DerivePointerAlignment: false
7
+ PointerAlignment: Left
8
+ IncludeBlocks: Preserve
@@ -0,0 +1,29 @@
1
+ # Naming rules of the project's lightened Google C++ style (see CONTRIBUTING.md).
2
+ Checks: '-*,readability-identifier-naming'
3
+ WarningsAsErrors: '*'
4
+ HeaderFilterRegex: '(src|include|cli|python|tests)/'
5
+ CheckOptions:
6
+ readability-identifier-naming.NamespaceCase: lower_case
7
+ readability-identifier-naming.ClassCase: CamelCase
8
+ readability-identifier-naming.StructCase: CamelCase
9
+ readability-identifier-naming.EnumCase: CamelCase
10
+ readability-identifier-naming.TypeAliasCase: CamelCase
11
+ readability-identifier-naming.TemplateParameterCase: CamelCase
12
+ readability-identifier-naming.FunctionCase: CamelCase
13
+ readability-identifier-naming.FunctionIgnoredRegexp: '^main$'
14
+ readability-identifier-naming.MethodCase: camelBack
15
+ readability-identifier-naming.VariableCase: lower_case
16
+ readability-identifier-naming.ParameterCase: lower_case
17
+ readability-identifier-naming.MemberCase: lower_case
18
+ readability-identifier-naming.PrivateMemberCase: camelBack
19
+ readability-identifier-naming.PrivateMemberPrefix: 'm_'
20
+ readability-identifier-naming.ProtectedMemberCase: camelBack
21
+ readability-identifier-naming.ProtectedMemberPrefix: 'm_'
22
+ readability-identifier-naming.ConstexprVariableCase: CamelCase
23
+ readability-identifier-naming.ConstexprVariablePrefix: 'k'
24
+ readability-identifier-naming.StaticConstantCase: CamelCase
25
+ readability-identifier-naming.StaticConstantPrefix: 'k'
26
+ readability-identifier-naming.EnumConstantCase: CamelCase
27
+ readability-identifier-naming.EnumConstantPrefix: 'k'
28
+ readability-identifier-naming.GlobalConstantCase: CamelCase
29
+ readability-identifier-naming.GlobalConstantPrefix: 'k'
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
+ # Looks for a Mozilla CA bundle (as extracted by curl) that is newer than the one pinned in CMakeLists.txt and, if
4
+ # there is one, rewrites the pinned date and SHA-256 there. Writes "date=<new date>" to $GITHUB_OUTPUT when it did.
5
+ # CA_BASE_URL and CMAKE_FILE exist for testing.
6
+ set -euo pipefail
7
+
8
+ base=${CA_BASE_URL:-https://curl.se/ca}
9
+ cmake_file=${CMAKE_FILE:-CMakeLists.txt}
10
+ work=$(mktemp -d)
11
+ trap 'rm -rf "$work"' EXIT
12
+
13
+ current=$(sed -n 's/^set(PXSTEAMDL_CA_BUNDLE_DATE \([0-9-]*\))$/\1/p' "$cmake_file")
14
+ if [ -z "$current" ]; then
15
+ echo "::error::No PXSTEAMDL_CA_BUNDLE_DATE in $cmake_file" >&2
16
+ exit 1
17
+ fi
18
+
19
+ # The newest bundle names the date it was extracted on in its header: "## Certificate data from Mozilla as of: ...".
20
+ curl -fsSL "$base/cacert.pem" -o "$work/latest.pem"
21
+ as_of=$(sed -n 's/^## Certificate data from Mozilla as of: //p' "$work/latest.pem" | head -n 1)
22
+ if [ -z "$as_of" ]; then
23
+ echo "::error::The bundle has no 'Certificate data from Mozilla as of' line" >&2
24
+ exit 1
25
+ fi
26
+ new=$(date -u -d "$as_of" +%F)
27
+ if [[ ! "$new" > "$current" ]]; then
28
+ echo "The pinned bundle ($current) is up to date; the newest is $new"
29
+ exit 0
30
+ fi
31
+
32
+ # The pin names a file by date, so fetch that very file and check that it is what the newest bundle is.
33
+ if ! curl -fsSL "$base/cacert-$new.pem" -o "$work/dated.pem"; then
34
+ echo "::error::$base/cacert-$new.pem does not exist; the date in the bundle's header may not be its file name" >&2
35
+ exit 1
36
+ fi
37
+ hash=$(sha256sum "$work/dated.pem" | cut -d' ' -f1)
38
+ if [ "$hash" != "$(sha256sum "$work/latest.pem" | cut -d' ' -f1)" ]; then
39
+ echo "::error::cacert-$new.pem differs from cacert.pem" >&2
40
+ exit 1
41
+ fi
42
+ if [ "$(grep -c 'BEGIN CERTIFICATE' "$work/dated.pem")" -lt 50 ]; then
43
+ echo "::error::cacert-$new.pem has suspiciously few certificates" >&2
44
+ exit 1
45
+ fi
46
+
47
+ # The hash line to change is the one of the cacert package; other packages have theirs.
48
+ sed -i "s/^set(PXSTEAMDL_CA_BUNDLE_DATE .*/set(PXSTEAMDL_CA_BUNDLE_DATE $new)/" "$cmake_file"
49
+ sed -i "/NAME cacert/,/DOWNLOAD_NO_EXTRACT/ s/SHA256=[0-9a-f]\{64\}/SHA256=$hash/" "$cmake_file"
50
+ if ! grep -q "SHA256=$hash" "$cmake_file" || ! grep -q "PXSTEAMDL_CA_BUNDLE_DATE $new)" "$cmake_file"; then
51
+ echo "::error::Could not update $cmake_file" >&2
52
+ exit 1
53
+ fi
54
+ echo "Pinned bundle: $current -> $new ($hash)"
55
+ echo "date=$new" >> "${GITHUB_OUTPUT:-/dev/null}"
@@ -0,0 +1,66 @@
1
+ # SPDX-License-Identifier: LGPL-3.0-or-later
2
+ """Downloads small Workshop items through the installed package, synchronously and with asyncio."""
3
+ import asyncio
4
+ import faulthandler
5
+ import pathlib
6
+ import tempfile
7
+ import threading
8
+
9
+ import pxsteamdl
10
+
11
+ ITEMS = [2009463077, 818773962] # Harmony, HugsLib
12
+
13
+ # A hang fails the job with a traceback of every thread instead of running into the CI timeout.
14
+ faulthandler.dump_traceback_later(300, exit=True)
15
+
16
+ with tempfile.TemporaryDirectory() as tmp:
17
+ root = pathlib.Path(tmp)
18
+
19
+ caller = threading.get_ident()
20
+ resolved = []
21
+ first_progress = {}
22
+
23
+ def on_resolved(info):
24
+ assert threading.get_ident() == caller
25
+ assert info.item_id not in first_progress, "progress before on_resolved"
26
+ resolved.append(info.item_id)
27
+
28
+ def on_progress(p):
29
+ first_progress.setdefault(p.item_id, len(resolved))
30
+
31
+ results = pxsteamdl.Client().download(ITEMS, root / "sync", on_progress=on_progress, on_resolved=on_resolved)
32
+ print(results)
33
+ assert resolved == ITEMS, resolved
34
+ for result in results:
35
+ assert result.ok, result.error
36
+ assert result.error_kind == pxsteamdl.ErrorKind.NONE
37
+ assert (result.path / "About" / "About.xml").is_file()
38
+ assert result.downloaded_bytes > 0 and result.unpacked_bytes > 0
39
+
40
+ # A second run finds everything in place and fetches nothing; a repeated ID is answered each time.
41
+ again = pxsteamdl.Client().download(ITEMS + ITEMS[:1], root / "sync")
42
+ assert [r.item_id for r in again] == ITEMS + ITEMS[:1]
43
+ assert all(r.ok and r.downloaded_bytes == 0 and r.unpacked_bytes == 0 for r in again), again
44
+
45
+ # An item that does not exist is not found (or at least refused), and says so by kind, not by wording.
46
+ missing = pxsteamdl.Client().download([1], root / "missing")[0]
47
+ assert not missing.ok and missing.error_kind in (pxsteamdl.ErrorKind.NOT_FOUND, pxsteamdl.ErrorKind.REJECTED), missing
48
+ assert not missing.cancelled
49
+
50
+ async def download_async():
51
+ client = await pxsteamdl.AsyncClient.create()
52
+ loop_thread = threading.get_ident()
53
+ calls = []
54
+
55
+ def on_progress(p):
56
+ assert threading.get_ident() == loop_thread
57
+ calls.append(p)
58
+
59
+ results = await client.download(ITEMS[:1], root / "async", on_progress=on_progress)
60
+ print(results)
61
+ assert results[0].ok, results[0].error
62
+ assert (results[0].path / "About" / "About.xml").is_file()
63
+ assert calls
64
+
65
+ asyncio.run(download_async())
66
+ print("smoke test passed")
@@ -0,0 +1,140 @@
1
+ name: CI
2
+
3
+ # Pull requests are checked by the pull_request event; push covers main only, so a PR commit runs once and tags
4
+ # (built by the Release workflow) do not rerun CI. The smoke tests download real items, so a failure may be Steam's
5
+ # and not the code's: a run on main every day shows whether main itself is fine.
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ pull_request:
10
+ workflow_dispatch:
11
+ schedule:
12
+ - cron: "17 5 * * *"
13
+
14
+ # A new push to a PR supersedes its running check; every commit on main keeps its own run.
15
+ concurrency:
16
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
17
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
18
+
19
+ jobs:
20
+ build:
21
+ strategy:
22
+ fail-fast: false
23
+ matrix:
24
+ os: [ubuntu-latest, windows-latest, macos-latest]
25
+ runs-on: ${{ matrix.os }}
26
+ env:
27
+ CPM_SOURCE_CACHE: ${{ github.workspace }}/.cpm-cache
28
+ defaults:
29
+ run:
30
+ shell: bash
31
+ steps:
32
+ - uses: actions/checkout@v7
33
+
34
+ - uses: actions/cache@v6
35
+ with:
36
+ path: .cpm-cache
37
+ key: cpm-${{ runner.os }}-${{ hashFiles('CMakeLists.txt') }}
38
+
39
+ - uses: ilammy/msvc-dev-cmd@v1.13.0
40
+ if: runner.os == 'Windows'
41
+
42
+ - name: Install Ninja
43
+ if: runner.os == 'macOS'
44
+ run: brew install ninja
45
+
46
+ - name: Configure
47
+ run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DPXSTEAMDL_BUILD_TESTS=ON -DPXSTEAMDL_BUILD_EXAMPLES=ON
48
+
49
+ - name: Build
50
+ run: cmake --build build
51
+
52
+ - name: Unit tests
53
+ run: ctest --test-dir build --output-on-failure
54
+
55
+ # Style (see CONTRIBUTING.md): formatting and naming, checked once with pinned LLVM 18 tools.
56
+ - name: clang-format
57
+ if: runner.os == 'Linux'
58
+ run: clang-format-18 --dry-run --Werror src/*.cpp src/*.hpp include/pxsteamdl/*.hpp cli/main.cpp python/pxsteamdl.cpp tests/*.cpp tests/*.hpp tests/fakes/*.?pp
59
+
60
+ - name: clang-tidy
61
+ if: runner.os == 'Linux'
62
+ run: clang-tidy-18 -p build --quiet src/*.cpp cli/main.cpp tests/*.cpp tests/fakes/*.cpp
63
+
64
+ # The installed package must be complete: a separate project builds against it with find_package.
65
+ - name: Install and use as a package
66
+ run: |
67
+ cmake --install build --prefix "$RUNNER_TEMP/prefix"
68
+ cmake -S tests/install_consumer -B "$RUNNER_TEMP/consumer" -G Ninja -DCMAKE_BUILD_TYPE=Release \
69
+ -DCMAKE_PREFIX_PATH="$RUNNER_TEMP/prefix"
70
+ cmake --build "$RUNNER_TEMP/consumer"
71
+ expected=$(sed -n 's/^project(PxSteamDL VERSION \([0-9.]*\).*/\1/p' CMakeLists.txt)
72
+ test "$("$RUNNER_TEMP/consumer/consumer" | tr -d '\r')" = "$expected"
73
+ "$RUNNER_TEMP/prefix/bin/pxsteamdl" --version
74
+
75
+ - name: Binary size
76
+ run: |
77
+ exe=build/pxsteamdl${{ runner.os == 'Windows' && '.exe' || '' }}
78
+ ls -l "$exe"
79
+ if [ "$RUNNER_OS" != Windows ]; then cp "$exe" stripped && strip stripped && ls -l stripped; fi
80
+ if [ "$RUNNER_OS" = Linux ]; then ldd "$exe"; fi
81
+ if [ "$RUNNER_OS" = macOS ]; then otool -L "$exe"; fi
82
+
83
+ - name: CLI check
84
+ run: |
85
+ status=0
86
+ ./build/pxsteamdl || status=$?
87
+ test "$status" -eq 2
88
+ # The version printed is the one in CMakeLists.txt.
89
+ expected=$(sed -n 's/^project(PxSteamDL VERSION \([0-9.]*\).*/\1/p' CMakeLists.txt)
90
+ test "$(./build/pxsteamdl --version | tr -d '\r')" = "pxsteamdl $expected"
91
+
92
+ # Downloads real items from Steam. The timeout turns a hang (seen once on a Windows runner) into a failure.
93
+ - name: Smoke test
94
+ timeout-minutes: 10
95
+ run: |
96
+ mods="$RUNNER_TEMP/mods"
97
+ ./build/pxsteamdl -o "$mods" 2009463077 818773962
98
+ test -f "$mods/2009463077/About/About.xml"
99
+ test -f "$mods/818773962/About/About.xml"
100
+ # A damaged file must be replaced (rename over an existing file).
101
+ expected=$(cksum < "$mods/2009463077/About/About.xml")
102
+ echo damaged >> "$mods/2009463077/About/About.xml"
103
+ ./build/pxsteamdl -o "$mods" 2009463077
104
+ test "$(cksum < "$mods/2009463077/About/About.xml")" = "$expected"
105
+ # An unknown item fails alone, with exit code 1 and a readable reason.
106
+ status=0
107
+ ./build/pxsteamdl -o "$mods" 2009463077 1 > "$RUNNER_TEMP/out.txt" || status=$?
108
+ cat "$RUNNER_TEMP/out.txt"
109
+ test "$status" -eq 1
110
+ grep -q "Steam rejected the item: not found (EResult 9)" "$RUNNER_TEMP/out.txt"
111
+
112
+ python:
113
+ strategy:
114
+ fail-fast: false
115
+ matrix:
116
+ os: [ubuntu-latest, windows-latest, macos-latest]
117
+ runs-on: ${{ matrix.os }}
118
+ env:
119
+ CPM_SOURCE_CACHE: ${{ github.workspace }}/.cpm-cache
120
+ steps:
121
+ - uses: actions/checkout@v7
122
+
123
+ - uses: actions/setup-python@v7
124
+ with:
125
+ python-version: "3.13"
126
+
127
+ - uses: actions/cache@v6
128
+ with:
129
+ path: .cpm-cache
130
+ key: cpm-python-${{ runner.os }}-${{ hashFiles('CMakeLists.txt') }}
131
+
132
+ - name: Install package
133
+ run: python -m pip install .
134
+
135
+ - name: Import check
136
+ run: python -c "import pxsteamdl; print(pxsteamdl.__version__, pxsteamdl.Client, pxsteamdl.AsyncClient, pxsteamdl.CancelToken)"
137
+
138
+ - name: Smoke test
139
+ timeout-minutes: 10
140
+ run: python -u .github/smoke_test.py
@@ -0,0 +1,226 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+ workflow_dispatch:
7
+ inputs:
8
+ publish:
9
+ description: "Publish GitHub Release (if false, only builds artifacts)"
10
+ type: boolean
11
+ default: false
12
+ tag_name:
13
+ description: "Release tag (e.g. v0.1.0, required if publish is true)"
14
+ type: string
15
+ default: ""
16
+
17
+ permissions:
18
+ contents: write
19
+
20
+ jobs:
21
+ check:
22
+ name: Check the version
23
+ if: (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || (github.event_name == 'workflow_dispatch' && inputs.publish && inputs.tag_name != '')
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v7
27
+
28
+ # A release is tagged vX.Y.Z, with X.Y.Z in project(PxSteamDL VERSION ...) and a section in CHANGELOG.md.
29
+ - name: Tag, version and changelog agree
30
+ shell: bash
31
+ env:
32
+ TAG: ${{ github.event.inputs.tag_name || github.ref_name }}
33
+ run: |
34
+ expected=$(sed -n 's/^project(PxSteamDL VERSION \([0-9.]*\).*/\1/p' CMakeLists.txt)
35
+ if [ "$TAG" != "v$expected" ]; then
36
+ echo "::error::Tag $TAG does not match the version $expected in CMakeLists.txt"
37
+ exit 1
38
+ fi
39
+ if ! grep -q "^## \[$expected\] - " CHANGELOG.md; then
40
+ echo "::error::CHANGELOG.md has no section for $expected"
41
+ exit 1
42
+ fi
43
+
44
+ binaries:
45
+ name: binary (${{ matrix.os }})
46
+ strategy:
47
+ fail-fast: false
48
+ matrix:
49
+ include:
50
+ - os: ubuntu-latest
51
+ artifact_name: pxsteamdl-linux-x86_64
52
+ archive_name: pxsteamdl-linux-x86_64.tar.gz
53
+ pack_type: tar
54
+ - os: macos-latest
55
+ artifact_name: pxsteamdl-macos-arm64
56
+ archive_name: pxsteamdl-macos-arm64.tar.gz
57
+ pack_type: tar
58
+ - os: windows-latest
59
+ artifact_name: pxsteamdl-windows-x64
60
+ archive_name: pxsteamdl-windows-x64.zip
61
+ pack_type: zip
62
+ runs-on: ${{ matrix.os }}
63
+ steps:
64
+ - uses: actions/checkout@v7
65
+
66
+ - uses: actions/cache@v6
67
+ with:
68
+ path: .cpm-cache
69
+ key: cpm-bin-${{ matrix.os }}-${{ hashFiles('CMakeLists.txt') }}
70
+
71
+ - uses: ilammy/msvc-dev-cmd@v1.13.0
72
+ if: matrix.os == 'windows-latest'
73
+
74
+ - name: Install Ninja
75
+ if: matrix.os != 'windows-latest'
76
+ run: |
77
+ if [ "${{ runner.os }}" = "Linux" ]; then
78
+ sudo apt-get update -qy && sudo apt-get install -qy ninja-build
79
+ else
80
+ brew install ninja
81
+ fi
82
+
83
+ - name: Configure
84
+ env:
85
+ CPM_SOURCE_CACHE: ${{ github.workspace }}/.cpm-cache
86
+ run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DPXSTEAMDL_BUILD_CLI=ON -DPXSTEAMDL_BUILD_PYTHON=OFF
87
+
88
+ - name: Build CLI
89
+ run: cmake --build build --target pxsteamdl-cli --config Release
90
+
91
+ - name: Strip binary (Linux/macOS)
92
+ if: matrix.pack_type == 'tar'
93
+ run: |
94
+ if [ "${{ runner.os }}" = "Linux" ]; then
95
+ strip -s build/pxsteamdl
96
+ else
97
+ strip build/pxsteamdl
98
+ fi
99
+
100
+ - name: Package (tar.gz)
101
+ if: matrix.pack_type == 'tar'
102
+ run: |
103
+ mkdir -p staging/${{ matrix.artifact_name }} dist
104
+ cp build/pxsteamdl COPYING COPYING.LESSER README.md staging/${{ matrix.artifact_name }}/
105
+ tar -czvf dist/${{ matrix.archive_name }} -C staging ${{ matrix.artifact_name }}
106
+
107
+ - name: Package (zip)
108
+ if: matrix.pack_type == 'zip'
109
+ shell: pwsh
110
+ run: |
111
+ New-Item -ItemType Directory -Path staging\${{ matrix.artifact_name }} -Force
112
+ Copy-Item build\pxsteamdl.exe, COPYING, COPYING.LESSER, README.md staging\${{ matrix.artifact_name }}\
113
+ New-Item -ItemType Directory -Path dist -Force
114
+ Compress-Archive -Path staging\${{ matrix.artifact_name }} -DestinationPath dist\${{ matrix.archive_name }}
115
+
116
+ - uses: actions/upload-artifact@v7
117
+ with:
118
+ name: binary-${{ matrix.artifact_name }}
119
+ path: dist/*
120
+ if-no-files-found: error
121
+
122
+ wheels:
123
+ name: wheels (${{ matrix.os }})
124
+ strategy:
125
+ fail-fast: false
126
+ matrix:
127
+ os: [ubuntu-latest, windows-latest, macos-latest]
128
+ runs-on: ${{ matrix.os }}
129
+ env:
130
+ CIBW_ENVIRONMENT_LINUX: CPM_SOURCE_CACHE=/tmp/cpm-cache
131
+ CPM_SOURCE_CACHE: ${{ github.workspace }}/.cpm-cache
132
+ steps:
133
+ - uses: actions/checkout@v7
134
+
135
+ - uses: actions/cache@v6
136
+ if: runner.os != 'Linux'
137
+ with:
138
+ path: .cpm-cache
139
+ key: cpm-wheels-${{ runner.os }}-${{ hashFiles('CMakeLists.txt') }}
140
+
141
+ - uses: pypa/cibuildwheel@v4.2.1
142
+
143
+ - name: Wheel sizes
144
+ shell: bash
145
+ run: ls -l wheelhouse
146
+
147
+ - uses: actions/upload-artifact@v7
148
+ with:
149
+ name: wheels-${{ runner.os }}
150
+ path: wheelhouse/*.whl
151
+ if-no-files-found: error
152
+
153
+ sdist:
154
+ name: sdist
155
+ runs-on: ubuntu-latest
156
+ steps:
157
+ - uses: actions/checkout@v7
158
+
159
+ - name: Build sdist
160
+ run: pipx run build --sdist
161
+
162
+ - uses: actions/upload-artifact@v7
163
+ with:
164
+ name: sdist
165
+ path: dist/*.tar.gz
166
+ if-no-files-found: error
167
+
168
+ pypi:
169
+ name: Publish to PyPI
170
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
171
+ needs: [check, wheels, sdist]
172
+ runs-on: ubuntu-latest
173
+ environment: pypi
174
+ permissions:
175
+ id-token: write
176
+ steps:
177
+ - uses: actions/download-artifact@v8
178
+ with:
179
+ path: dist
180
+ pattern: "{wheels-*,sdist}"
181
+ merge-multiple: true
182
+
183
+ - name: List distributions
184
+ run: ls -l dist
185
+
186
+ # Trusted publishing: no API token; the pending publisher on pypi.org names this workflow and the pypi environment.
187
+ - uses: pypa/gh-action-pypi-publish@release/v1
188
+
189
+ release:
190
+ name: Publish Release
191
+ if: (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || (github.event_name == 'workflow_dispatch' && inputs.publish && inputs.tag_name != '')
192
+ needs: [check, binaries, wheels, sdist]
193
+ runs-on: ubuntu-latest
194
+ permissions:
195
+ contents: write
196
+ steps:
197
+ - uses: actions/checkout@v7
198
+
199
+ # The release notes are the changelog section of this version.
200
+ - name: Release notes
201
+ shell: bash
202
+ run: |
203
+ version=$(sed -n 's/^project(PxSteamDL VERSION \([0-9.]*\).*/\1/p' CMakeLists.txt)
204
+ awk -v v="$version" '$0 ~ "^## \\[" v "\\]" {found=1; next} found && /^## \[/ {exit} found {print}' CHANGELOG.md > release-notes.md
205
+ test -s release-notes.md
206
+ cat release-notes.md
207
+
208
+ - uses: actions/download-artifact@v8
209
+ with:
210
+ path: release_assets
211
+ merge-multiple: true
212
+
213
+ - name: Generate Checksums
214
+ shell: bash
215
+ run: |
216
+ cd release_assets
217
+ sha256sum * > SHA256SUMS.txt
218
+ echo "=== Release Assets & Checksums ==="
219
+ cat SHA256SUMS.txt
220
+
221
+ - name: Publish Release
222
+ uses: softprops/action-gh-release@v3.0.3
223
+ with:
224
+ tag_name: ${{ github.event.inputs.tag_name || github.ref_name }}
225
+ files: release_assets/*
226
+ body_path: release-notes.md
@@ -0,0 +1,50 @@
1
+ name: Update CA bundle
2
+
3
+ # The Mozilla CA bundle that curl publishes is compiled into the library, and CMakeLists.txt pins its date and hash.
4
+ # Once a week this looks for a newer one and opens a pull request that pins it. Binaries built from an old pin would
5
+ # eventually stop trusting Steam's certificates.
6
+ #
7
+ # Needs "Allow GitHub Actions to create and approve pull requests" in the repository's Actions settings.
8
+ on:
9
+ schedule:
10
+ - cron: "23 4 * * 1"
11
+ workflow_dispatch:
12
+
13
+ permissions:
14
+ contents: write
15
+ pull-requests: write
16
+ actions: write
17
+
18
+ jobs:
19
+ update:
20
+ runs-on: ubuntu-latest
21
+ env:
22
+ GH_TOKEN: ${{ github.token }}
23
+ steps:
24
+ - uses: actions/checkout@v7
25
+
26
+ - name: Look for a newer bundle
27
+ id: bundle
28
+ run: .github/scripts/update-ca-bundle.sh
29
+
30
+ - name: Open a pull request
31
+ if: steps.bundle.outputs.date != ''
32
+ env:
33
+ DATE: ${{ steps.bundle.outputs.date }}
34
+ run: |
35
+ branch="ci/ca-bundle-$DATE"
36
+ git config user.name "github-actions[bot]"
37
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
38
+ git switch -c "$branch"
39
+ git commit -am "Update the Mozilla CA bundle to $DATE"
40
+ git push origin "$branch"
41
+ gh pr create --base "${{ github.event.repository.default_branch }}" --head "$branch" \
42
+ --title "Update the Mozilla CA bundle to $DATE" \
43
+ --body "Pins the newest CA bundle published by curl (https://curl.se/docs/caextract.html), as extracted on $DATE. Opened by the *Update CA bundle* workflow."
44
+ # Pull requests opened with the workflow token do not start CI by themselves; a manual run does.
45
+ gh workflow run ci.yml --ref "$branch"
46
+ # An older, unmerged update is obsolete now.
47
+ for number in $(gh pr list --state open --search 'in:title "Update the Mozilla CA bundle"' \
48
+ --json number,headRefName --jq ".[] | select(.headRefName != \"$branch\") | .number"); do
49
+ gh pr close "$number" --comment "Superseded by $branch." --delete-branch
50
+ done
@@ -0,0 +1,23 @@
1
+ build/
2
+ .cache/
3
+ .vscode/
4
+ .idea/
5
+ compile_commands.json
6
+ *.o
7
+ *.obj
8
+ *.a
9
+ *.lib
10
+ *.dll
11
+ *.dylib
12
+ *.so
13
+ *.exe
14
+ CMakeUserPresets.json
15
+ .cpm-cache/
16
+
17
+ # Python
18
+ __pycache__/
19
+ *.egg-info/
20
+ dist/
21
+ .venv/
22
+ _skbuild/
23
+ wheelhouse/