Skip to content

Commit af24108

Browse files
committed
feat: shell script for creating macOS app bundle
Refs #2238
1 parent a3acc04 commit af24108

File tree

1 file changed

+203
-0
lines changed

1 file changed

+203
-0
lines changed

create-macos-app.sh

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
### CONFIGURATION #########################################################
5+
6+
APP_NAME="heidisql"
7+
BUNDLE_NAME="${APP_NAME}.app"
8+
APP_DIR="$(pwd)/${BUNDLE_NAME}"
9+
10+
# Path to the already built Lazarus executable
11+
EXECUTABLE_PATH="$(pwd)/out/heidisql" # change if needed
12+
13+
# Directory that contains your ini files
14+
INI_SOURCE_DIR="$(pwd)/extra/ini" # change if needed
15+
16+
# List your ini files here (5 files as requested)
17+
INI_FILES=(
18+
"functions.mariadb.ini"
19+
"functions.mysql.ini"
20+
"functions.mysql8.ini"
21+
"functions.postgresql.ini"
22+
"functions.redshift.ini"
23+
"functions.sqlite.ini"
24+
)
25+
26+
# Homebrew prefix (auto-detected; override if needed)
27+
BREW_PREFIX="$(brew --prefix 2>/dev/null || echo "/opt/homebrew")"
28+
29+
### INSTALL REQUIRED LIBRARIES VIA HOMEBREW ##############################
30+
31+
# MySQL client (libmysqlclient.dylib)
32+
brew list mysql-client >/dev/null 2>&1 || brew install mysql-client # [web:26]
33+
34+
# PostgreSQL client (libpq.dylib)
35+
brew list libpq >/dev/null 2>&1 || brew install libpq # [web:28]
36+
37+
# SQLite (libsqlite3.dylib; comes with macOS, but install via brew for consistency)
38+
brew list sqlite >/dev/null 2>&1 || brew install sqlite # [web:28]
39+
40+
MYSQL_LIB_DIR="${BREW_PREFIX}/opt/mysql-client/lib"
41+
PG_LIB_DIR="${BREW_PREFIX}/opt/libpq/lib"
42+
SQLITE_LIB_DIR="${BREW_PREFIX}/opt/sqlite/lib"
43+
44+
### PREPARE APP BUNDLE STRUCTURE #########################################
45+
46+
rm -rf "${APP_DIR}"
47+
mkdir -p "${APP_DIR}/Contents/MacOS"
48+
mkdir -p "${APP_DIR}/Contents/Resources"
49+
mkdir -p "${APP_DIR}/Contents/Frameworks" # where we will put .dylib files [web:12][web:19]
50+
51+
# Copy main executable
52+
cp "${EXECUTABLE_PATH}" "${APP_DIR}/Contents/MacOS/${APP_NAME}"
53+
chmod +x "${APP_DIR}/Contents/MacOS/${APP_NAME}"
54+
55+
# Minimal Info.plist (adjust identifiers/versions as needed) [web:12][web:16]
56+
cat > "${APP_DIR}/Contents/Info.plist" <<EOF
57+
<?xml version="1.0" encoding="UTF-8"?>
58+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
59+
<plist version="1.0">
60+
<dict>
61+
<key>CFBundleName</key>
62+
<string>${APP_NAME}</string>
63+
<key>CFBundleDisplayName</key>
64+
<string>${APP_NAME}</string>
65+
<key>CFBundleIdentifier</key>
66+
<string>com.example.${APP_NAME}</string>
67+
<key>CFBundleVersion</key>
68+
<string>1.0</string>
69+
<key>CFBundleShortVersionString</key>
70+
<string>1.0</string>
71+
<key>CFBundleExecutable</key>
72+
<string>${APP_NAME}</string>
73+
<key>CFBundlePackageType</key>
74+
<string>APPL</string>
75+
<key>LSMinimumSystemVersion</key>
76+
<string>15.0</string>
77+
</dict>
78+
</plist>
79+
EOF
80+
81+
### COPY INI FILES INTO RESOURCES ########################################
82+
83+
for ini in "${INI_FILES[@]}"; do
84+
if [[ -f "${INI_SOURCE_DIR}/${ini}" ]]; then
85+
cp "${INI_SOURCE_DIR}/${ini}" "${APP_DIR}/Contents/Resources/${ini}"
86+
else
87+
echo "WARNING: INI file not found: ${INI_SOURCE_DIR}/${ini}" >&2
88+
fi
89+
done
90+
91+
### FUNCTION: COPY A DYLIB AND ITS DEPENDENCIES ##########################
92+
93+
copy_and_rewrite_dylib() {
94+
local src_dylib="$1"
95+
local dest_dir="${APP_DIR}/Contents/Frameworks"
96+
mkdir -p "${dest_dir}"
97+
98+
local base
99+
base="$(basename "${src_dylib}")"
100+
local dest_dylib="${dest_dir}/${base}"
101+
102+
# If already copied, skip
103+
if [[ -f "${dest_dylib}" ]]; then
104+
return
105+
fi
106+
107+
echo "Copying ${src_dylib} -> ${dest_dylib}"
108+
cp "${src_dylib}" "${dest_dylib}"
109+
110+
# Make it writable for install_name_tool
111+
chmod u+w "${dest_dylib}"
112+
113+
# Change its own install name to @rpath/@loader_path-style inside the app [web:18]
114+
install_name_tool -id "@rpath/${base}" "${dest_dylib}"
115+
116+
# Find direct dependencies
117+
local deps
118+
# otool -L output: first line is the file itself, subsequent lines are dependencies [web:18][web:31]
119+
deps=$(otool -L "${dest_dylib}" | tail -n +2 | awk '{print $1}')
120+
121+
for dep in ${deps}; do
122+
case "${dep}" in
123+
/usr/lib/*|/System/*)
124+
# System libraries: keep them as-is, do not copy
125+
continue
126+
;;
127+
esac
128+
129+
# If dependency already points into @rpath/@loader_path, just rewrite to Frameworks
130+
local dep_base
131+
dep_base="$(basename "${dep}")"
132+
local new_dep_path="@loader_path/../Frameworks/${dep_base}"
133+
134+
echo " Rewriting dep ${dep} -> ${new_dep_path}"
135+
install_name_tool -change "${dep}" "${new_dep_path}" "${dest_dylib}"
136+
137+
# If dep is an absolute path, copy that dylib too
138+
if [[ -f "${dep}" ]]; then
139+
copy_and_rewrite_dylib "${dep}"
140+
fi
141+
done
142+
}
143+
144+
### COPY CLIENT LIBRARIES AND DEPENDENCIES ################################
145+
146+
# libmysqlclient.dylib [web:20][web:26]
147+
if ls "${MYSQL_LIB_DIR}"/libmysqlclient*.dylib >/dev/null 2>&1; then
148+
for f in "${MYSQL_LIB_DIR}"/libmysqlclient*.dylib; do
149+
copy_and_rewrite_dylib "${f}"
150+
done
151+
else
152+
echo "WARNING: No libmysqlclient*.dylib found in ${MYSQL_LIB_DIR}" >&2
153+
fi
154+
155+
# libpq.dylib [web:20]
156+
if [[ -f "${PG_LIB_DIR}/libpq.dylib" ]]; then
157+
copy_and_rewrite_dylib "${PG_LIB_DIR}/libpq.dylib"
158+
else
159+
# libpq often has versioned names
160+
if ls "${PG_LIB_DIR}"/libpq*.dylib >/dev/null 2>&1; then
161+
for f in "${PG_LIB_DIR}"/libpq*.dylib; do
162+
copy_and_rewrite_dylib "${f}"
163+
done
164+
else
165+
echo "WARNING: No libpq*.dylib found in ${PG_LIB_DIR}" >&2
166+
fi
167+
fi
168+
169+
# libsqlite3.dylib [web:20]
170+
if [[ -f "${SQLITE_LIB_DIR}/libsqlite3.dylib" ]]; then
171+
copy_and_rewrite_dylib "${SQLITE_LIB_DIR}/libsqlite3.dylib"
172+
elif ls "${SQLITE_LIB_DIR}"/libsqlite3*.dylib >/dev/null 2>&1; then
173+
for f in "${SQLITE_LIB_DIR}"/libsqlite3*.dylib; do
174+
copy_and_rewrite_dylib "${f}"
175+
done
176+
else
177+
echo "WARNING: No libsqlite3*.dylib found in ${SQLITE_LIB_DIR}" >&2
178+
fi
179+
180+
### FIX MAIN EXECUTABLE’S REFERENCES TO CLIENT LIBS #######################
181+
182+
EXE="${APP_DIR}/Contents/MacOS/${APP_NAME}"
183+
184+
# Helper: rewrite dependency of the main executable to bundled Frameworks [web:18]
185+
rewrite_exe_dep () {
186+
local pattern="$1" # e.g. libmysqlclient
187+
local dep
188+
dep=$(otool -L "${EXE}" | awk "/${pattern}.*dylib/ {print \$1}" | head -n1 || true)
189+
if [[ -n "${dep}" ]]; then
190+
local base
191+
base="$(basename "${dep}")"
192+
local new="@loader_path/../Frameworks/${base}"
193+
echo "Rewriting ${EXE} dep ${dep} -> ${new}"
194+
chmod u+w "${EXE}"
195+
install_name_tool -change "${dep}" "${new}" "${EXE}"
196+
fi
197+
}
198+
199+
rewrite_exe_dep "libmysqlclient"
200+
rewrite_exe_dep "libpq"
201+
rewrite_exe_dep "libsqlite3"
202+
203+
echo "Done. Bundled app is at: ${APP_DIR}"

0 commit comments

Comments
 (0)