Skip to content

Commit c96712b

Browse files
Merge pull request #656 from splitgraph/bugfix/osx-pyinstaller-fixes
Add new artifact `sgr-osx-x86_64.tgz` to releases, compiled with `pyinstaller --onedir`, and default to it in `install.sh` on Darwin
2 parents 9442dbb + a8ddd1e commit c96712b

File tree

4 files changed

+137
-42
lines changed

4 files changed

+137
-42
lines changed

.ci/build_wheel.sh

+26-10
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,36 @@ DEFAULT_PYPI_URL="https://test.pypi.org/legacy/"
55
CI_DIR=$(cd -P -- "$(dirname -- "$0")" && pwd -P)
66
REPO_ROOT_DIR="${CI_DIR}/.."
77

8-
test -z "$PYPI_PASSWORD" && { echo "Fatal Error: No PYPI_PASSWORD set" ; exit 1 ; }
8+
# By default, will configure PyPi for publishing.
9+
# To skip publishing setup, set NO_PUBLISH=1 .ci/build_wheel.sh
10+
NO_PUBLISH_FLAG="${NO_PUBLISH}"
11+
12+
test -n "$NO_PUBLISH_FLAG" && { echo "Skipping publish because \$NO_PUBLISH is set" ; }
13+
test -z "$PYPI_PASSWORD" && \
14+
! test -n "$NO_PUBLISH_FLAG" \
15+
&& { echo "Fatal Error: No PYPI_PASSWORD set. To skip, set NO_PUBLISH=1" ; exit 1 ; }
916
test -z "$PYPI_URL" && { echo "No PYPI_URL set. Defaulting to ${DEFAULT_PYPI_URL}" ; }
1017

1118
PYPI_URL=${PYPI_URL-"${DEFAULT_PYPI_URL}"}
1219

1320
source "$HOME"/.poetry/env
1421

1522
# Configure pypi for deployment
16-
pushd "$REPO_ROOT_DIR" \
17-
&& poetry config repositories.testpypi "$PYPI_URL" \
18-
&& poetry config http-basic.testpypi splitgraph "$PYPI_PASSWORD" \
19-
&& poetry config http-basic.pypi splitgraph "$PYPI_PASSWORD" \
20-
&& poetry build \
21-
&& popd \
22-
&& exit 0
23-
24-
exit 1
23+
pushd "$REPO_ROOT_DIR"
24+
25+
set -e
26+
if ! test -n "$NO_PUBLISH_FLAG" ; then
27+
echo "Configuring poetry with password from \$PYPI_PASSWORD"
28+
echo "To skip, try: NO_PUBLISH=1 $0 $*"
29+
poetry config http-basic.testpypi splitgraph "$PYPI_PASSWORD"
30+
poetry config http-basic.pypi splitgraph "$PYPI_PASSWORD"
31+
fi
32+
33+
# Set the PyPi URL because it can't hurt (we skipped setting the credentials)
34+
poetry config repositories.testpypi "$PYPI_URL"
35+
36+
poetry build
37+
popd
38+
39+
set +e
40+
exit 0

.github/workflows/build_and_test_and_release.yml

