Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ src/backup.bat
specbas_dev_notes.txt
specbas_log.txt
*.log

# SDL2 backend build: fetched dependencies and build products.
/build-sdl2/vendor/
/build-sdl2/build/
135 changes: 135 additions & 0 deletions build-sdl2/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Builds SpecBAS with the SDL2 backend, using Free Pascal on its own. No
# Lazarus, no lazbuild, no .lpi.
#
# make deps fetch the outside pieces into vendor/
# make build the executable into build/
# make app macOS only: build build/SpecBAS.app, self-contained
# make clean remove build/
# make distclean also remove vendor/
#
# Linking against a framework on macOS: SDL2-for-Pascal's sdl2.pas carries
# {$LINKLIB libSDL2} for Darwin, so fpc always asks the linker for a library
# named libSDL2 and there is no switch that stops it. build/link/ holds one
# symlink, libSDL2.dylib, pointing at the framework's binary. The linker
# follows it, reads that binary's own LC_ID_DYLIB, and records it in the
# executable - @rpath/SDL2.framework/Versions/A/SDL2, which is the
# dependency a bundled framework needs. The symlink exists only at link
# time; nothing is copied and nothing is rewritten, so the framework's
# signature stays valid.
#
# On Linux SDL2 comes from the distribution. sdl2.pas names libSDL2.so, fpc
# turns that into -lSDL2, and the development package supplies it.

FPC ?= fpc
APP_NAME ?= SpecBAS
BIN_NAME ?= specbas

HERE := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST)))))
ROOT := $(dir $(HERE))
SRC := $(ROOT)src
VENDOR := $(HERE)/vendor
BUILD := $(HERE)/build
UNITS := $(VENDOR)/SDL2-for-Pascal/units
FRAMEWORK := $(VENDOR)/SDL2.framework
LINKDIR := $(BUILD)/link
OBJDIR := $(BUILD)/obj
APP := $(BUILD)/$(APP_NAME).app
PROGRAM := $(SRC)/SpecBAS_SDL2.dpr

UNAME_S := $(shell uname -s)

# -O1, not -O2: Free Pascal 3.2.2 for aarch64 raises internal error
# 200510011 on SP_FPEditor.pas at -O2.
#
# -MDelphi matches the mode every SpecBAS unit selects for itself.
#
# -Fi$(OBJDIR) is for the generated version include below.
FPCFLAGS := -MDelphi -Sh -O1 -vew -Fu$(SRC) -Fi$(SRC) -Fu$(UNITS) -Fi$(UNITS) \
-Fi$(OBJDIR)

# The version SpecBAS shows in Finder, taken from the same file the Windows
# build's VERSIONINFO resource is written in.
VERSION_RC := $(SRC)/SpecBAS.rc
VERSION := $(shell sed -n 's/^FILEVERSION *[0-9]*,[0-9]*,\([0-9]*\),\([0-9]*\).*/\1.\2/p' $(VERSION_RC))
BUILDNUM := $(shell sed -n 's/^FILEVERSION *[0-9]*,[0-9]*,[0-9]*,\([0-9]*\).*/\1/p' $(VERSION_RC))

ifeq ($(UNAME_S),Darwin)
# -WM11.0 pins the deployment target to the macOS 11 floor the SDL2 release
# framework carries, so the build is not tied to whatever macOS built it.
#
# -undefined dynamic_lookup leaves the BASS imports unresolved at link time.
# bass.pas declares them "delayed", which Free Pascal honours on Windows
# only; elsewhere they are ordinary imports and the link fails with no BASS
# library present. SpecBAS loads BASS by hand at startup and runs silent
# when that fails, so nothing calls those symbols while the library is
# absent.
FPCFLAGS += -WM11.0 -Fl$(LINKDIR) -k-rpath -k@executable_path/../Frameworks \
-k-undefined -kdynamic_lookup
LINKDEPS := $(LINKDIR)/libSDL2.dylib
else
# The same BASS problem, answered in two pieces because GNU ld fails earlier
# than Apple's linker does.
#
# 1. bass.pas names its library libbass.so, which fpc hands to the linker
# as -lbass, so the link dies on a missing library before it reaches
# the symbols. build/link/libbass.so is an empty GNU ld linker script:
# -lbass finds it, it contributes nothing, and unlike a stub shared
# object it leaves no DT_NEEDED behind, so the finished executable does
# not ask for a BASS library at run time either.
# 2. --unresolved-symbols=ignore-all then lets the BASS_ symbols stay
# undefined, which is what "delayed" does on Windows.
FPCFLAGS += -Fl$(LINKDIR) -k--unresolved-symbols=ignore-all
LINKDEPS := $(LINKDIR)/libbass.so
endif

