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.
- pxsteamdl-0.4.0/.clang-format +8 -0
- pxsteamdl-0.4.0/.clang-tidy +29 -0
- pxsteamdl-0.4.0/.github/scripts/update-ca-bundle.sh +55 -0
- pxsteamdl-0.4.0/.github/smoke_test.py +66 -0
- pxsteamdl-0.4.0/.github/workflows/ci.yml +140 -0
- pxsteamdl-0.4.0/.github/workflows/release.yml +226 -0
- pxsteamdl-0.4.0/.github/workflows/update-ca-bundle.yml +50 -0
- pxsteamdl-0.4.0/.gitignore +23 -0
- pxsteamdl-0.4.0/CHANGELOG.md +118 -0
- pxsteamdl-0.4.0/CMakeLists.txt +380 -0
- pxsteamdl-0.4.0/CONTRIBUTING.md +101 -0
- pxsteamdl-0.4.0/COPYING +674 -0
- pxsteamdl-0.4.0/COPYING.LESSER +165 -0
- pxsteamdl-0.4.0/PKG-INFO +259 -0
- pxsteamdl-0.4.0/README.md +246 -0
- pxsteamdl-0.4.0/cli/main.cpp +117 -0
- pxsteamdl-0.4.0/cli/utf8.manifest +10 -0
- pxsteamdl-0.4.0/cmake/CPM.cmake +28 -0
- pxsteamdl-0.4.0/cmake/MergeArchives.cmake +37 -0
- pxsteamdl-0.4.0/cmake/PxSteamDLConfig.cmake.in +18 -0
- pxsteamdl-0.4.0/cmake/PxSteamDLInstall.cmake +115 -0
- pxsteamdl-0.4.0/cmake/mbedtls/pxsteamdl_mbedtls_config.h +10 -0
- pxsteamdl-0.4.0/cmake/mbedtls/threading_alt.h +7 -0
- pxsteamdl-0.4.0/cmake/version.hpp.in +8 -0
- pxsteamdl-0.4.0/examples/download.cpp +104 -0
- pxsteamdl-0.4.0/examples/download.py +80 -0
- pxsteamdl-0.4.0/examples/download_async.py +56 -0
- pxsteamdl-0.4.0/include/pxsteamdl/error.hpp +48 -0
- pxsteamdl-0.4.0/include/pxsteamdl/pxsteamdl.hpp +140 -0
- pxsteamdl-0.4.0/pyproject.toml +53 -0
- pxsteamdl-0.4.0/python/pxsteamdl/__init__.py +30 -0
- pxsteamdl-0.4.0/python/pxsteamdl/_async.py +87 -0
- pxsteamdl-0.4.0/python/pxsteamdl/py.typed +0 -0
- pxsteamdl-0.4.0/python/pxsteamdl.cpp +224 -0
- pxsteamdl-0.4.0/src/cdn.cpp +62 -0
- pxsteamdl-0.4.0/src/cdn.hpp +61 -0
- pxsteamdl-0.4.0/src/chunk.cpp +104 -0
- pxsteamdl-0.4.0/src/chunk.hpp +14 -0
- pxsteamdl-0.4.0/src/client.cpp +120 -0
- pxsteamdl-0.4.0/src/cm_packet.cpp +153 -0
- pxsteamdl-0.4.0/src/cm_packet.hpp +52 -0
- pxsteamdl-0.4.0/src/common.hpp +74 -0
- pxsteamdl-0.4.0/src/crypto.cpp +61 -0
- pxsteamdl-0.4.0/src/crypto.hpp +25 -0
- pxsteamdl-0.4.0/src/directory_lock.cpp +183 -0
- pxsteamdl-0.4.0/src/directory_lock.hpp +26 -0
- pxsteamdl-0.4.0/src/directory_update.cpp +109 -0
- pxsteamdl-0.4.0/src/directory_update.hpp +40 -0
- pxsteamdl-0.4.0/src/downloader.cpp +506 -0
- pxsteamdl-0.4.0/src/downloader.hpp +65 -0
- pxsteamdl-0.4.0/src/eresult.cpp +162 -0
- pxsteamdl-0.4.0/src/eresult.hpp +86 -0
- pxsteamdl-0.4.0/src/error.cpp +28 -0
- pxsteamdl-0.4.0/src/http.cpp +137 -0
- pxsteamdl-0.4.0/src/http.hpp +50 -0
- pxsteamdl-0.4.0/src/manifest.cpp +243 -0
- pxsteamdl-0.4.0/src/manifest.hpp +56 -0
- pxsteamdl-0.4.0/src/paths.cpp +68 -0
- pxsteamdl-0.4.0/src/paths.hpp +30 -0
- pxsteamdl-0.4.0/src/pending_file.cpp +244 -0
- pxsteamdl-0.4.0/src/pending_file.hpp +59 -0
- pxsteamdl-0.4.0/src/proto.cpp +111 -0
- pxsteamdl-0.4.0/src/proto.hpp +58 -0
- pxsteamdl-0.4.0/src/retry.cpp +25 -0
- pxsteamdl-0.4.0/src/retry.hpp +36 -0
- pxsteamdl-0.4.0/src/session.cpp +464 -0
- pxsteamdl-0.4.0/src/session.hpp +42 -0
- pxsteamdl-0.4.0/src/sleep.cpp +21 -0
- pxsteamdl-0.4.0/src/sleep.hpp +13 -0
- pxsteamdl-0.4.0/src/version.cpp +8 -0
- pxsteamdl-0.4.0/src/workshop.cpp +154 -0
- pxsteamdl-0.4.0/src/workshop.hpp +36 -0
- pxsteamdl-0.4.0/src/zip.cpp +118 -0
- pxsteamdl-0.4.0/src/zip.hpp +12 -0
- pxsteamdl-0.4.0/tests/chunk_test.cpp +86 -0
- pxsteamdl-0.4.0/tests/client_test.cpp +1187 -0
- pxsteamdl-0.4.0/tests/cm_packet_test.cpp +93 -0
- pxsteamdl-0.4.0/tests/crypto_test.cpp +43 -0
- pxsteamdl-0.4.0/tests/depot_builder.hpp +68 -0
- pxsteamdl-0.4.0/tests/directory_lock_test.cpp +259 -0
- pxsteamdl-0.4.0/tests/directory_update_test.cpp +84 -0
- pxsteamdl-0.4.0/tests/eresult_test.cpp +52 -0
- pxsteamdl-0.4.0/tests/error_test.cpp +80 -0
- pxsteamdl-0.4.0/tests/fake_steam.cpp +310 -0
- pxsteamdl-0.4.0/tests/fake_steam.hpp +127 -0
- pxsteamdl-0.4.0/tests/fakes/fake_http.cpp +48 -0
- pxsteamdl-0.4.0/tests/fakes/fake_http.hpp +24 -0
- pxsteamdl-0.4.0/tests/fakes/fake_session.cpp +67 -0
- pxsteamdl-0.4.0/tests/fakes/fake_session.hpp +26 -0
- pxsteamdl-0.4.0/tests/fakes/fake_sleep.cpp +36 -0
- pxsteamdl-0.4.0/tests/fakes/fake_sleep.hpp +16 -0
- pxsteamdl-0.4.0/tests/install_consumer/CMakeLists.txt +11 -0
- pxsteamdl-0.4.0/tests/install_consumer/main.cpp +25 -0
- pxsteamdl-0.4.0/tests/lock_holder.cpp +24 -0
- pxsteamdl-0.4.0/tests/manifest_test.cpp +126 -0
- pxsteamdl-0.4.0/tests/paths_test.cpp +50 -0
- pxsteamdl-0.4.0/tests/proto_test.cpp +69 -0
- pxsteamdl-0.4.0/tests/retry_test.cpp +153 -0
- pxsteamdl-0.4.0/tests/sleep_test.cpp +49 -0
- pxsteamdl-0.4.0/tests/test_util.hpp +135 -0
- pxsteamdl-0.4.0/tests/version_test.cpp +19 -0
- pxsteamdl-0.4.0/tests/zip_test.cpp +48 -0
|
@@ -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/
|