+29-9
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ jobs:
88
runs-on: ubuntu-18.04
99
if: "!contains(github.event.head_commit.message, '[skip ci]')"
1010
env:
11-
COMPOSE_VERSION: '1.25.4'
12-
POETRY_VERSION: '1.1.6'
11+
COMPOSE_VERSION: "1.25.4"
12+
POETRY_VERSION: "1.1.6"
1313
DOCKER_REPO: splitgraph
1414
DOCKER_ENGINE_IMAGE: engine
1515
DOCKER_TAG: development
@@ -23,7 +23,7 @@ jobs:
2323
- name: Setup Python 3.8
2424
uses: actions/setup-python@v2
2525
with:
26-
python-version: '3.8'
26+
python-version: "3.8"
2727
- uses: actions/cache@v1
2828
with:
2929
path: ~/.cache/pip
@@ -113,6 +113,15 @@ jobs:
113113
# TODO figure out if we want to do poetry upload here (can only do once, so will fail
114114
# if we're retrying an upload)
115115
# "$HOME"/.poetry/bin/poetry build
116+
- name: "Build wheel only (do not configure publish)"
117+
# The {windows,linux,osx}_binary stage will run if ref is tag, or msg contains "[artifacts]""
118+
# If no tag, but [artifacts], we still need to build the wheel, but with NO_PUBLISH=1
119+
# But if tag _and_ [artifacts], we want to skip this stage, to not build the wheel twice
120+
if: "!startsWith(github.ref, 'refs/tags/') && contains(github.event.head_commit.message, '[artifacts]')"
121+
env:
122+
NO_PUBLISH: "1"
123+
run: |
124+
./.ci/build_wheel.sh
116125
- name: "Upload release artifacts"
117126
uses: actions/upload-artifact@v2
118127
with:
@@ -121,7 +130,7 @@ jobs:
121130

122131
windows_binary:
123132
runs-on: windows-latest
124-
if: "startsWith(github.ref, 'refs/tags/')"
133+
if: "startsWith(github.ref, 'refs/tags/') || contains(github.event.head_commit.message, '[artifacts]')"
125134
needs: build_and_test
126135
steps:
127136
- uses: actions/checkout@v1
@@ -150,7 +159,7 @@ jobs:
150159

151160
linux_binary:
152161
runs-on: ubuntu-18.04
153-
if: "startsWith(github.ref, 'refs/tags/')"
162+
if: "startsWith(github.ref, 'refs/tags/') || contains(github.event.head_commit.message, '[artifacts]')"
154163
needs: build_and_test
155164
steps:
156165
- uses: actions/checkout@v1
@@ -188,7 +197,7 @@ jobs:
188197

189198
osx_binary:
190199
runs-on: macOS-latest
191-
if: "startsWith(github.ref, 'refs/tags/')"
200+
if: "startsWith(github.ref, 'refs/tags/') || contains(github.event.head_commit.message, '[artifacts]')"
192201
needs: build_and_test
193202
steps:
194203
- uses: actions/checkout@v1
@@ -201,18 +210,27 @@ jobs:
201210
with:
202211
name: dist
203212
path: dist
204-
- name: Build the binary
213+
- name: Build the single-file binary
205214
run: |
206215
pip install dist/splitgraph-*-py3-none-any.whl
207216
pip install pyinstaller
208217
pyinstaller -F splitgraph.spec
209218
dist/sgr --version
210-
- name: Upload binary as artifact
219+
- name: Upload single-file binary as artifact
211220
uses: actions/upload-artifact@v2
212221
with:
213222
name: sgr-osx
214223
path: dist/sgr
215-
224+
- name: Build the multi-file binary.gz
225+
run: |
226+
pyinstaller --clean --noconfirm --onedir splitgraph.spec
227+
dist/sgr-pkg/sgr --version
228+
cd dist/sgr-pkg && tar zcvf ../sgr.tgz .
229+
- name: Upload multi-file binary.gz as artifact
230+
uses: actions/upload-artifact@v2
231+
with:
232+
name: sgr-osx.tgz
233+
path: dist/sgr.tgz
216234

217235
upload_release:
218236
runs-on: ubuntu-18.04
@@ -231,13 +249,15 @@ jobs:
231249
mv artifacts/sgr-windows/sgr.exe artifacts/sgr-windows-x86_64.exe
232250
mv artifacts/sgr-linux/sgr artifacts/sgr-linux-x86_64
233251
mv artifacts/sgr-osx/sgr artifacts/sgr-osx-x86_64
252+
mv artifacts/sgr-osx/sgr.tgz artifacts/sgr-osx-x86_64.tgz
234253
- name: Release artifacts
235254
uses: softprops/action-gh-release@v1
236255
with:
237256
files: |
238257
artifacts/sgr-windows-x86_64.exe
239258
artifacts/sgr-linux-x86_64
240259
artifacts/sgr-osx-x86_64
260+
artifacts/sgr-osx-x86_64.tgz
241261
artifacts/dist/sgr-docs-bin.tar.gz
242262
artifacts/dist/install.sh
243263
draft: true