.PHONY: all deps exe app clean distclean

all: exe

deps:
@$(HERE)/fetch-deps.sh

$(LINKDIR)/libSDL2.dylib: $(FRAMEWORK)/Versions/A/SDL2
@mkdir -p $(LINKDIR)
@ln -sf $(FRAMEWORK)/Versions/A/SDL2 $@

$(LINKDIR)/libbass.so:
@mkdir -p $(LINKDIR)
@printf '/* Empty linker script. See the BASS note in build-sdl2/Makefile. */\n' > $@

# The version SpecBAS puts in its window title and in BUILDSTR.
#
# On Windows it comes from the executable's own VERSIONINFO resource, read
# back at run time. src/SpecBAS.rc is where that resource is written, so it
# is the project's one statement of its own version, and this build reads
# the same file rather than keeping a second copy of the number.
#
# Only the last two of the four components are used, because that is what
# the Windows title bar shows: FILEVERSION 0,0,0,1511 is 0.1511 there and
# 0.1511 here.
VERSION_INC := $(OBJDIR)/SpecBAS_Version.inc

$(VERSION_INC): $(VERSION_RC)
@mkdir -p $(OBJDIR)
@sed -n 's/^FILEVERSION *\([0-9]*\),\([0-9]*\),\([0-9]*\),\([0-9]*\).*/'"'"'\3.\4'"'"'/p' \
$(VERSION_RC) > $@
@test -s $@ || { echo "Makefile: no FILEVERSION in $(VERSION_RC)" >&2; rm -f $@; exit 1; }

exe: $(LINKDEPS) $(VERSION_INC)
@mkdir -p $(OBJDIR)
$(FPC) $(FPCFLAGS) -FU$(OBJDIR) -FE$(OBJDIR) -o$(OBJDIR)/$(BIN_NAME) $(PROGRAM)

app: exe
ifeq ($(UNAME_S),Darwin)
@$(HERE)/bundle.sh $(OBJDIR)/$(BIN_NAME) $(FRAMEWORK) $(APP) $(BIN_NAME) \
$(VERSION) $(BUILDNUM)
else
@echo "make app builds a macOS application bundle; on $(UNAME_S) the"
@echo "executable in $(OBJDIR) is the build product."
endif

clean:
rm -rf $(BUILD)

distclean: clean
rm -rf $(VENDOR)
71 changes: 71 additions & 0 deletions build-sdl2/bundle.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
# bundle.sh <executable> <SDL2.framework> <out.app> <binname> <version> <build>
#
# Assemble a self-contained .app: the executable, an Info.plist, and SDL2
# inside the bundle. The result runs on a machine with no toolchain and no
# system SDL2.
#
# Three facts have to agree, and none of them is set here:
#
# 1. SDL2.framework's own install name is
# @rpath/SDL2.framework/Versions/A/SDL2, which is what the release
# framework ships with.
# 2. The executable records that same dependency, picked up at link time
# through the symlink the Makefile makes.
# 3. The executable carries an rpath of @executable_path/../Frameworks,
# which from Contents/MacOS is Contents/Frameworks, where the framework
# is copied.
#
# So nothing needs install_name_tool, and because nothing is rewritten the
# framework's signature survives the copy.

set -euo pipefail

exe="${1:?executable}"
framework="${2:?SDL2.framework}"
app="${3:?out .app}"
binname="${4:?bin name}"
version="${5:?version}"
buildnum="${6:?build number}"

