Skip to content

Commit dcb0098

Browse files
authored
Merge pull request #358 from appwrite/fix/php-sdk-runtime-builds
Fix PHP SDK fatal and validate template builds
2 parents f33acc7 + f964069 commit dcb0098

6 files changed

Lines changed: 324 additions & 15 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
if ($argc !== 2) {
6+
fwrite(STDERR, "Usage: php load-composer-classes.php <template-directory>\n");
7+
exit(2);
8+
}
9+
10+
$template = realpath($argv[1]);
11+
if ($template === false || !is_file($template . '/vendor/autoload.php')) {
12+
fwrite(STDERR, "Installed template not found: {$argv[1]}\n");
13+
exit(2);
14+
}
15+
16+
require $template . '/vendor/autoload.php';
17+
18+
function importedClasses(string $declaration): array
19+
{
20+
$prefix = '';
21+
if (str_contains($declaration, '{')) {
22+
[$prefix, $declaration] = explode('{', $declaration, 2);
23+
$declaration = strstr($declaration, '}', true) ?: '';
24+
}
25+
26+
$classes = [];
27+
foreach (explode(',', $declaration) as $import) {
28+
$import = trim($import);
29+
if ($import === '' || preg_match('/^(?:function|const)\s/i', $import)) {
30+
continue;
31+
}
32+
33+
$class = preg_split('/\s+as\s+/i', $import)[0];
34+
$classes[] = trim($prefix) . $class;
35+
}
36+
37+
return $classes;
38+
}
39+
40+
$symbols = [];
41+
foreach (glob($template . '/src/*.php') ?: [] as $source) {
42+
$contents = file_get_contents($source);
43+
if ($contents === false) {
44+
fwrite(STDERR, "Unable to read PHP source: {$source}\n");
45+
exit(2);
46+
}
47+
48+
preg_match_all('/^\s*use\s+(?!function\s|const\s)([^;]+);/m', $contents, $matches);
49+
foreach ($matches[1] as $declaration) {
50+
array_push($symbols, ...importedClasses($declaration));
51+
}
52+
}
53+
54+
// A PHP use declaration does not autoload its class. Load every imported symbol explicitly so
55+
// dependency parse errors (including case-insensitive duplicate methods) fail during CI.
56+
foreach (array_unique($symbols) as $symbol) {
57+
$loaded = class_exists($symbol)
58+
|| interface_exists($symbol)
59+
|| trait_exists($symbol)
60+
|| (function_exists('enum_exists') && enum_exists($symbol));
61+
62+
if (!$loaded) {
63+
fwrite(STDERR, "Unable to load imported symbol: {$symbol}\n");
64+
exit(1);
65+
}
66+
}
67+
68+
$entrypoint = $template . '/src/index.php';
69+
if (is_file($entrypoint) && !is_callable(require $entrypoint)) {
70+
fwrite(STDERR, "PHP entrypoint did not return a callable: {$entrypoint}\n");
71+
exit(1);
72+
}