install.sh

+36-8
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,15 @@ _get_binary_name() {
6262
if [ "$os" == Linux ]; then
6363
BINARY="sgr-linux-x86_64"
6464
elif [ "$os" == Darwin ]; then
65-
BINARY="sgr-osx-x86_64"
65+
if [ -n "$FORCE_ONEFILE" ] ; then
66+
echo "Forcing --onefile installation on OS X because \$FORCE_ONEFILE is set."
67+
BINARY="sgr-osx-x86_64"
68+
else
69+
# OS X has bad single-file executable support (pyinstaller --onefile), so we default to --onedir variant
70+
echo "Installing optimized package for OS X (built with pyinstaller --onedir instead of --onefile)"
71+
echo "To force install single-file executable (not recommended), set FORCE_ONEFILE=1"
72+
BINARY="sgr-osx-x86_64.tgz"
73+
fi
6674
else
6775
_die "This installation method only supported on Linux/OSX. Please see https://www.splitgraph.com/docs/installation/ for other installation methods."
6876
fi
@@ -77,14 +85,34 @@ _install_binary () {
7785
_check_sgr_exists
7886

7987
URL="https://github.com/splitgraph/splitgraph/releases/download/v${SGR_VERSION}"/$BINARY
80-
echo "Installing the sgr binary from $URL into $INSTALL_DIR"
81-
mkdir -p "$INSTALL_DIR"
82-
curl -fsL "$URL" > "$INSTALL_DIR/sgr"
83-
chmod +x "$INSTALL_DIR/sgr"
84-
"$INSTALL_DIR/sgr" --version
85-
echo "sgr binary installed."
86-
echo
88+
# on OS X, splitgraph.spec is called with --onedir to output .tgz of exe and shlibs
89+
if [ "$BINARY" == "sgr-osx-x86_64.tgz" ] ; then
90+
echo "Installing the compressed sgr binary and deps from $URL into $INSTALL_DIR"
91+
echo "Installing sgr binary and deps into $INSTALL_DIR/pkg"
92+
93+
if [ -d "$INSTALL_DIR/pkg/sgr" ] ; then
94+
echo "Removing existing $INSTALL_DIR/pkg/sgr"
95+
rm -rf "$INSTALL_DIR/pkg/sgr"
96+
fi
97+
98+
mkdir -p "$INSTALL_DIR/pkg/sgr"
99+
100+
curl -fsL "$URL" > "$INSTALL_DIR/pkg/sgr/sgr.tgz"
101+
102+
echo "Extract sgr binary and deps into $INSTALL_DIR/pkg/sgr (necessary on MacOS)"
103+
(cd "$INSTALL_DIR"/pkg/sgr && tar xfz sgr.tgz && rm sgr.tgz)
104+
echo "Main sgr binary is at $INSTALL_DIR/pkg/sgr/sgr"
105+
echo "Link $INSTALL_DIR/sgr -> $INSTALL_DIR/pkg/sgr/sgr"
106+
ln -fs "$INSTALL_DIR"/pkg/sgr/sgr "$INSTALL_DIR"/sgr
107+
else
108+
echo "Installing the sgr binary from $URL into $INSTALL_DIR"
109+
mkdir -p "$INSTALL_DIR"
110+
curl -fsL "$URL" > "$INSTALL_DIR/sgr"
111+
chmod +x "$INSTALL_DIR/sgr"
112+
fi
87113

114+
"$INSTALL_DIR/sgr" --version && echo "sgr binary installed." && echo && return 0
115+
_die "Installation apparently failed. got non-zero exit code from: $INSTALL_DIR/sgr --version"
88116
}
89117

90118
_setup_engine() {

splitgraph.spec

+46-15
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,23 @@
33
# * LD_LIBRARY_PATH=`echo $(python3-config --prefix)/lib` pyinstaller -F splitgraph.spec produces a single sgr binary in the dist/ folder
44
# with libc being the only dynamic dependency (python interpreter included)
55
# * can also do poetry install && poetry run pyinstaller -F splitgraph.spec to build the binary inside of the poetry's venv.
6+
# * specifying `--onedir` instead of `-F` will compile a multi-file executable with COLLECT()
7+
# * e.g. : pyinstaller --clean --noconfirm --onedir splitgraph.spec
68

9+
import sys
710
import os
811
import importlib
912

13+
# Pass --onedir or -D to build a multi-file executable (dir with shlibs + exe)
14+
# Note: This is the same flag syntax as pyinstaller uses, but when using a
15+
# .spec file with pyinstaller, the flag is normally ignored, so we
16+
# explicitly check for it here. This way we can pass different arguments
17+
# to EXE() and conditionally call COLLECTION() without duplicating code.
18+
MAKE_EXE_COLLECTION = False
19+
if "--onedir" in sys.argv or "-D" in sys.argv:
20+
print("splitgraph.spec : --onedir was specified. Will build a multi-file executable...")
21+
MAKE_EXE_COLLECTION = True
22+
1023
block_cipher = None
1124

1225
datas = []
@@ -43,18 +56,36 @@ a = Analysis(
4356
a.datas += Tree("./splitgraph/resources", "splitgraph/resources")
4457

4558
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
46-
exe = EXE(
47-
pyz,
48-
a.scripts,
49-
a.binaries,
50-
a.zipfiles,
51-
a.datas,
52-
[],
53-
name="sgr",
54-
debug=False,
55-
bootloader_ignore_signals=False,
56-
strip=False,
57-
upx=True,
58-
runtime_tmpdir=None,
59-
console=True,
60-
)
59+
60+
# Note: `exe` global is injected by pyinstaller. Can see the code for EXE at:
61+
# https://github.com/pyinstaller/pyinstaller/blob/15f23a8a89be5453b3520df8fc3e667346e103a6/PyInstaller/building/api.py
62+
63+
# TOCS to include in EXE() when in single-file mode (default, i.e. no --onedir flag)
64+
all_tocs = [a.scripts, a.binaries, a.zipfiles, a.datas]
65+
# TOCS to include in EXE() when in multi-file mode (i.e., --onedir flag)
66+
exe_tocs = [a.scripts]
67+
# TOCS to include in COLL() when in multi-file mode (i.e., --onedir flag)
68+
coll_tocs = [a.binaries, a.zipfiles, a.datas]
69+
70+
# When compiling single-file executable, we include every TOC in the EXE
71+
# When compiling multi-file executable, include some TOC in EXE, and rest in COLL
72+
exe_args = [pyz, *exe_tocs, []] if MAKE_EXE_COLLECTION else [pyz, *all_tocs, []]
73+
74+
exe_kwargs_base = {
75+
"name": "sgr",
76+
"debug": False,
77+
"bootloader_ignore_signals": False,
78+
"strip_binaries": False,
79+
"runtime_tmpdir": None,
80+
"console": True,
81+
}
82+
# In multi-file mode, we exclude_binaries from EXE since they will be in COLL
83+
exe_kwargs_onedir = {**exe_kwargs_base, "upx": False, "exclude_binaries": True}
84+
# In single-file mode, we set upx: true because it works. (It might actually work in multi-file mode too)
85+
exe_kwargs_onefile = {**exe_kwargs_base, "upx": True, "exclude_binaries": False}
86+
exe_kwargs = exe_kwargs_onedir if MAKE_EXE_COLLECTION else exe_kwargs_onefile
87+
88+
exe = EXE(*exe_args, **exe_kwargs)
89+
90+
if MAKE_EXE_COLLECTION:
91+
coll = COLLECT(exe, *coll_tocs, name="sgr-pkg", strip=False, upx=False)

0 commit comments

Comments
 (0)