#!/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]" } # Run the Serval DNA configure script once for each architecture, and save the # files produced by each run. main() { setup parse_command_line "$@" check exec 5>&1 TARGETS=() configure iphoneos armv7 /Library/Serval configure iphoneos arm64 /Library/Serval configure iphonesimulator i386 /tmp/serval-dna configure iphonesimulator x86_64 /tmp/serval-dna 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() { library=libservaldaemon.a swiftmodule=ServalDNA.swiftmodule opt_force_configure=false opt_force_config_status=false log_display_line_count=10 while [ $# -ne 0 ]; do opt="$1" shift 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" ;; *) usage_error "Spurious argument: $opt" ;; 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_library):$(foreach_target echo '" \\"' \; \ echo -n '" $target_dir/$library"') mkdir -p \$(dir \$@) lipo -create -output \$@ \$^ $(foreach_target echo '"\$(fw_swiftmodule)/$arch.swiftmodule: $target_dir/$swiftmodule"' \; \ echo '" mkdir -p \$(dir \$@)"' \; \ echo '" cp \$< \$@"' \; \ echo) \$(fw_config_headers): \$(fw_dstdir)/Headers/%: build/% mkdir -p \$(dir \$@) cp \$< \$@ \$(fw_serval_headers): \$(fw_dstdir)/Headers/%: \$(srcdir)/% mkdir -p \$(dir \$@) cp \$< \$@ \$(fw_sqlite3_headers): \$(fw_dstdir)/Headers/%: \$(srcdir)/\$(SQLITE3_AMALGAMATION)/% mkdir -p \$(dir \$@) cp \$< \$@ \$(fw_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 on Apple iOS' ;\\ echo 'Copyright 2017 Flinders University' ;\\ echo 'This file was auto-generated by \$(abspath Makefile)' ;\\ echo '\$(shell date)' ;\\ echo '*/' ;\\ echo 'framework module ServalDNA {' ;\\ echo ' requires tls // thread-local storage' ;\\ echo ' explicit module Daemon {' ;\\ echo ' export Sodium' ;\\ $(foreach_target echo '" echo '\'' header \"$target/config.h\"'\'' ;\\"') for header in \$(LIB_HDRS) \$(PUBLIC_HDRS) \$(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 ifaddrs [system] [extern_c] {' ;\\ echo ' header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/ifaddrs.h"' ;\\ echo ' export *' ;\\ 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 '}' ;\\ } >\$@ \$(xcconfig): Makefile mkdir -p \$(dir \$@) @{ \\ echo '// ServalDNA.xcconfig for Serval DNA on Apple iOS' ;\\ echo '// Copyright 2017 Flinders University' ;\\ echo '// This file was auto-generated by \$(abspath Makefile)' ;\\ echo '// \$(shell date)' ;\\ echo 'SERVAL_DNA_FRAMEWORK_DIR = \$(abspath \$(fw_dir))' ;\\ 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) \$\$(SERVAL_DNA_FRAMEWORK_DIR)/Headers/$target'\'' ;\\"') echo 'SWIFT_INCLUDE_PATHS = \$\$(inherited) \$\$(SERVAL_DNA_FRAMEWORK_DIR)/Modules' ;\\ echo 'OTHER_SWIFT_FLAGS = \$\$(inherited) -Xcc -DHAVE_CONFIG_H=1' ;\\ } >\$@ $(foreach_target echo \; \ echo '"install_${target_var}_sodium_headers: $target_dir/$library"' \; \ echo '" cp -R $target_dir/libsodium-dev/include/ \$(fw_dstdir)/Headers/$target/"') # 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 '"$target_dir/$library:"' \; \ echo '" \$(RM) \$(srcdir)/config.h"' \; \ echo '" \$(MAKE) -C $target_dir $library"') $(foreach_target echo \; \ echo '"$target_dir/$swiftmodule:"' \; \ echo '" \$(RM) \$(srcdir)/config.h"' \; \ echo '" \$(MAKE) -C $target_dir $swiftmodule"') clean: $(foreach_target echo -n '" clean-$target"') \$(RM) -r \$(dstdir) $(foreach_target echo \; \ echo '"clean-$target:"' \; \ echo '" \$(MAKE) -C $target_dir clean"') EOF } main "$@"