#!/bin/bash # # Build configuration utility for Serval DNA on Apple iOS. # # Copyright 2016-2017 Flinders University # # This script runs Serval DNA's main 'configure' script several times, once for # each different target iOS target, and saves the Makefile that is generated by # each run. It then creates a top-level Makefile that: # (1) invokes each of the saved Makefiles in turn to generate one static library # for each target, then # (2) combines all these libraries into a single "multi slice" static library # that can be imported into an Xcode project. # # This allows the iOS development cycle to be similarly rapid as for platforms # such as Linux or native Apple OS-X: # (1) a single 'configure' step adapts the Makefile(s) to the specific needs of # the target architecture(s), then # (2) the edit-make-test cycle can be repeated as often as desired: # (a) edit; make changes to source file(s) # (b) make; compile the changed source files into executable form using the # dependency rules in the Makefile(s), which avoids unnecessary # re-compilation, then re-build the app in Xcode, which links in the # new static library # (c) test; run the re-built app, either in an iOS simulator or by # side-loading into an iPhone # # Exit on error set -e usage() { echo "Usage: ${0##*/} [-f|--force] [iphone-version]" echo "Eg: ${0##*/} 10.3" } # Run the Serval DNA configure script once for each architecture, and save the # files produced by each run. main() { setup parse_command_line "$@" if [ -z "$ios_version" ]; then discover_sdk_versions fi check exec 5>&1 TARGETS=() if [ -n "$ios_version" ]; then configure "iphoneos$ios_version" arm64 /Library/Serval # 32-bit architectures are not supported in iOS 11 and later. if [ ${ios_version%%.*} -lt 11 ]; then configure "iphoneos$ios_version" armv7 /Library/Serval fi fi if [ -n "$sim_version" ]; then configure "iphonesimulator$sim_version" x86_64 /tmp/serval-dna # 32-bit architectures are not supported in iOS 11 and later. if [ ${sim_version%%.*} -lt 11 ]; then configure "iphonesimulator$sim_version" i386 /tmp/serval-dna fi fi create_makefile } # Trace and diagnostic functions. run() { echo + "$@" >&5 "$@" } usage_error() { echo "${0##*/}: $*" >&2 usage >&2 exit 1 } fatal() { echo "${0##*/}: $1" >&2 shift while [ $# -ne 0 ]; do echo "$1" >&2 shift done exit 1 } # Parse command-line options. parse_command_line() { c_api_library=libservaldaemon.a swift_api_library=libServalDNA.a swift_api_module=ServalDNA.swiftmodule ios_version= opt_force_configure=false opt_force_config_status=false log_display_line_count=10 while [ $# -ne 0 ]; do opt="$1" case "$opt" in -h | --help) usage exit 0 ;; -f|--force-configure) opt_force_configure=true ;; --force-config-status) opt_force_config_status=true ;; -*) usage_error "Unknown option: $opt" ;; *) break ;; esac shift done if [ $# -eq 1 ]; then ios_version="$1" shift elif [ $# -gt 1 ]; then usage_error "Spurious argument: $2" fi sim_version="$ios_version" } # Discover available SDK versions for iPhoneOS and iPhoneSimulator. discover_sdk_versions() { local sdk for sdk in $(xcodebuild -showsdks | sed -n -e 's/^.*-sdk \([^ ][^ ]*\)$/\1/p'); do case "$sdk" in iphoneos*) ios_version=${sdk#iphoneos};; iphonesimulator*) sim_version=${sdk#iphonesimulator};; esac done } # Work out the path of the directory containing this script relative to the # current working directory (SCRIPT_DIR), the path of the Serval DNA repository # root directory relative to the current working directory (SERVAL_DNA_DIR), and # the path of these two directories relative to each other. setup() { case "$0" in */?*/*) SCRIPT_DIR="${0%/*}"; SERVAL_DNA_DIR="${0%/?*/*}";; ./*) SCRIPT_DIR="."; SERVAL_DNA_DIR="..";; */*) SCRIPT_DIR="${0%/*}"; SERVAL_DNA_DIR=".";; *) SCRIPT_DIR="."; SERVAL_DNA_DIR="..";; esac SCRIPT_DIR_RELATIVE_TO_SERVAL_DNA_DIR="$(cd "$SCRIPT_DIR" >/dev/null && echo "${PWD##*/}")" SERVAL_DNA_DIR_RELATIVE_TO_SCRIPT_DIR=".." SERVAL_DNA_CONFIGURE="$SERVAL_DNA_DIR/configure" } # Ensure that the Serval DNA 'configure' script exists and is executable. check() { if [ ! -e "$SERVAL_DNA_CONFIGURE" ]; then fatal "missing script: $SERVAL_DNA_CONFIGURE" \ "Run 'autoreconf -f -i -I m4' then run me again." fi if [ ! -x "$SERVAL_DNA_CONFIGURE" ]; then fatal "script is not executable: $SERVAL_DNA_CONFIGURE" \ "Run 'autoreconf -f -i -I m4' then run me again." fi case "$("$SERVAL_DNA_CONFIGURE" --version)" in servald\ configure\ *) ;; *) fatal "malfunctioning script: $SERVAL_DNA_CONFIGURE" \ "Run 'autoreconf -f -i -I m4' then run me again." esac } configure() { local platform="${1?}" local arch="${2?}" local prefix="${3?}" # Convert from the Xcode architecture name to the autoconf cpu name, so it # can be passed in the --host option to the configure script. local cpu case "$arch" in arm64) cpu=aarch64;; *) cpu="$arch";; esac # A "target" is an architecture (aka, "slice" or "architecture", eg, armv7, # arm64, i386) and an Apple SDK (eg, iphoneos, iphonesimulator, macosx). local target="$arch-$platform" TARGETS+=("$arch-$platform") configure_script="$SERVAL_DNA_DIR_RELATIVE_TO_SCRIPT_DIR/configure" makefile_in="$SERVAL_DNA_DIR_RELATIVE_TO_SCRIPT_DIR/Makefile.in" # Path of the directory under which the files this script creates will be placed # relative to the directory containing this script. local target_build_dir="$SCRIPT_DIR/build/$target" target_build_dir="${target_build_dir#./}" script_dir_relative_to_target_build_dir="../.." if [ ! -d "$target_build_dir" ]; then run mkdir -p "$target_build_dir" fi # Note: the configure script created by autoconf places its output files in # the current working directory. if $opt_force_configure || $opt_force_config_status || ! [ -e "$target_build_dir/.configured" ] || [ "$target_build_dir/config.status" -ot "$configure_script" ] || [ "$target_build_dir/Makefile" -ot "$configure_script" ] || [ "$target_build_dir/Makefile" -ot "$makefile_in" ] || [ "$target_build_dir/Makefile" -ot "$target_build_dir/config.status" ] then run pushd "$target_build_dir" >/dev/null rm -f .configured configure_script="$script_dir_relative_to_target_build_dir/$configure_script" makefile_in="$script_dir_relative_to_target_build_dir/$makefile_in" if $opt_force_configure || ! [ -e ".configured" ] || [ "config.status" -ot "$configure_script" ] || [ "Makefile" -ot "$configure_script" ] then run rm -f config.status Makefile if ! run "$configure_script" \ --host="$cpu-apple-darwin" \ --enable-xcode-sdk="$platform" \ --prefix "$prefix" \ &>configure.out then fatal "configure failed; see $target_build_dir/configure.out for full log" \ "Last $log_display_line_count lines were:" \ "" \ "$(tail -n $log_display_line_count configure.out)" fi if [ ! -r config.status ]; then fatal "configure for $target did not produce config.status" fi if [ ! -r Makefile ]; then fatal "configure for $target did not produce Makefile" fi elif $opt_force_config_status || [ "Makefile" -ot "$makefile_in" ] || [ "Makefile" -ot "config.status" ] then run rm -f Makefile if ! run ./config.status &>config.status.out then fatal "./config.status failed; see $target_build_dir/config.status.out:" \ "$(cat config.status.out)" fi fi > .configured run popd >/dev/null fi } # Create a Serval DNA Makefile that creates an Apple framework bundle: # - generates a modulemap file for the bundle # - generates a Xcode config file # - invokes all the per-target iOS makefiles to create one static library for # each target slice (arch-sdk) # - merges all the static libraries into a single, multi-slice library # - generates an Info.plist XML file describing the bundle # - creates the correct directory structure and symbolic links for the bundle foreach_target() { local target local target_dir for target in "${TARGETS[@]}"; do arch="${target%%-*}" sdk="${target#-*}" target_dir="build/$target" target_var=$(echo -n "$target" | tr -C a-zA-z0-9_ _) eval "$@" done } create_makefile() { echo "Creating $SCRIPT_DIR/Makefile" cat >"$SCRIPT_DIR/Makefile" <\$@ \$(fw_capi_library):$(foreach_target echo '" \\"' \; \ echo -n '" $target_dir/$c_api_library"') mkdir -p \$(dir \$@) lipo -create -output \$@ \$^ \$(fw_capi_config_headers): \$(fw_capi_dstdir)/Headers/%: build/% mkdir -p \$(dir \$@) cp \$< \$@ \$(fw_capi_serval_headers): \$(fw_capi_dstdir)/Headers/%: \$(srcdir)/% mkdir -p \$(dir \$@) cp \$< \$@ \$(fw_capi_sqlite3_headers): \$(fw_capi_dstdir)/Headers/%: \$(srcdir)/\$(SQLITE3_AMALGAMATION)/% mkdir -p \$(dir \$@) cp \$< \$@ \$(fw_capi_modulemap):$(foreach_target echo '" \\"' \; \ echo -n '" install_${target_var}_sodium_headers"') \\ \$(srcdir)/headerfiles.mk Makefile mkdir -p \$(dir \$@) @{ \\ echo '/*' \\ echo 'module.modulemap for Serval DNA C API on Apple iOS' ;\\ echo 'Copyright 2017 Flinders University' ;\\ echo 'This file was auto-generated by \$(abspath Makefile)' ;\\ echo '\$(shell date)' ;\\ echo 'The naming and structure of this file must match ../modules.modulemap' ;\\ echo '*/' ;\\ echo 'framework module serval_dna {' ;\\ echo ' requires tls // thread-local storage' ;\\ echo ' explicit module lib {' ;\\ $(foreach_target echo '" echo '\'' header \"$target/config.h\"'\'' ;\\"') for header in \$(LIB_HDRS); do \\ echo " header \"\$\$header\"" ;\\ done ;\\ echo ' }' ;\\ echo ' explicit module daemon {' ;\\ echo ' export lib' ;\\ echo ' export sqlite' ;\\ echo ' export sodium' ;\\ for header in \$(PUBLIC_HDRS); do \\ echo " header \"\$\$header\"" ;\\ done ;\\ echo ' }' ;\\ echo ' explicit module sqlite {' ;\\ for header in \$(notdir \$(SQLITE3_HDRS)); do \\ echo " header \"\$\$header\"" ;\\ done ;\\ echo ' }' ;\\ echo ' explicit module sodium {' ;\\ $(foreach_target echo '" echo '\'' header \"$target/sodium.h\"'\'' ;\\"' \; \ echo '" (cd $target_dir/libsodium-dev/include/; find . -type f -print | sed -e '\''s|^\./||'\'' -e '\''s|.*| header \"$target/&\"|'\'' ) ;\\"') echo ' }' ;\\ echo '}' ;\\ echo 'module inttypes [system] [extern_c] {' ;\\ echo ' header "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/clang/include/inttypes.h"' ;\\ echo ' export *' ;\\ echo '}' ;\\ } >\$@ # The Swift API is in a framework that contains one Swift module file per # architecture. and a multi-slice (multi-architecture) static library file. .PHONY: fw_swiftapi fw_swiftapi: \$(fw_swiftapi_info_plist) \\ \$(fw_swiftapi_library) \\ $(foreach_target echo '" \\"' \; \ echo -n '" \$(fw_swiftapi_module)/$arch.swiftmodule"') cd \$(fw_swiftapi_dstdir)/.. && ln -snf \$(fw_capi_version) Current cd \$(fw_swiftapi_dstdir)/../.. && ln -snf Versions/Current/* . \$(fw_swiftapi_info_plist): ./info-plist.sh mkdir -p \$(dir \$@) ./info-plist.sh \$(fw_swiftapi_name) \$(fw_swiftapi_bundle_id) \$(fw_swiftapi_bundle_version) >\$@ \$(fw_swiftapi_library):$(foreach_target echo '" \\"' \; \ echo -n '" $target_dir/$swift_api_library"') mkdir -p \$(dir \$@) lipo -create -output \$@ \$^ $(foreach_target echo '"\$(fw_swiftapi_module)/$arch.swiftmodule: $target_dir/$swift_api_module"' \; \ echo '" mkdir -p \$(dir \$@)"' \; \ echo '" cp \$< \$@"' \; \ echo) \$(xcconfig): Makefile mkdir -p \$(dir \$@) @echo "create \$@" @{ \\ echo '// \$(notdir \$@) for Serval DNA on Apple iOS' ;\\ echo '// Copyright 2018 Flinders University' ;\\ echo '// This file was auto-generated by \$(abspath Makefile)' ;\\ echo '// \$(shell date)' ;\\ echo 'FRAMEWORK_SEARCH_PATHS = \$\$(inherited) \$(abspath \$(dstdir))' ;\\ echo 'GCC_PREPROCESSOR_DEFINITIONS = \$\$(inherited) HAVE_CONFIG_H=1' ;\\ $(foreach_target echo '" echo '\''HEADER_SEARCH_PATHS[arch=$arch] = \$\$(inherited) \$(abspath \$(fw_capi_dir))/Headers/$target'\'' ;\\"') echo 'SWIFT_VERSION = 4.1' ;\\ echo 'OTHER_SWIFT_FLAGS = \$\$(inherited) -Xcc -DHAVE_CONFIG_H=1' ;\\ } >\$@ $(foreach_target echo \; \ echo '".PHONY: install_${target_var}_sodium_headers"' \; \ echo '"install_${target_var}_sodium_headers: $target_dir/$c_api_library"' \; \ echo '" cp -R $target_dir/libsodium-dev/include/ \$(fw_capi_dstdir)/Headers/$target/"') # Recursive make targets. # # If the source directory is already configured, then its config.h header # file would interfere with these sub-makes, so delete it before invoking # each sub-make. $(foreach_target echo \; \ echo '".PHONY: $target_dir/$c_api_library"' \; \ echo '"$target_dir/$c_api_library:"' \; \ echo '" \$(RM) \$(srcdir)/config.h"' \; \ echo '" \$(MAKE) -C $target_dir $c_api_library"') $(foreach_target echo \; \ echo '".PHONY: $target_dir/$swift_api_library"' \; \ echo '"$target_dir/$swift_api_library:"' \; \ echo '" \$(RM) \$(srcdir)/config.h"' \; \ echo '" \$(MAKE) -C $target_dir $swift_api_library"') $(foreach_target echo \; \ echo '".PHONY: $target_dir/$swift_api_module"' \; \ echo '"$target_dir/$swift_api_module:"' \; \ echo '" \$(RM) \$(srcdir)/config.h"' \; \ echo '" \$(MAKE) -C $target_dir $swift_api_module"') # Clean targets. clean: $(foreach_target echo -n '" clean-$target"') \$(RM) -r \$(dstdir) $(foreach_target echo \; \ echo '"clean-$target:"' \; \ echo '" \$(MAKE) -C $target_dir clean"') EOF } main "$@"