.github/workflows/build.yml

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
name: Template builds
2+
3+
concurrency:
4+
group: ${{ github.workflow }}-${{ github.ref }}
5+
cancel-in-progress: true
6+
7+
on:
8+
pull_request:
9+
schedule:
10+
# Re-resolve compatible dependency updates weekly and make sure they still build.
11+
- cron: "0 7 * * 1"
12+
workflow_dispatch:
13+
14+
permissions:
15+
contents: read
16+
17+
jobs:
18+
node:
19+
name: Node.js and TypeScript
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
24+
with:
25+
node-version: "22"
26+
- name: Build templates
27+
run: |
28+
set -euo pipefail
29+
for dir in node/*/ node-typescript/*/; do
30+
[ -f "${dir}package.json" ] || continue
31+
echo "::group::${dir%/}"
32+
npm ci --prefix "$dir" --ignore-scripts --no-audit --no-fund
33+
if [ -f "${dir}tsconfig.json" ]; then
34+
npm run --prefix "$dir" build
35+
else
36+
find "${dir}src" -type f -name '*.js' -exec node --check {} \;
37+
fi
38+
echo "::endgroup::"
39+
done
40+
41+
bun:
42+
name: Bun
43+
runs-on: ubuntu-latest
44+
steps:
45+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
46+
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
47+
with:
48+
bun-version: latest
49+
- name: Build templates
50+
run: |
51+
set -euo pipefail
52+
for dir in bun/*/; do
53+
[ -f "${dir}package.json" ] || continue
54+
echo "::group::${dir%/}"
55+
(cd "$dir" && bun install --frozen-lockfile --ignore-scripts && bun -e "await import('./src/main.ts')")
56+
echo "::endgroup::"
57+
done
58+
59+
python:
60+
name: Python ${{ matrix.name }}
61+
runs-on: ubuntu-latest
62+
strategy:
63+
fail-fast: false
64+
matrix:
65+
include:
66+
- name: "3.9"
67+
version: "3.9"
68+
templates: "python"
69+
- name: "3.12 MCP"
70+
version: "3.12"
71+
templates: "mcp"
72+
- name: "3.11 ML"
73+
version: "3.11"
74+
templates: "python-ml"
75+
steps:
76+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
77+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
78+
with:
79+
python-version: ${{ matrix.version }}
80+
- name: Build templates
81+
env:
82+
TEMPLATE_SET: ${{ matrix.templates }}
83+
run: |
84+
set -euo pipefail
85+
if [ "$TEMPLATE_SET" = mcp ]; then
86+
dirs=(python/mcp-server/)
87+
elif [ "$TEMPLATE_SET" = python-ml ]; then
88+
dirs=(python-ml/*/)
89+
else
90+
dirs=(python/*/)
91+
fi
92+
for dir in "${dirs[@]}"; do
93+
[ -f "${dir}requirements.txt" ] || continue
94+
if [ "$TEMPLATE_SET" = python ] && [ "$dir" = python/mcp-server/ ]; then continue; fi
95+
echo "::group::${dir%/}"
96+
rm -rf /tmp/template-venv
97+
python -m venv /tmp/template-venv
98+
/tmp/template-venv/bin/pip install --disable-pip-version-check -r "${dir}requirements.txt"
99+
/tmp/template-venv/bin/pip check
100+
/tmp/template-venv/bin/python -m compileall -q "${dir}src"
101+
echo "::endgroup::"
102+
done
103+
104+
php:
105+
name: PHP
106+
runs-on: ubuntu-latest
107+
steps:
108+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
109+
- uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
110+
with:
111+
php-version: "8.3"
112+
tools: composer
113+
- name: Build and load templates
114+
run: |
115+
set -euo pipefail
116+
for dir in php/*/; do
117+
[ -f "${dir}composer.json" ] || continue
118+
echo "::group::${dir%/}"
119+
composer install --working-dir="$dir" --no-interaction --no-progress --optimize-autoloader
120+
find "${dir}src" -type f -name '*.php' -exec php -l {} \;
121+
php .github/scripts/load-composer-classes.php "$dir"
122+
echo "::endgroup::"
123+
done
124+
125+
ruby:
126+
name: Ruby
127+
runs-on: ubuntu-latest
128+
steps:
129+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
130+
- uses: ruby/setup-ruby@a30dfa457ad68707b8b910ac3a244714b61c0626 # v1.320.0
131+
with:
132+
ruby-version: "3.1"
133+
bundler: latest
134+
- name: Build templates
135+
run: |
136+
set -euo pipefail
137+
for dir in ruby/*/; do
138+
[ -f "${dir}Gemfile" ] || continue
139+
echo "::group::${dir%/}"
140+
(cd "$dir" && bundle install && find lib -type f -name '*.rb' -exec ruby -c {} \;)
141+
echo "::endgroup::"
142+
done
143+
144+
dart:
145+
name: Dart
146+
runs-on: ubuntu-latest
147+
steps:
148+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
149+
- uses: dart-lang/setup-dart@65eb853c7ba17dde3be364c3d2858773e7144260 # v1.7.2
150+
with:
151+
sdk: stable
152+
- name: Build templates
153+
run: |
154+
set -euo pipefail
155+
for dir in dart/*/; do
156+
[ -f "${dir}pubspec.yaml" ] || continue
157+
echo "::group::${dir%/}"
158+
(cd "$dir" && dart pub get && dart analyze)
159+
echo "::endgroup::"
160+
done
161+
162+
deno:
163+
name: Deno
164+
runs-on: ubuntu-latest
165+
steps:
166+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
167+
- uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2
168+
with:
169+
deno-version: v2.x
170+
- name: Build templates
171+
run: |
172+
set -euo pipefail
173+
for dir in deno/*/; do
174+
echo "::group::${dir%/}"
175+
(cd "$dir" && deno check src/main.ts)
176+
echo "::endgroup::"
177+
done
178+
179+
go:
180+
name: Go
181+
runs-on: ubuntu-latest
182+
steps:
183+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
184+
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
185+
with:
186+
go-version: "1.26.6"
187+
cache: false
188+
- name: Build templates
189+
run: for dir in go/*/; do [ ! -f "${dir}go.mod" ] || (cd "$dir" && go build ./...); done
190+
191+
rust:
192+
name: Rust
193+
runs-on: ubuntu-latest
194+
steps:
195+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
196+
- uses: dtolnay/rust-toolchain@bd41891a8e7f4b8649f6d684415e1a6155fe4e22 # 1.83.0
197+
- name: Build templates
198+
run: for dir in rust/*/; do [ ! -f "${dir}Cargo.toml" ] || (cd "$dir" && cargo build --locked); done
199+
200+
runtime-builds:
201+
name: ${{ matrix.template }}
202+
runs-on: ubuntu-latest
203+
strategy:
204+
fail-fast: false
205+
matrix:
206+
include:
207+
- template: cpp/starter
208+
image: openruntimes/cpp:v4-17
209+
entrypoint: src/main.cc
210+
- template: dotnet/starter
211+
image: openruntimes/dotnet:v4-6.0
212+
entrypoint: src/Index.cs
213+
- template: java/starter
214+
image: openruntimes/java:v4-17.0
215+
entrypoint: src/Main.java
216+
- template: kotlin/starter
217+
image: openruntimes/kotlin:v4-1.8
218+
entrypoint: src/Main.kt
219+
- template: kotlin/sync-with-meilisearch
220+
image: openruntimes/kotlin:v4-1.8
221+
entrypoint: src/Main.kt
222+
- template: swift/starter
223+
image: openruntimes/swift:v5-6.2
224+
entrypoint: Sources/index.swift
225+
steps:
226+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
227+
- name: Build in Open Runtimes
228+
env:
229+
IMAGE: ${{ matrix.image }}
230+
ENTRYPOINT: ${{ matrix.entrypoint }}
231+
TEMPLATE: ${{ matrix.template }}
232+
run: |
233+
docker run --rm \
234+
-e OPEN_RUNTIMES_ENTRYPOINT="$ENTRYPOINT" \
235+
-v "$PWD/$TEMPLATE:/mnt/code" \
236+
"$IMAGE" sh helpers/build.sh
237+
rm -f "$TEMPLATE/code.tar.gz"

php/email-contact-form/src/index.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?php
22

33
require_once(__DIR__ . '/../vendor/autoload.php');
4-
require_once(__DIR__ . 'utils.php');
5-
require_once(__DIR__ . 'cors.php');
4+
require_once(__DIR__ . '/utils.php');
5+
require_once(__DIR__ . '/cors.php');
66

77
$ERROR_CODE = [
88
'INVALID_REQUEST' => 'invalid-request',

php/starter/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
"type": "library",
44
"require": {
55
"php": ">=8.0.0",
6-
"appwrite/appwrite": "^17.4.1"
6+
"appwrite/appwrite": "^18.0.1"
77
}
88
}

php/starter/composer.lock

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)