-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathbundle_octave_app
executable file
·1609 lines (1441 loc) · 59.9 KB
/
bundle_octave_app
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env bash
# bundle_octave_app - Create an Octave.app app bundle
#
# Warning: This program will delete all your existing Homebrew build logs from
# ~/Library/Logs/Homebrew. This is so it can differentiate logs for the formulae
# built for Octave.app from those for any main system-level Homebrew installation.
#
# This script uses sudo when creating the disk image (DMG), and you will need to
# enter your password interactively at that point.
#
# Hints:
#
# If you want to do your compilations single-threaded, which will be slower
# but produce more readable output, set the environment variable
# HOMEBREW_MAKE_JOBS=1 before running bundle_octave_app.
#
# If you're working on getting a release out, consider calling this through one
# of the wip_bundle_* wrapper scripts, for convenience and consistency.
#
# Environment variables:
#
# OCTAPP_CREATEDMG_HOME - Path to the create-dmg repo or installation, if it's not
# at the default ~/repos/create-dmg location.
# TRACE – Set this to 1 to enable low-level debug tracing of the code in this script.
# Code notes:
#
# This must run under Bash 3.x; can't use Bash 4+ stuff like associative arrays.
#
# Path globals:
#
# * $INSTALL_DIR - The .app dir/bundle under /Applications, which is both its initial
# build and final installation location.
# * $INSTALL_USR - The Contents/Resources/usr dir under the app at $INSTALL_DIR. This
# is where most of the brew-managed packages live.
# * $BUILDS_DIR - The parent dir to put build-and-munge data under, typically ./build.
# * $BUILD_DIR - The build dir for this particular build run, under $BUILDS_DIR, in a
# subdir named after the build label.
# * $APP_BUILD - The copy of the .app under $BUILD_DIR, where we munge it in to its final
# form, and create the distribution DMG from.
# * $APP_BUILD_USR - The usr dir under the app at $APP_BUILD, analagous to $INSTALL_USR.
#
# Names and label globals:
# TODO: Fill this out. Maybe move it to a separate .md doc file.
#
# * $APP_NAME -
# * $OCTAVE_FORMULA_VERSION - The version of the octave brew formula used, which may have
# a "_<revision>" suffix.
# * $OCTAVE_VERSION - The version of Octave, without any revision suffix (e.g. "_1") like
# in the formula version. Uses all 3 "x.y.z" parts.
# * $OCTAVE_VERSION_LABEL - "Label" form of the Octave version, which uses just "x.y" and
# omits the final ".z" patch version for ".0" patch releases. This is the "friendly"
# human-presentable version, and is used in the app name and path.
# * $BUILD_LABEL - A unique label for a particular build *run*, not an octapp release, for
# internal use.
#
# TODO: "$INSTALL_DIR" is a lousy name for this. Come up with something better.
# TODO: Fix this "python not found" error at the end of the build. Might be as simple as
# using 'python3' instead, since that exists on macOS 12 and 14.
#
# For pkg-config to find [email protected] you may need to set:
# export PKG_CONFIG_PATH="/Applications/Octave-8.3.0.app/Contents/Resources/usr/opt/[email protected]/lib/pkgconfig"
#
# Uninstalling /Applications/Octave-8.3.0.app/Contents/Resources/usr/Cellar/rust/1.72.1... (38,925 files, 853.2MB)
# Munging app build at ./build/Octave-8.3.0.app
# Octave versions: ver='8.3.0' ver_string='GNU Octave, version 8.3.0' copy='1993-2023 The Octave Project Developers.'
# ./bundle_octave_app: line 695: /usr/bin/python: No such file or directory
#
# TODO: version style controls for app name
# # valid: 'major', 'minor', 'patch', 'full', or 'none'
# appname_ver_style='minor'
# TODO: Also, spaces in app name instead of dashes?
set -o errexit
set -o nounset
set -o pipefail
if [[ "${TRACE-0}" == "1" ]]; then set -o xtrace; fi
# Function definitions
#
# (Scroll down to the very bottom to see the main script logic.)
# Utility functions and boilerplate
# A hacky version of realpath, because macOS didn't ship realpath until macOS 13, and we
# still want to run on older versions.
#
# This is the best/most suitable solution of all the stuff I found on SO here:
# https://stackoverflow.com/questions/3572030/bash-script-absolute-path-with-os-x
function oa_realpath() {
local file="$1"
python3 -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$file"
}
THIS_PROGRAM=$(basename $0)
THIS_DIR=$(oa_realpath $(dirname $0))
function info() { echo "$*"; }
function error() { echo >&2 "${THIS_PROGRAM}: ERROR: $*"; }
function warning() { echo >&2 "${THIS_PROGRAM}: Warning: $*"; }
function die() { error "$*"; exit 1; }
function verbose() { if is_verbose; then echo "$*"; fi; }
function is_dry_run() { if [[ $DRY_RUN == 'y' ]]; then return 0; else return 1; fi; }
function is_verbose() { if [[ $VERBOSE == 'y' ]]; then return 0; else return 1; fi; }
function wet() {
if is_dry_run; then
info "dry run: would run: $*"
else
verbose "running: $*"
"$@"
return $?
fi
}
function tic() { date +%s; }
function toc() { local t0="$1" t1; t1=$(tic); echo $((t1 - t0)); }
function s2mmss() { printf "%02d:%02d" "$(($1/60))" "$(($1%60))"; }
function say_toc() {
local label="$1" t0="$2"
te=$(toc "$t0")
info $(printf "Elapsed time: %s: %s" "$label" $(s2mmss "$te"))
}
function timeit() {
local label="${1:-action}"; shift
if [[ $# == 0 ]]; then
die "BUG: timeit() called with too few arguments"
fi
t0=$(tic)
"$@"
say_toc "$label" "$t0"
}
function timeit_fcn() { local fcn="$1"; timeit "$fcn" "$@"; }
# Special functions to turn errexit on/off, to support the special no-errexit debugging
# control.
function disable_errexit() {
set +o errexit
}
function enable_errexit() {
if [[ $EXIT_ON_ERROR == 'n' ]]; then
# Leave it disabled bc a debugging option asked for that.
# In fact, explicitly disable it, because it starts off enabled due to the
# code-safety settings at the very top of this script; this will get it right at the
# start of main()
set +o errexit
else
set -o errexit
fi
}
# Octapp-specific functions
function logged() {
"$@" 2>&1 | tee -a "$MY_LOG_FILE"
}
function is_do_sudo() {
if [[ $USE_SUDO == 'y' ]]; then
return 0;
else
return 1;
fi;
}
function detect_tool_dependencies() {
# Check that dependencies of this tool (not of Octave.app) are available. We use
# system-installed ones instead of installing them in to Octave.app to avoid bloating
# the app build, and because some are needed before the app is built at all.
#
# Sets: CREATEDMG_HOME
if ! which python3 >/dev/null; then
die "Could not find python3. ${THIS_PROGRAM} requires Python 3.x, and the command must be 'python3' and not plain 'python'."
fi
if [[ -n "${OCTAPP_CREATEDMG_HOME:-}" ]]; then
CREATEDMG_HOME="$OCTAPP_CREATEDMG_HOME"
else
CREATEDMG_HOME="$HOME/repos/create-dmg"
fi
if [[ ! -f "${CREATEDMG_HOME}/create-dmg" ]]; then
die "No create-dmg found at ${CREATEDMG_HOME}.
Please clone or install the create-dmg repo there, or set the OCTAPP_CREATEDMG_HOME
environment variable to point to your create-dmg repo or installation.
This needs to be the current create-dmg/create-dmg or apjanke/create-dmg repo, not the old
octave-app/create-dmg repo.
"
fi
}
function usage() {
cat <<EOHELP
${THIS_PROGRAM}
${THIS_PROGRAM} [OPTION [ARG]] ...
${THIS_PROGRAM} brew <brew_args...>
Build and package an Octave.app application bundle for macOS.
The '${THIS_PROGRAM} brew ...' calling form runs an arbitrary 'brew' command in the
Homebrew installation inside the staged app bundle.
All other calling forms do regular build operations.
Several options are supported:
Packaging options:
--pre-release <name>
Declare this to be a prerelease of a given name, using that as the build suffix.
E.g., 'alpha1' or 'beta1'.
--update-release <name>
Declare this to be an update of a given name, using that as the build suffix.
E.g., 'u1' or 'u2'.
-u, --build-suffix <suffix>
Set a build version suffix. Used to distinguish revisions and updates
to Octave.app's packaging, like 'u1' or 'u2'. This can be used for beta builds, too,
like '-u beta1'. This is an old option, and the newer --pre-release or
--update-release option should generally be used instead. [$BUILD_SUFFIX]
--sign
Code-sign the completed app bundle. (Probably not working.)
Octave build options:
-a, --variant <variant>
Variant of Octave.app to build. Affects both formula variant and release variant.
--formula-variant <variant>
Formula variant to use for the octave formula. [$FORMULA_VARIANT]
--release-variant <variant>
Release variant to put in the release and file names. [$RELEASE_VARIANT]
-V, --octave-version <version>
Version of Octave formula to build, affecting formula choice.
-d, --build-devel
Build the latest Octave development snapshot instead of a released version.
--tap-branch <branch>
Use an alternate branch instead of main in the homebrew-octave-app tap.
-S, --all-from-source
Build all packages from source instead of using (relocatable) bottles. By default,
only packages known to have spuriously-relocatable bottles are explicitly built
from source. But that "known spurious" list may well be incomplete. This option
guarantees that all packages are built from source.
Debugging options:
-h, -?, --help
Display this help text.
-s, --step <step>
Run a single step of the build process (for debugging use).
-v, --verbose
Verbose output, and list state of all options.
-x, --trace
Trace program execution (even more detail than --verbose). You can also set the
TRACE=1 environment variable to get the same effect.
-Y, --dry-run
Compute values but do not actually build. (Experimental. May fail in various ways.)
--show-options
Just show the effective options and resulting choices and exit.
--debug-no-errexit
Do not exit on errors. This is a low-level debugging option only for developers.
EOHELP
}
function show_tool_ver_info() {
local oab_git_sha oab_git_status oab_gitstate_extra oab_ver
local oab_git_info
local xcode_version xcode_clt_version clang_version_str latex_version_str
oab_git_info=$(read_git_state_info "$OAB_HOME")
xcode_version=$(xcodebuild -version | sed -En 's/Xcode[[:space:]]+([0-9.]+)/\1/p')
clang_version_str=$(clang --version | head -1)
xcode_clt_version=$(pkgutil --pkg-info=com.apple.pkg.CLTools_Executables | head -2 | tail -1 | sed 's/.* //')
latex_version_str=$(latex -v | head -1)
cat <<EOS
octave-app-bundler ${OAB_VERSION} (from git ${oab_git_info})
macOS ${MACHINE_MACOS_VER}, arch ${MACHINE_ARCH} (${MACHINE_ARCH_DESCR})
Xcode ${xcode_version}, Xcode CLT ${xcode_clt_version}, clang ${clang_version_str}
LaTeX: ${latex_version_str}
EOS
}
function show_options() {
local hostname createdmg_ver_info createdmg_git_info
local createdmg_git_extra=''
hostname=$(hostname)
createdmg_ver_info=$("${CREATEDMG_HOME}/create-dmg" --version)
if [[ -d "${CREATEDMG_HOME}/.git" ]]; then
createdmg_git_info=$(read_git_state_info "$CREATEDMG_HOME")
createdmg_git_extra=" (git ${createdmg_git_info})"
fi
cat <<EOS
${THIS_PROGRAM} run at $(date) on ${hostname}
$(show_tool_ver_info)
${createdmg_ver_info}${createdmg_git_extra}
Options:
formula_variant = ${FORMULA_VARIANT}
release_variant = ${RELEASE_VARIANT}
build_suffix = ${BUILD_SUFFIX} (prerelease = ${PRE_RELEASE}, update_release = ${UPDATE_RELEASE})
octave_formula_version = ${OCTAVE_FORMULA_VERSION}
octapp_tap_branch = ${OCTAPP_TAP_BRANCH}
build_devel = ${DO_BUILD_DEVEL}
build_dmg = ${DO_BUILD_DMG}
use_gcc = ${USE_GCC}
all_from_source = ${BUILD_ALL_FROM_SOURCE}
Values:
octapp_release = ${OCTAPP_RELEASE}
app_name = ${APP_NAME}
octave_version = ${OCTAVE_VERSION}
version_name = ${VERSION_NAME}
octave_version_label = ${OCTAVE_VERSION_LABEL}
octapp_version_label = ${OCTAPP_VERSION_LABEL}
dist_archive_name = ${DIST_ARCHIVE_NAME}
dmg_file = ${DMG_FILE}
dmg_volume_name = ${DMG_VOLUME_NAME}
octave_formula = ${OCTAVE_FORMULA}
build_label = ${BUILD_LABEL}
Paths:
octave_app_bundler_home = ${OAB_HOME}
createdmg_home = ${CREATEDMG_HOME}
build_dir = ${BUILD_DIR}
build_log_dir = ${BUILD_LOG_DIR}
app_build = ${APP_BUILD}
EOS
}
# The OCTAVE_VERSION is a version number of a main Octave release, in "x.y.z" form. The
# OCTAVE_FORMULA_VERSION is an Octave.app-specific thing which may include suffixes like "_u1"
# to indicate different Octave.app builds or definitions that use the same Octave release.
#
# Pre: Needs OCTAVE_FORMULA_BASE, DEFAULT_OCTAVE_FORMULA_VERSION,VARIANT
# Post: Sets OCTAVE_FORMULA_VERSION
function pick_default_octave_version() {
# Bash 3 does not have associative arrays, so we need to do the version lookup with a
# function. (MacOS is stuck on Bash 3.x for GPL licensing reasons.)
if [[ "$OCTAVE_FORMULA_BASE" == "octave-octapp" ]]; then
OCTAVE_FORMULA_VERSION="$DEFAULT_OCTAVE_FORMULA_VERSION"
elif [[ "$OCTAVE_FORMULA_BASE" == "octave-unversioned" ]]; then
OCTAVE_FORMULA_VERSION=""
else
die "Formula variant '${FORMULA_VARIANT}' (formula base ${OCTAVE_FORMULA_BASE}) has no default version defined. You must supply one."
fi
}
# Picks names, populating various global variables
function pick_names_for_things() {
local app_base_name app_base_name_base
if [[ -z "$FORMULA_VARIANT" ]]; then
OCTAVE_FORMULA_BASE="octave-octapp"
else
OCTAVE_FORMULA_BASE="octave-${FORMULA_VARIANT}"
fi
if [[ -z "$RELEASE_VARIANT" ]]; then
app_base_name_base="octave"
else
app_base_name_base="${OCTAVE_FORMULA_BASE}"
fi
app_base_name="$(tr '[:lower:]' '[:upper:]' <<< ${app_base_name_base:0:1})${app_base_name_base:1}"
# Pick default version for variant
if [[ -z "${OCTAVE_FORMULA_VERSION:-}" ]]; then
pick_default_octave_version
fi
OCTAVE_VERSION="${OCTAVE_FORMULA_VERSION//_[[:digit:]]/}"
OCTAVE_VERSION_LABEL=$(echo "$OCTAVE_VERSION" | sed -e 's/\.0$//')
if [[ -z "$RELEASE_VARIANT" ]]; then
VERSION_NAME="${OCTAVE_VERSION_LABEL}"
else
VERSION_NAME="${OCTAVE_VERSION_LABEL}-${RELEASE_VARIANT}"
fi
if [[ -z "$OCTAVE_FORMULA_VERSION" ]]; then
OCTAVE_FORMULA="${OCTAVE_FORMULA_BASE}"
else
OCTAVE_FORMULA="${OCTAVE_FORMULA_BASE}@${OCTAVE_FORMULA_VERSION}"
fi
# OCTAPP_VERSION_LABEL is exactly the <ver> in "Octave-<ver>.app" for the app bundle
# file name. By comparison, OCTAPP_RELEASE_LABEL *always* includes the build suffix
# if there is one, and is used for downloads like the DMG; it's the "container" or
# publishing name. A single version can have multiple releases. And a single release
# can have multiple builds or artifacts, like the AS an Intel variant build/DMGs.
if [[ "$DO_BUILD_SUFFIX_IN_APP_NAME" = y ]]; then
if [[ -z "$BUILD_SUFFIX" ]]; then
OCTAPP_VERSION_LABEL="${OCTAVE_VERSION_LABEL}"
else
OCTAPP_VERSION_LABEL="${OCTAVE_VERSION_LABEL}_${BUILD_SUFFIX}"
fi
else
OCTAPP_VERSION_LABEL="${OCTAVE_VERSION_LABEL}"
fi
# An only-locally-meaningful label to distinguish build runs.
BUILD_LABEL="build_${OCTAPP_VERSION_LABEL}_${BUILD_TIMESTAMP}_${HOST_SHORT}"
BUILD_DIR="${BUILDS_DIR}/${BUILD_LABEL}"
BUILD_LOG_DIR="${BUILD_DIR}/logs"
BUILD_WORK_DIR="${BUILD_DIR}/work"
APP_NAME="${app_base_name}-${OCTAPP_VERSION_LABEL}"
if [[ -z "$BUILD_SUFFIX" ]]; then
OCTAPP_RELEASE="${OCTAVE_VERSION_LABEL}"
else
OCTAPP_RELEASE="${OCTAVE_VERSION_LABEL}_${BUILD_SUFFIX}"
fi
local dist_arch_extra=''
if [[ $MACHINE_ARCH == 'x86_64' ]]; then
dist_arch_extra="-Intel"
fi
DIST_ARCHIVE_NAME="${app_base_name}-${OCTAPP_RELEASE}${dist_arch_extra}"
DMG_FILE="${DIST_ARCHIVE_NAME}.dmg"
DMG_VOLUME_NAME="${app_base_name} ${OCTAPP_RELEASE}"
}
function pick_paths_for_things() {
# Path vars that do depend on chosen names and/or detected dependencies
INSTALL_DIR="/Applications/${APP_NAME}.app"
INSTALL_BUILT="/Applications/${APP_NAME}-BUILT.app"
INSTALL_DIR_UNSTAGED="${INSTALL_DIR}-UNSTAGED"
INSTALL_USR="${INSTALL_DIR}/Contents/Resources/usr"
INSTALL_OCTAPP_META="${INSTALL_DIR}/Contents/Resources/octapp-meta"
INSTALL_OCTAPP_VERS_INFO_FILE="${INSTALL_OCTAPP_META}/build-versions.txt"
INSTALL_BUILT_USR="${INSTALL_BUILT}/Contents/Resources/usr"
brew="${INSTALL_USR}/bin/brew"
APP_BUILD="${BUILD_DIR}/${APP_NAME}.app"
APP_BUILD_USR="${APP_BUILD}/Contents/Resources/usr"
}
# "Staging the app build" means to get the working build in to the final install location
# at /Applications/Octave-<blah>.app. Might be made fresh, or moved from -BUILT, or
function stage_app_build() {
info "Staging app build..."
if [[ -d "$INSTALL_DIR" ]]; then
# Reuse already-staged app build
if [[ -f "$INSTALL_DIR/STAGING" ]]; then
info "Looks like $APP_NAME is already staged at $INSTALL_DIR; re-using in place"
else
die "$INSTALL_DIR exists, but is not a staged build. Please move ${INSTALL_DIR} out of the way before running ${THIS_PROGRAM}."
fi
elif [[ -d "$INSTALL_BUILT" ]]; then
# Move existing staged app build back to staging location
info "Re-staging existing build dir from $INSTALL_BUILT"
mv "$INSTALL_BUILT" "$INSTALL_DIR"
else
# Create new staged app build
info "Creating new staged app build at $INSTALL_DIR"
# mkdir -p "$INSTALL_DIR"
osacompile -o "$INSTALL_DIR" -e " "
cat <<EOS > "$INSTALL_DIR/STAGING"
This directory is not a real app!
This is a staged in-progress build created by octave-app-bundler, the build tool for Octave.app.
It cannot be used as a regular app.
If you found this at ${INSTALL_DIR}, then there was probably a failed or incomplete build, and this
directory needs to be manually deleted or moved aside to clean things up.
For more info about Octave.app and octave-app-bundler, see:
https://github.com/octave-app/octave-app-bundler
EOS
fi
}
function require_staged_app_build() {
local err_msg
if [[ ! -f "${INSTALL_DIR}/STAGING" ]]; then
err_msg="This step requires a staged app build, but there is no app build staged at ${INSTALL_DIR}."
if [[ -n "$BUILD_STEP" ]]; then
err_msg="${err_msg}\nPlease run '${THIS_PROGRAM} -s stage' to stage an app build and then retry."
fi
die "$err_msg"
fi
}
function require_built_app() {
if [[ ! -d "$INSTALL_BUILT" ]]; then
die "No built app found at ${INSTALL_BUILT}. You may need to re-run the build."
fi
}
function read_git_head_commit() {
local repo_path="$1" commit_sha
commit_sha=$(git -C "$repo_path" rev-parse --short HEAD)
echo "$commit_sha"
}
function read_git_state_info() {
# Get a human-readable summary of a git repo's state
local repo_dir="$1"
local commit_sha git_status git_branch info
commit_sha=$(git -C "$repo_dir" rev-parse --short HEAD)
git_status=$(git -C "$repo_dir" status --porcelain)
git_branch=$(git -C "$repo_dir" branch --show-current)
info="commit ${commit_sha}"
if [[ -n "$git_status" ]]; then
info="${info}+"
fi
if [[ "$git_branch" != 'main' ]]; then
info="${info}, branch ${git_branch}"
fi
echo "$info"
}
function setup_staged_homebrew() {
local install_type
info "Setting up Homebrew in the staged build..."
require_staged_app_build
# Check if we need to do a full install or update existing install
if [[ -e "${INSTALL_USR}/bin/brew" ]]; then
install_type='update'
else
install_type='full'
fi
if [[ "$install_type" == "update" ]]; then
# Update homebrew
info "Incrementally building in existing Homebrew installation in ${INSTALL_USR}"
else
# Install homebrew
info "Creating new Homebrew installation in ${INSTALL_USR}"
mkdir -p "${INSTALL_USR}/bin" "${INSTALL_USR}/var" "${INSTALL_USR}/lib"
git clone 'https://github.com/Homebrew/brew' "${INSTALL_USR}/Homebrew"
ln -s "${INSTALL_USR}/Homebrew/bin/brew" "${INSTALL_USR}/bin/brew"
if [[ -n "$BREW_REMOTE_BRANCH" ]]; then
(
info "Checking out Homebrew branch ${BREW_REMOTE_BRANCH} from remote ${BREW_REMOTE_URL}"
"$brew" update
cd "${INSTALL_USR}/Homebrew"
git remote add "$BREW_REMOTE_URL" "$BREW_REMOTE_URL"
git fetch --all
git checkout "$BREW_REMOTE_BRANCH"
)
fi
"$brew" tap octave-app/octave-app
fi
# Switch to non-default tap branch if requested
# TODO: support new AS dir layout?
local octapp_tap_dir="${INSTALL_USR}/Homebrew/Library/Taps/octave-app/homebrew-octave-app"
local octapp_tap_ver octapp_tap_commit octapp_tap_commit_msg
(
cd "${octapp_tap_dir}"
info "Working on octapp tap at: $(pwd)"
if [[ -n "$OCTAPP_TAP_BRANCH" ]]; then
info "Checking out octave-app tap branch ${OCTAPP_TAP_BRANCH}"
git fetch
git switch "$OCTAPP_TAP_BRANCH"
fi
)
octapp_tap_ver=$(read_octapp_tool_version "$octapp_tap_dir" "octave-app tap")
octapp_tap_commit=$(git -C "$octapp_tap_dir" rev-parse --short HEAD)
octapp_tap_commit_msg=$(git -C "$octapp_tap_dir" log -1)
info "octave-app tap: version: ${octapp_tap_ver} commit: ${octapp_tap_commit}"
# Get the current commit for the core Homebrew tap.
# HACK: Can't just read it from disk, because brew no longer uses a local clone. So
# clone it ourselves, and assume we get the same version.
local tempdir core_tap_clone core_tap_commit core_tap_commit_msg
tempdir=$(mktemp -d -t oab_brew_core-XXXXXXX)
core_tap_clone="${tempdir}/homebrew-core"
git clone --depth 12 "https://github.com/Homebrew/homebrew-core" "$core_tap_clone"
core_tap_commit=$(read_git_head_commit "$core_tap_clone")
core_tap_commit_msg=$(git -C "$core_tap_clone" log -1)
rm -rf "$tempdir"
mkdir -p "${INSTALL_OCTAPP_META}"
cat >>"${INSTALL_OCTAPP_META}/tap-info.txt" <<EOS
Homebrew:
$("$brew" --version)
homebrew-octave-app tap:
version: ${octapp_tap_ver}
commit: ${octapp_tap_commit}
commit msg:
${octapp_tap_commit_msg}
homebrew-core tap:
commit: ${core_tap_commit}
commit msg:
${core_tap_commit_msg}
EOS
# Force conservative architecture builds in brew's ENV
# TODO: Can this be replaced with an env var or the like?
local app_brew_lib_dir="${INSTALL_USR}/Homebrew/Library/Homebrew"
sed -E -i '' "s/ARGV.build_bottle./true/g" \
"${app_brew_lib_dir}/extend/ENV/super.rb" \
"${app_brew_lib_dir}/extend/ENV/std.rb"
}
function read_octapp_tool_version() {
# Read version from an octapp tool's repo's meta file.
# Works on both octapp-bundler and our Homebrew taps, or anything else that has a
# "version" field in its meta.json.
local tool_dir="$1" tool_label="${2:-octapp tool}"
local tool_meta_file="${tool_dir}/meta.json"
if [[ ! -f "$tool_meta_file" ]]; then
warning "No ${tool_label} meta file found; cannot detect version. Expected at: ${tool_meta_file}"
echo "???"
return
fi
local tool_ver=$(cat "$tool_meta_file" | sed -E -n -e 's/[[:space:]]*"version"[[:space:]]*:[[:space:]]*"(.*)"\s*$/\1/p')
if [[ -z "$tool_ver" ]]; then
warning "Failed parsing ${tool_label} version from meta.json file. File: ${tool_meta_file}"
echo "???"
return
fi
echo "$tool_ver"
}
function install_special_build_deps() {
info "Installing special required build-time packages"
# svn is needed to fetch the netpbm source
# TODO: Could this be fixed by adding a build-time dependency on svn to the netpbm formula?
# TODO: Better yet, can we just require a system-level svn? Doesn't seem to work when done
# in a separate Homebrew installation (maybe due to brew's environment isolation), so for
# now, install it in Octave.app and let it bloat up. If we have to do it this way, then we
# should maybe add subversion and its exclusive deps to the list of packages to prune later.
# TODO: The HOMEBREW_SVN env var looks like a way to do this.
if brew deps --include-build "$OCTAVE_FORMULA" | grep netpbm | grep -v netpbm-octapp &>/dev/null; then
info "This build requires subversion bc it uses regular netpbm. Installing subversion..."
wet "$brew" install subversion
else
info "This build does not require subversion bc it has no dep on regular netpbm. Not installing subversion."
fi
}
function build_octave_in_staged_app() {
local build_opts nonreloc_deps brew_install_opts brew_inst_exit brew_inst_octapp_exit
info "Building Octave.app..."
require_staged_app_build
if [[ -e "$INSTALL_BUILT" ]]; then
die "A built app aleady exists unstaged at $INSTALL_BUILT. Needs manual cleanup."
fi
# Fortran: set FC to point to gfortran from the brewed gcc inside the built Octave.app
export FC="${INSTALL_USR}/bin/gfortran"
# Scientific libraries may need GCC 6, if using GCC
# TODO: Is this still needed in 2024? Why exactly did we do it in the first place?
# TODO: Document exactly which libraries need this.
if [[ "$USE_GCC" == 'y' ]]; then
export HOMEBREW_CC=gcc-6
export HOMEBREW_CXX=g++-6
fi
install_special_build_deps
# Build octave (and its deps)
build_opts=""
brew_install_opts=""
if [[ "$VERBOSE" == "y" ]]; then
brew_install_opts="$brew_install_opts --verbose"
fi
if [[ "$DO_BUILD_DEVEL" == "y" ]]; then
build_opts="$build_opts --HEAD"
fi
# Force building selected dependencies from source
if [[ $BUILD_ALL_FROM_SOURCE = 'y' ]]; then
info "Forcing build-from-source of all deps, because --all-from-source was specified"
local all_deps=$("$brew" deps --include-build "$OCTAVE_FORMULA" | tr '\n' ' ')
if ! wet "$brew" install $brew_install_opts --build-from-source $all_deps; then
brew_inst_exit=$?
die "brew install <all deps> --build-from-source failed: returned failure (exit status ${brew_inst_exit}); aborted."
fi
else
# Force building formulae with non-relocatable bottles, including deps, from source, to
# avoid picking up bottles that are marked as relocatable but don't actually work in
# alternate locations, like qt 6
local dep_temp_file="${BUILD_WORK_DIR}/octapp_nonreloc_deps.txt"
"$brew" deps --include-build "$OCTAVE_FORMULA" | sort | uniq > "$dep_temp_file"
nonreloc_deps=$(comm -1 -2 "$dep_temp_file" "${THIS_DIR}/nonreloc_bottles.txt" | tr '\n' ' ')
if [[ -n "$nonreloc_deps" ]]; then
info "Forcing build-from-source of deps with spurious relocatable bottles: ${nonreloc_deps}"
if ! wet "$brew" install $brew_install_opts --build-from-source $nonreloc_deps; then
brew_inst_exit=$?
info "brew install <non-reloc deps for octapp> returned failure (exit status ${brew_inst_exit}). But brew just does that sometimes. Ignoring."
fi
else
info "No deps with spurious relocatable bottles to handle"
fi
fi
if wet "$brew" install $brew_install_opts --build-from-source "$OCTAVE_FORMULA" $build_opts; then
info "brew install <octapp> returned success exit status"
else
brew_inst_octapp_exit=$?
info "brew install <octapp> exit status: $brew_inst_octapp_exit"
if [[ $brew_inst_octapp_exit == 1 ]]; then
if brew_is_installed "$OCTAVE_FORMULA"; then
info "brew install <octapp> had non-zero exit status ${brew_inst_octapp_exit} but looks successful, because ${OCTAVE_FORMULA} is in 'brew list'. Maybe a keg-only side effect? Treating as successful and proceeding."
else
die "brew install <octapp> failed: returned exit status 1, and formula ${OCTAVE_FORMULA} was not installed (not present in 'brew list')"
fi
else
die "brew install <octapp> returned failure, non-1 exit status ($brew_inst_octapp_exit); aborted."
fi
fi
# HACK: remove large build-time-only dependencies to reduce app size.
# This is done before moving to -BUILT because brew needs to run from
# the original built location.
local big_pkg
info "Removing big build-time-only packages"
for big_pkg in "${BIG_BUILD_ONLY_PKGS[@]}"; do
info "Checking for big build-time-only package: $big_pkg"
if brew_is_installed "$big_pkg"; then
info "Removing big build-time-only package: $big_pkg"
wet "$brew" rm "$big_pkg"
fi
done
info "Doing brew cleanup after successful app build"
wet "$brew" cleanup
}
function brew_is_installed() {
local formula="$1"
"$brew" list --formula | grep -x "$formula" &>/dev/null
}
function setup_brew_env() {
export HOMEBREW_NO_AUTO_UPDATE=1
export HOMEBREW_NO_ENV_HINTS=1
#TODO: Use HOMEBREW_SVN to support svn downloads without in-octapp svn install?
}
# Moves the completed build to a -BUILT copy. At that point it's "pristine" and should
# (mostly) no longer be modified. As of 2024-01-18, I think this is redundant with unstage_*.
function move_build_to_stash() {
if [[ -e "$INSTALL_BUILT" ]]; then
die "Cannot stash built app. A build aleady exists at $INSTALL_BUILT. Needs manual cleanup."
fi
wet mv "$INSTALL_DIR" "$INSTALL_BUILT"
info "Captured pristine build to ${INSTALL_BUILT} (from $INSTALL_DIR)"
}
# Unstages the completed/staged build to a -BUILD location. As of 2024-01-18, this is
# working exactly like move_build_to_stash, and they can probably be unified.
function unstage_app_build() {
info "Unstaging app build..."
if [[ ! -f "${INSTALL_DIR}/STAGING" ]]; then
die "${INSTALL_DIR} does not look like a staged build from this tool. (Maybe it's a final app from the installer?) Not moving it."
fi
if [[ -e "$INSTALL_BUILT" ]]; then
die "There is already an unstaged app build at ${INSTALL_BUILT}. Cannot un-stage."
fi
wet mv "$INSTALL_DIR" "$INSTALL_BUILT"
info "Unstaged app build to ${INSTALL_BUILT}"
}
function capture_build_logs() {
# Capture build logs from unstaged app and other places.
# This is run after capturing the build to the unstaged location
# This no longer needs to capture the user Homebrew logs, because we now redirect brew
# to log directly to our build dir instead of the default user logs dir.
local brew_logs_dir octave_test_log
info "Capturing build logs..."
wet mkdir -p "$BUILD_LOG_DIR"
octave_test_log="${INSTALL_BUILT}/opt/${OCTAVE_FORMULA}/make-check.log"
if [[ -e "$octave_test_log" ]]; then
info "Captured make-check.log from octave build"
wet cp "$octave_test_log" "$BUILD_LOG_DIR"
else
info "No make-check.log file found in octave build"
fi
info "Captured build logs to ${BUILD_LOG_DIR}"
}
function capture_homebrew_logs() {
# Capture the user Homebrew logs.
# This is not needed in our current setup that does log redirection, but keeping the
# code around for reference for a bit.
local target_dir="$1"
local brew_logs_dir="${HOME}/Library/Logs/Homebrew"
if [[ -d "$brew_logs_dir" ]]; then
wet cp -pR "$brew_logs_dir" "$target_dir"
fi
}
function record_build_versions_and_meta() {
# Record metadata about tool and package versions to files in the build.
# Capture the version info from built binaries to a simple file for use later
# Get versions dynamically from the built program. Stash them in a file inside
# the app build. That modifies the app build, and is the only modification we do
# before capturing it for further munging.
local octave oct_ver oct_ver_string oct_copyright octapp_ver_file formulae_vers_file
local gs_ver gs_opt gs_share octapp_meta_dir
# Keep this in sync with the corresponding code below!
# Just on a hunch that dirs right under the app bundle root interfere with codesigning,
# moved this under Contents/Resources as of 8.4.0.
octapp_meta_dir="${INSTALL_OCTAPP_META}"
octapp_ver_sh_file="${octapp_meta_dir}/octapp-ver-info.sh"
formulae_vers_file="${octapp_meta_dir}/octapp-formulae-vers.txt"
octave="${INSTALL_USR}/opt/${OCTAVE_FORMULA}/bin/octave"
# x.y.z semver-ish version string
oct_ver_string=$("$octave" --version | sed -n 1p)
oct_ver=$(echo "$oct_ver_string" | grep -o '\d\..*$' )
oct_copyright=$("$octave" --version | sed -n 2p | cut -c 15- )
gs_ver=$("${INSTALL_USR}/bin/gs" --version)
gs_opt=$("$brew" --prefix "ghostscript")
gs_share="${gs_opt}/share/ghostscript"
mkdir -p "$octapp_meta_dir"
"$brew" config --verbose > "${octapp_meta_dir}/brew-config.txt"
"$brew" info --installed --json > "${octapp_meta_dir}/brew-info-installed.json"
"$brew" list --full-name > "${octapp_meta_dir}/brew-list-fullname.txt"
"$brew" list --versions > "${octapp_meta_dir}/brew-list-versions.txt"
"$brew" octave-app-list-formulae > "$formulae_vers_file"
cat >"$octapp_ver_sh_file" <<EOS
oct_ver='$oct_ver'
oct_ver_string='$oct_ver_string'
oct_copyright='$oct_copyright'
gs_ver='$gs_ver'
gs_opt='$gs_opt'
gs_share='$gs_share'
EOS
info "Saved version info under ${octapp_meta_dir}"
# Tool versions
cat >"${octapp_meta_dir}/tool-versions.txt" <<EOS
octave-app-bundler tools and environment:
$(show_tool_ver_info)
EOS
cat "${octapp_meta_dir}/tap-info.txt" >> "${octapp_meta_dir}/tool-versions.txt"
}
function munge_build() {
local octave oct_ver oct_ver_string oct_copyright
local octapp_ver_file formulae_vers_file octapp_meta_dir
local gs_ver gs_opt gs_share
local oct_keg vers_code oct_mcode oct_site
info "Munging build..."
if [[ $DRY_RUN = 'y' ]]; then
info "dry run: would munge build"
return
fi
require_built_app
# Make a local copy of the app for munging, clobbering any existing copy
if [[ -e "$APP_BUILD" ]]; then
info "Deleting old munged app at ${APP_BUILD}"
chmod -R u+w "$APP_BUILD"
rm -rf "$APP_BUILD"
fi
info "Grabbing copy of build from ${INSTALL_BUILT} to ${APP_BUILD}"
mkdir -p "$BUILD_DIR"
cp -pR "$INSTALL_BUILT" "$APP_BUILD"
# Get ver info from our custom data file
# Keep this in sync with the corresponding code in the build step!
octapp_usr="${APP_BUILD}/Contents/Resources/usr"
octapp_meta_dir="${APP_BUILD}/Contents/Resources/octapp-meta"
octapp_ver_file="${octapp_meta_dir}/octapp-ver-info.sh"
formulae_vers_file="${octapp_meta_dir}/octapp-formulae-vers.txt"
vers_code=$(cat "${octapp_ver_file}")
eval "$vers_code"
info "Octave versions: ver='${oct_ver}' ver_string='${oct_ver_string}' copyright='${oct_copyright}'"
# Extract a copy of metadata go logs for our records
cp -pR "$octapp_meta_dir" "${BUILD_LOG_DIR}"
# Munge the app
info "Munging app in ${APP_BUILD}..."
# Add a bare "octave" link in the main bin, so when the octapp "env" is loaded,
# an unqualified "octave" command runs that.
# TODO: Move this to the build step?
( cd "${octapp_usr}/bin"; ln -s "$OCTAVE_FORMULA" octave )
# Have app use its internal font cache instead of global one
/usr/bin/sed -i '' 's/\/Applications.*fontconfig/~\/.cache\/fontconfig/g' \
"${APP_BUILD_USR}/etc/fonts/fonts.conf"
# App-specific startup configuration
oct_keg="${APP_BUILD_USR}/Cellar/${OCTAVE_FORMULA}/${OCTAVE_FORMULA_VERSION}"
oct_mcode="${oct_keg}/share/octave/${OCTAVE_VERSION}/m"
oct_site="${oct_keg}/share/octave/site"
cat >>"${oct_site}/m/startup/octaverc" <<EOS
% Octave.app special configuration
% Use a distinct version-specific package dir outside of the app bundle.
%
% This avoids changing the app bundle in a way that could break code signing.
% This can also avoid crashes with compiled packages. (Because some compiled
% oct-files have linkage dependency on the exact path to the Octave
% installation, and can't be shared between multiple Octave installations on
% the same machine.)
default_pkg_prefix = pkg ("prefix");
octave_app_pkg_prefix = [getenv("HOME") "/Library/Application Support/Octave.app/${OCTAPP_VERSION_LABEL}/pkg"];
% Create the directory ourselves to avoid a warning in the console
if !isfolder (octave_app_pkg_prefix)
mkdir (octave_app_pkg_prefix);
endif
pkg ("prefix", octave_app_pkg_prefix, octave_app_pkg_prefix);
pkg ("local_list", [octave_app_pkg_prefix "/octave_packages"]);
clear default_pkg_prefix octave_app_pkg_prefix
% End Octave.app special configuration
EOS
# Drop a build version indicator
echo "$OCTAPP_RELEASE" > "${oct_site}/Octave.app-RELEASE.txt"
# Add our custom octapp functions
cp -R Mcode/* "${oct_site}/m"
# Expose the original ver.m, which our Mcode masks, as octapp.ver_pristine
mkdir -p "${oct_site}/m/+octapp"
cat "${oct_mcode}/miscellaneous/ver.m" | sed -e 's/function v = ver /function v = ver_pristine /' \
> "${oct_site}/m/+octapp/ver_pristine.m"
# AppleScript app launcher script
local launch_script_tmp="${BUILD_WORK_DIR}/octapp-launcher.applescript"
read -r -d '' program_launch_code <<EOS || true
set cmd to env_setup_cmd() & run_octave_gui()
do shell script cmd
EOS
cat <<EOSCRIPT >> "$launch_script_tmp"
on env_setup_cmd()
export_lang() & export_gs_options() & export_fc() & export_path() & export_dyld()
end env_setup_cmd
on export_lang()
return "default_lang=\$(osascript -e 'user locale of (get system info)'); export LANG=\$default_lang.UTF-8;"
end export_lang
on export_gs_options()
return "export GS_OPTIONS=\\"-sICCProfilesDir=${gs_share}/${gs_ver}/iccprofiles/ -sGenericResourceDir=${gs_share}/${gs_ver}/Resource/ -sFontResourceDir=${gs_share}/${gs_ver}/Resource/Font\\";"
end export_gs_options
on export_path()
return "export PATH=\\"${INSTALL_USR}/bin/:\$PATH\\";"
end export_path
on export_dyld()
return "export DYLD_FALLBACK_LIBRARY_PATH=\\"${INSTALL_USR}/lib:/lib:/usr/lib\\";"
end export_dyld
on export_fc()
return "export FC=\\"${INSTALL_USR}/bin/gfortran\\"; export F77=\\"${INSTALL_USR}/bin/gfortran\\";"
end export_fc
on cache_fontconfig()
set fileTarget to (path to home folder as text) & ".cache:fontconfig"
try
fileTarget as alias
on error
display dialog "Font cache not found, so first plotting will be slow. Create font cache now?" with icon caution buttons {"Yes", "No"}
if button returned of result = "Yes" then
do shell script "${INSTALL_USR}/bin/fc-cache -frv;"
end if
end try
end cache_fontconfig
on run_octave_gui()
return "cd ~; clear; ${INSTALL_USR}/opt/${OCTAVE_FORMULA}/bin/octave -q --gui;"
end run_octave_gui
on run_octave_cli()
return "cd ~; clear; ${INSTALL_USR}/opt/${OCTAVE_FORMULA}/bin/octave -q; exit;"
end run_octave_cli
on run_octave_test_suite()
return "cd ~; clear; ${INSTALL_USR}/opt/${OCTAVE_FORMULA}/bin/octave --no-window-system --eval '__run_test_suite__' > ~/octave-app-fntest.log 2>&1; exit;"
end run_octave_test_suite
on run_octave_open(filename)
return "cd ~; clear; ${INSTALL_USR}/opt/${OCTAVE_FORMULA}/bin/octave -q --persist --eval \\"edit '" & filename & "'\\";"
end run_octave_open
on path_check()
if not (POSIX path of (path to me) starts with "${INSTALL_DIR}") then
display dialog "Please move Octave.app from the ${INSTALL_DIR} folder and run it from there." with icon stop with title "Error" buttons {"OK"}
error number -128
end if
end path_check
on open argv
path_check()
cache_fontconfig()
set filename to "\\"" & POSIX path of item 1 of argv & "\\""
set cmd to env_setup_cmd() & run_octave_open(filename)
do shell script cmd
end open
on run argv
path_check()
cache_fontconfig()
if argv contains "--run-test-suite" then
set cmd to env_setup_cmd() & run_octave_test_suite()
do shell script cmd