contents="${app}/Contents"

rm -rf "${app}"
mkdir -p "${contents}/MacOS" "${contents}/Frameworks" "${contents}/Resources"

cp "${exe}" "${contents}/MacOS/${binname}"
# ditto keeps the framework's symlinks and signature; cp -R does not.
ditto "${framework}" "${contents}/Frameworks/SDL2.framework"

cat > "${contents}/Info.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key> <string>en</string>
<key>CFBundleExecutable</key> <string>${binname}</string>
<key>CFBundleIdentifier</key> <string>org.specos.SpecBAS</string>
<key>CFBundleInfoDictionaryVersion</key> <string>6.0</string>
<key>CFBundleName</key> <string>$(basename "${app}" .app)</string>
<key>CFBundlePackageType</key> <string>APPL</string>
<key>CFBundleShortVersionString</key> <string>${version}</string>
<key>CFBundleVersion</key> <string>${buildnum}</string>
<key>LSMinimumSystemVersion</key> <string>11.0</string>
<key>NSHighResolutionCapable</key> <true/>
<key>NSPrincipalClass</key> <string>NSApplication</string>
</dict>
</plist>
PLIST

# Ad-hoc signature. Apple Silicon refuses to run an unsigned Mach-O.
# --deep is deprecated, so each piece is named, innermost first.
codesign --force --sign - --timestamp=none \
"${contents}/Frameworks/SDL2.framework" >/dev/null 2>&1
codesign --force --sign - --timestamp=none \
"${contents}/MacOS/${binname}" >/dev/null 2>&1
codesign --force --sign - --timestamp=none "${app}" >/dev/null 2>&1

echo "==> ${app}"
codesign --verify --deep --verbose=1 "${app}" 2>&1 | sed 's/^/ /'
echo " executable links:"
otool -L "${contents}/MacOS/${binname}" | tail -n +2 | sed 's/^/ /'
120 changes: 120 additions & 0 deletions build-sdl2/fetch-deps.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env bash
#
# Put the outside pieces the SDL2 build needs into vendor/:
#
# vendor/SDL2-for-Pascal/ the Pascal bindings, all platforms
# vendor/SDL2.framework the SDL2 runtime for the bundle, macOS only
#
# vendor/ is gitignored. Everything fetched here is pinned and checked, so
# this script is the tracked half and vendor/ is disposable.
#
# On Linux SDL2 comes from the distribution instead: the bindings name
# libSDL2.so, fpc turns that into -lSDL2, and the development package
# supplies both. This script checks it is there and says what to install if
# it is not.
#
# On macOS the runtime is the release framework from libsdl.org rather than
# Homebrew's sdl2, which is sdl2-compat: a shim that reaches SDL3 by dlopen
# at run time, so copying it into a bundle copies something that then looks
# for an SDL3 the target machine has no reason to have. Homebrew also builds
# to the host's own macOS version, so the result refuses to load on anything
# older. The release framework is built for this job: its LC_ID_DYLIB is
# already @rpath/SDL2.framework/Versions/A/SDL2, it is universal, and its
# minimum is macOS 11.
#
# The disk image is mounted with hdiutil rather than unpacked with an
# archiver. A framework is held together by symlinks, and an archiver writes
# those out as small text files, which gives a directory that looks right,
# fails to load, and fails codesign.

set -euo pipefail

here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
vendor="${here}/vendor"
work="${vendor}/.download"

SDL2_VERSION="2.32.8"
SDL2_DMG_SHA256="de07f71cc85cd8909c977e105c4f2ca419abd09b981c347e003592186caf6fa0"
SDL2_DMG_URL="https://github.com/libsdl-org/SDL/releases/download/release-${SDL2_VERSION}/SDL2-${SDL2_VERSION}.dmg"

BINDINGS_URL="https://github.com/PascalGameDevelopment/SDL2-for-Pascal.git"
BINDINGS_COMMIT="a48b83efbfa7c13ce548905ebdc954eb6c8384be"

mkdir -p "${work}"

# --- SDL2 runtime ------------------------------------------------------

