#!/usr/bin/env bash # Compila jogos do AlexGames (Alex Barry, AGPL-3.0-or-later, commit 689b40c de github.com/alexbarry/AlexGames) para o # fredygames.com: o motor em C (Lua 5.4.6 + a API de jogos) em WebAssembly com o Emscripten 6.0.10, pelo CMake do # proprio projeto, e uma pagina por jogo. Os jogos sao scripts Lua que a build embute (--preload-file); a lista de # jogos que o site publica e a do GAMES abaixo. # # source /emsdk_env.sh # emsdk install 6.0.10 && emsdk activate 6.0.10 # src/FredGames.Bundler/Builds/build-alexgames.sh # # alexgames/alexgames-fredygames.patch (publicado junto do fonte, e a licenca exige): # - build/wasm e CMake: sem libzip/zlib (o envio de jogos .zip pelo usuario sai), sem a flag WASM_BIGINT (o # Emscripten atual recusa), data de build em UTC (SOURCE_DATE_EPOCH deixa o texto fixo), sem baixar o SQLite # (nao usado) e sem o luac.c (tem main) no Lua da tarball oficial; o Rust fica desligado (so o Gem Match usa, # e ele nao entra); # - src/html/index.html vira a pagina de UM jogo (o jogo e o titulo vem de __AG_GAME__/__AG_TITLE__, trocados # aqui): sem escolha de jogo, sem sobre/links/redes (GitHub, Discord, Lemmy, Matrix, F-Droid), sem envio de zip, # sem multiplayer, sem manifest e sem imagens (os jogos escolhidos nao usam nenhuma); politica de seguranca que # bloqueia conexoes fora da origem; # - src/html/js/alexgames_ws.js: o WebSocket nunca e criado e nada e enviado (multiplayer removido); # - src/html/js/alexgames.js: o titulo da pagina vem do jogo escolhido; game_api.c: a mensagem "Starting game" nao # mostra mais o id interno do jogo; o botao "Load autosaved game" (navegador de saves, que mostra o id) fica oculto; # - word_mastermind_main.lua: o texto do quebra-cabeca personalizado nao manda mais "compartilhar o link". # # Dicionario (so o Word Code usa): out/words-en.txt, gerado pelo build_word_list_w_freq.py do projeto a partir da lista # de palavras da Wordnik (wordnik/wordlist, MIT, commit 46e6215) com as frequencias do pacote wordfreq 3.1.1 (Apache-2.0; # os dados dele, CC BY-SA 4.0). O wordfreq fica num venv proprio em artifacts/tools/venv-wordfreq. # # Saida: artifacts/build/alexgames-689b40c/.tar.gz (pasta / com play.html, o motor e, no Word Code, o # dicionario), mais licenses.txt (Lua, wordlist, wordfreq). Cada tar.gz tem que sair igual em dois builds seguidos. set -euo pipefail COMMIT=689b40c2a2ec4e0dd119f8c2f0000520d1a59e22 SHA256=da7b329b760491d32646f97aa0bd3cde1ebc2d19ce8d5e4205b136dca79584fb LUA=5.4.6 LUA_SHA256=7d5ea1b9cb6aa0b59ca3dde1c6adcb57ef83a1ba8e5432c0ecd06bf439b3ad88 WORDLIST=46e6215d0f90356afe9c8ba4be347e7e98cb425c WORDLIST_SHA256=09486c43b81fb23f7c509de9992045ff37f5a89cebd470f3436a0661aa223258 WORDFREQ=3.1.1 EMSCRIPTEN=6.0.10 # id do jogo | slug do site | titulo | 1 = precisa do dicionario GAMES=( "word_mastermind|word-code|Word Code|1" "crossword_letters|crossword-letters|Crossword Letters|0" "fluid_mix|fluid-mix|Fluid Mix|0" ) here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" root="$(git -C "$here" rev-parse --show-toplevel 2>/dev/null || pwd)" short="${COMMIT:0:7}" out="$root/artifacts/build/alexgames-$short" cache="$root/artifacts/build/cache" venv="$root/artifacts/tools/venv-wordfreq" if ! emcc --version 2>/dev/null | head -1 | grep -q " $EMSCRIPTEN "; then echo "Precisa do Emscripten $EMSCRIPTEN no PATH (source /emsdk_env.sh)." >&2 exit 1 fi if [ ! -x "$venv/bin/python3" ]; then python3 -m venv "$venv" fi if ! "$venv/bin/python3" -c "import importlib.metadata as m,sys; sys.exit(m.version('wordfreq') != '$WORDFREQ')" 2>/dev/null; then "$venv/bin/pip" install -q "wordfreq==$WORDFREQ" fi mkdir -p "$cache" fetch() { # url arquivo sha256 if [ ! -f "$cache/$2" ]; then curl -fsSL --retry 4 "$1" -o "$cache/$2.partial" && mv "$cache/$2.partial" "$cache/$2" fi echo "$3 $cache/$2" | shasum -a 256 -c - >/dev/null } fetch "https://github.com/alexbarry/AlexGames/archive/$COMMIT.tar.gz" "alexgames-$short.tar.gz" "$SHA256" fetch "https://www.lua.org/ftp/lua-$LUA.tar.gz" "lua-$LUA.tar.gz" "$LUA_SHA256" fetch "https://github.com/wordnik/wordlist/archive/$WORDLIST.tar.gz" "wordnik-wordlist-${WORDLIST:0:7}.tar.gz" "$WORDLIST_SHA256" rm -rf "$out" mkdir -p "$out/tree/third_party/wordlist" tree="$out/tree" tar -xzf "$cache/alexgames-$short.tar.gz" -C "$tree" --strip-components 1 tar -xzf "$cache/lua-$LUA.tar.gz" -C "$tree/third_party" tar -xzf "$cache/wordnik-wordlist-${WORDLIST:0:7}.tar.gz" -C "$tree/third_party/wordlist" --strip-components 1 patch -d "$tree" -p2 --no-backup-if-mismatch < "$here/alexgames/alexgames-fredygames.patch" > /dev/null # Caminhos do __FILE__ do C entram no .wasm: sem o prefixo, o resultado dependeria de onde o repositorio esta. export SOURCE_DATE_EPOCH=1700000000 PYTHONDONTWRITEBYTECODE=1 export CFLAGS="-ffile-prefix-map=$tree=/alexgames" CXXFLAGS="-ffile-prefix-map=$tree=/alexgames" export PATH="$venv/bin:$PATH" mkdir -p "$tree/build/wasm/out" ( cd "$tree/build/wasm/out" emcmake cmake -DENABLE_RUST_GAMES=OFF -DLUA_SRC_DIR="$tree/third_party/lua-$LUA/src" -DBUILD_TYPE_LABEL=fredygames ../ > "$out/cmake.log" 2>&1 cmake --build . -j"$(sysctl -n hw.ncpu 2>/dev/null || echo 4)" > "$out/build.log" 2>&1 || { tail -30 "$out/build.log"; exit 1; } ) engine="$tree/build/wasm/out" for f in alexgames_wasm.js alexgames_wasm.wasm alexgames_wasm.data; do [ -f "$engine/$f" ]; done [ -f "$tree/out/words-en.txt" ] if strings -a "$engine/alexgames_wasm.wasm" | grep -q "$root"; then echo "(aviso) o .wasm ainda traz o caminho do repositorio" >&2 fi # Licencas de terceiros: Lua (MIT, no proprio tarball), lista de palavras (MIT) e wordfreq (Apache-2.0 + dados CC BY-SA 4.0). { echo "== Lua $LUA (PUC-Rio): copyright e licenca MIT (doc/readme.html do tarball oficial)" python3 -I -c " import html, re, sys t = open(sys.argv[1], encoding='latin-1').read() t = t[t.index('Copyright ©'):t.index('THE SOFTWARE.')+len('THE SOFTWARE.')] t = re.sub(r'<[^>]*>', '', t) print(html.unescape(t)) " "$tree/third_party/lua-$LUA/doc/readme.html" echo echo "== Wordnik wordlist (commit $WORDLIST): LICENSE" cat "$tree/third_party/wordlist/LICENSE" echo echo "== wordfreq $WORDFREQ: LICENSE.txt" cat "$venv"/lib/python3*/site-packages/wordfreq-$WORDFREQ.dist-info/LICENSE.txt echo echo "== wordfreq $WORDFREQ: secao License do README (METADATA)" sed -n '/^## License/,/^## Citing/p' "$venv"/lib/python3*/site-packages/wordfreq-$WORDFREQ.dist-info/METADATA | sed '$d' } > "$out/licenses.txt" # Uma pasta por jogo, num tar.gz deterministico (ordem fixa, datas e donos zerados, gzip sem nome nem data). for game in "${GAMES[@]}"; do IFS='|' read -r id slug title dict <<< "$game" python3 -I - "$tree" "$engine" "$out" "$id" "$slug" "$title" "$dict" <<'PY' import gzip, io, os, re, sys, tarfile tree, engine, out, gid, slug, title, dict_ = sys.argv[1:8] html = open(os.path.join(tree, 'src/html/index.html'), encoding='utf-8').read() html = html.replace('__AG_GAME__', gid).replace('__AG_TITLE__', title) files = {'play.html': html.encode('utf-8')} for name in ('alexgames_wasm.js', 'alexgames_wasm.wasm', 'alexgames_wasm.data'): files[name] = open(os.path.join(engine, name), 'rb').read() # O Emscripten deixa no .js comentarios com o nome de arquivos temporarios (/var/folders/.../tmpXXXX.js). files['alexgames_wasm.js'] = re.sub(rb'(?m)^(\s*)// (end )?include: /\S+\.js$', rb'\1// \2include: (temporary file)', files['alexgames_wasm.js']) for sub in ('js', 'css'): d = os.path.join(tree, 'src/html', sub) for name in sorted(os.listdir(d)): files[f'{sub}/{name}'] = open(os.path.join(d, name), 'rb').read() if dict_ == '1': files['words-en.txt'] = open(os.path.join(tree, 'out/words-en.txt'), 'rb').read() raw = io.BytesIO() with tarfile.open(fileobj=raw, mode='w', format=tarfile.USTAR_FORMAT) as tar: for name in sorted(files): info = tarfile.TarInfo(f'{slug}/{name}') info.size = len(files[name]); info.mtime = 1700000000; info.mode = 0o644 info.uid = info.gid = 0; info.uname = info.gname = 'root' tar.addfile(info, io.BytesIO(files[name])) with open(os.path.join(out, f'{slug}.tar.gz'), 'wb') as f: with gzip.GzipFile(filename='', mode='wb', fileobj=f, mtime=0, compresslevel=9) as gz: gz.write(raw.getvalue()) PY done echo shasum -a 256 "$out"/*.tar.gz "$out/licenses.txt" "$tree/out/words-en.txt"