case "$(uname -s)" in
Darwin)
if [[ -d "${vendor}/SDL2.framework" ]]; then
echo "==> SDL2.framework already present"
else
dmg="${work}/SDL2-${SDL2_VERSION}.dmg"
if [[ ! -f "${dmg}" ]]; then
echo "==> downloading SDL2 ${SDL2_VERSION}"
curl -fsSL -o "${dmg}" "${SDL2_DMG_URL}"
fi

got="$(shasum -a 256 "${dmg}" | awk '{print $1}')"
if [[ "${got}" != "${SDL2_DMG_SHA256}" ]]; then
echo "fetch-deps.sh: sha256 mismatch for ${dmg}" >&2
echo " expected ${SDL2_DMG_SHA256}" >&2
echo " got ${got}" >&2
exit 1
fi

# The mount point is hdiutil's own choice, read back out of its
# output. Naming one under this directory is refused where the
# repository is on a secondary volume.
echo "==> mounting ${dmg}"
mnt="$(hdiutil attach -nobrowse -readonly "${dmg}" \
| sed -n 's#.*\(/Volumes/.*\)$#\1#p' | tail -1)"
if [[ -z "${mnt}" || ! -d "${mnt}/SDL2.framework" ]]; then
echo "fetch-deps.sh: could not mount ${dmg}" >&2
exit 1
fi
trap 'hdiutil detach -quiet "'"${mnt}"'" >/dev/null 2>&1 || true' EXIT

echo "==> copying SDL2.framework into vendor/"
# ditto, not cp -R: it keeps the symlinks, permissions and extended
# attributes that leave the framework's signature intact.
ditto "${mnt}/SDL2.framework" "${vendor}/SDL2.framework"
cp "${mnt}/License.txt" "${vendor}/SDL2-License.txt"

hdiutil detach -quiet "${mnt}" || true
trap - EXIT
fi

codesign --verify --verbose=1 "${vendor}/SDL2.framework" 2>&1 | sed 's/^/ /'
;;
Linux)
if command -v sdl2-config >/dev/null 2>&1; then
echo "==> system SDL2 $(sdl2-config --version)"
elif command -v pkg-config >/dev/null 2>&1 && pkg-config --exists sdl2; then
echo "==> system SDL2 $(pkg-config --modversion sdl2)"
else
echo "fetch-deps.sh: SDL2 development files not found." >&2
echo " Debian, Ubuntu, Raspberry Pi OS: sudo apt install libsdl2-dev" >&2
echo " Fedora: sudo dnf install SDL2-devel" >&2
echo " Arch: sudo pacman -S sdl2" >&2
exit 1
fi
;;
*)
echo "==> $(uname -s): assuming SDL2 is supplied by the system"
;;
esac

# --- SDL2-for-Pascal ---------------------------------------------------

if [[ -d "${vendor}/SDL2-for-Pascal/.git" ]]; then
echo "==> SDL2-for-Pascal already present"
else
echo "==> cloning SDL2-for-Pascal"
rm -rf "${vendor}/SDL2-for-Pascal"
git clone --quiet "${BINDINGS_URL}" "${vendor}/SDL2-for-Pascal"
fi
git -C "${vendor}/SDL2-for-Pascal" checkout --quiet "${BINDINGS_COMMIT}"
echo " bindings at $(git -C "${vendor}/SDL2-for-Pascal" rev-parse HEAD)"

echo "==> vendor/ ready"
4 changes: 3 additions & 1 deletion src/RunTimeCompiler.pas
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ TPayload = class(TObject)
implementation

uses
SysUtils, ActiveX, SP_Tokenise, SP_Util, SP_BankFiling, SP_SysVars;
// ActiveX is a Windows unit and supplies IsEqualGUID there. Off Windows
// the same function comes from SysUtils.
SysUtils, {$IFNDEF UNIX}ActiveX, {$ENDIF}SP_Tokenise, SP_Util, SP_BankFiling, SP_SysVars;

type
TPayloadFooter = packed record
Expand Down
2 changes: 1 addition & 1 deletion src/SP_BankManager.pas
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ interface

implementation

Uses MainForm, {$IFNDEF RUNTIMEONLY}SP_FPEditor, SP_BASICEditorHostUnit, SP_ToolTipWindow, {$ENDIF}
Uses {$IFDEF SDL2}SP_SDL2Host{$ELSE}MainForm{$ENDIF}, {$IFNDEF RUNTIMEONLY}SP_FPEditor, SP_BASICEditorHostUnit, SP_ToolTipWindow, {$ENDIF}
SP_Graphics, SP_Graphics32, SP_Sound, SP_Main, SP_BaseComponentUnit, SP_3DEngineUnit;

Procedure SP_ChangeBankSize(Index: Integer);
Expand Down
2 changes: 1 addition & 1 deletion src/SP_CheckBoxUnit.pas
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ interface

implementation

Uses {$IFNDEF FPC}Windows{$ELSE}LCLType{$ENDIF}, Types, Classes, Math, SP_Interpret_PostFix, SP_Input, SP_Components, SP_Sound, SP_SysVars;
Uses {$IFNDEF FPC}Windows, {$ELSE}{$IFNDEF SDL2}LCLType, {$ENDIF}{$ENDIF}Types, Classes, Math, SP_Interpret_PostFix, SP_Input, SP_Components, SP_Sound, SP_SysVars;

// SP_CheckBox

Expand Down
2 changes: 1 addition & 1 deletion src/SP_Components.pas
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
interface

Uses Types, SysUtils, {$IFNDEF FPC}System.Generics.Collections{$ELSE}Generics.Collections{$ENDIF}, Classes, SyncObjs, Math,
{$IFNDEF FPC}Vcl.ClipBrd{$ELSE}ClipBrd{$ENDIF}, SP_SysVars, SP_FileIO, SP_Util, SP_ButtonUnit, SP_BaseComponentUnit, SP_Errors;
{$IFNDEF FPC}Vcl.ClipBrd, {$ELSE}{$IFNDEF SDL2}ClipBrd, {$ENDIF}{$ENDIF}SP_SysVars, SP_FileIO, SP_Util, SP_ButtonUnit, SP_BaseComponentUnit, SP_Errors;

// A collection of UI elements for building UI apps. Based on Windows controls.

Expand Down
4 changes: 2 additions & 2 deletions src/SP_DebugPanel.pas
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

interface

uses Dialogs, Math, Classes, SyncObjs, SysUtils, SP_Util, SP_BaseComponentUnit, SP_ListBoxUnit, SP_ComboBoxUnit, SP_ControlMsgs, SP_ButtonUnit, SP_Input, SP_ContainerUnit, SP_AmigaGuideUnit;
uses {$IFNDEF SDL2}Dialogs, {$ENDIF}Math, Classes, SyncObjs, SysUtils, SP_Util, SP_BaseComponentUnit, SP_ListBoxUnit, SP_ComboBoxUnit, SP_ControlMsgs, SP_ButtonUnit, SP_Input, SP_ContainerUnit, SP_AmigaGuideUnit;

Type

Expand Down Expand Up @@ -73,7 +73,7 @@ interface

implementation

Uses {$IFNDEF FPC}Vcl.ClipBrd{$ELSE}ClipBrd{$ENDIF}, SP_FPEditor, SP_Errors, SP_Graphics, SP_BankManager, SP_BankFiling, SP_SysVars, SP_Components, SP_Variables, SP_AnsiStringList,
Uses {$IFNDEF FPC}Vcl.ClipBrd, {$ELSE}{$IFNDEF SDL2}ClipBrd, {$ENDIF}{$ENDIF}SP_FPEditor, SP_Errors, SP_Graphics, SP_BankManager, SP_BankFiling, SP_SysVars, SP_Components, SP_Variables, SP_AnsiStringList,
SP_Interpret_PostFix, SP_FileIO, SP_Main, SP_MenuActions, SP_BASICEditorHostUnit, SP_MemoUnit, SP_Debugging, SP_Execute;

Procedure SP_UpdateAfterDebug;
Expand Down
Loading