iOS: Refactor xcodebuild exclusive build logic into standalone makefile

Instead of going to qmake to generate the makefile that we want, we write
the makefile directly and include it from the generated makefile. This
leaves us with a single top level makefile for handling exclusive builds
through xcodebuild, and covers all the various build configurations in
a unified manner. It also allows for improved test device handling.

Change-Id: I66851f181ac4da2c8938645e0aa95ffa0fee33c7
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@theqtcompany.com>
This commit is contained in:
Tor Arne Vestbø 2015-06-16 17:14:53 +02:00
parent 5d511039f7
commit e405665318
5 changed files with 178 additions and 68 deletions

View File

@ -0,0 +1,3 @@
# For the xcodebuild wrapper makefile we deal with test targets manually
!xcodebuild: \
load(testcase_targets)

View File

@ -14,80 +14,36 @@ RESOURCES =
INSTALLS =
QMAKE_EXTRA_COMPILERS =
!build_pass {
# Top level makefile that aggregates all exclusive builds
!mkpath($$OUT_PWD): \
error("Failed to create $$OUT_PWD")
!mkpath($$OUT_PWD): \
error("Failed to create $$OUT_PWD")
args =
for(arg, QMAKE_ARGS): \
args += $$system_quote($$arg)
args =
for(arg, QMAKE_ARGS): \
args += $$system_quote($$arg)
cmd = "$$QMAKE_QMAKE $$args $$system_quote($$_PRO_FILE_) -spec macx-xcode"
debug(1, "Generating Xcode project in $$OUT_PWD using '$$cmd'")
system("cd $$system_quote($$OUT_PWD) && $$cmd")
cmd = "$$QMAKE_QMAKE $$args $$system_quote($$_PRO_FILE_) -spec macx-xcode"
debug(1, "Generating Xcode project in $$OUT_PWD using '$$cmd'")
system("cd $$system_quote($$OUT_PWD) && $$cmd")
# Subtargets
distclean_files += $${TARGET}.xcodeproj
for(build, BUILDS): \
SUBTARGETS += $$eval($${build}.target)
QMAKE_EXTRA_VARIABLES += SUBTARGETS
# Pretend we have a target, even though our template is aux
CONFIG += have_target
CONFIG += no_default_goal_deps
} else {
# Leaf makefile for a single exclusive build
QMAKE_EXTRA_INCLUDES += $$shell_quote($$QMAKESPEC/xcodebuild.mk)
iphonesimulator: \
sdk = iphonesimulator
else: \
sdk = iphoneos
# Distclean
debug: \
cfg = debug
else: \
cfg = release
distfiles = $${TARGET}.xcodeproj
for(build, BUILDS): \
distfiles += $$title($$eval($${build}.target))
distclean_xcodebuild.commands = -$(DEL_FILE) -R $$distfiles
for(action, $$list(build install clean test)) {
equals(action, build) {
action_target_suffix =
action_target = all
} else: equals(action, test) {
action_target_suffix = -check
action_target = check
} else {
action_target_suffix = -$$action
action_target = $$action
}
target = $${sdk}-$${cfg}$${action_target_suffix}
xcodebuild = "xcodebuild $$action -scheme $(TARGET) -sdk $$sdk -configuration $$title($$cfg)"
equals(action, test):equals(sdk, iphoneos) {
AVAILABLE_DEVICE_IDS = "$(shell system_profiler SPUSBDataType | sed -n -E -e '/(iPhone|iPad|iPod)/,/Serial/s/ *Serial Number: *(.+)/\1/p')"
CUSTOM_DEVICE_IDS = "$(filter $(EXPORT_AVAILABLE_DEVICE_IDS), $(IOS_TEST_DEVICE_IDS))"
TEST_DEVICE_IDS = "$(strip $(if $(EXPORT_CUSTOM_DEVICE_IDS), $(EXPORT_CUSTOM_DEVICE_IDS), $(EXPORT_AVAILABLE_DEVICE_IDS)))"
QMAKE_EXTRA_VARIABLES += AVAILABLE_DEVICE_IDS CUSTOM_DEVICE_IDS TEST_DEVICE_IDS
xcodebuild = "@$(if $(EXPORT_TEST_DEVICE_IDS),"\
"echo Running tests on $(words $(EXPORT_TEST_DEVICE_IDS)) device\\(s\\): && ("\
"$(foreach deviceid, $(EXPORT_TEST_DEVICE_IDS),"\
"(echo Testing on device ID '$(deviceid)' ... && $${xcodebuild} -destination 'platform=iOS,id=$(deviceid)' && echo) &&"\
") echo Tests completed successfully on all devices"\
"), $(error No iOS devices connected, please connect at least one device that can be used for testing.))"
}
$${target}.commands = $$xcodebuild
QMAKE_EXTRA_TARGETS += $$target
$${action_target}.depends += $$target
QMAKE_EXTRA_TARGETS *= $${action_target}
}
# Remove build dir
distclean_files += $$title($$cfg)-$${sdk}
}
distclean_xcodebuild.commands = -$(DEL_FILE) -R $$distclean_files
distclean.depends += distclean_xcodebuild
distclean.depends += clean_all distclean_xcodebuild
QMAKE_EXTRA_TARGETS += distclean distclean_xcodebuild
# Empty exclusive builds, we've set them up manually
BUILDS =

View File

@ -0,0 +1,55 @@
#!/bin/bash
#############################################################################
##
## Copyright (C) 2015 The Qt Company Ltd.
## Contact: http://www.qt.io/licensing/
##
## This file is the build configuration utility of the Qt Toolkit.
##
## $QT_BEGIN_LICENSE:LGPL21$
## Commercial License Usage
## Licensees holding valid commercial Qt licenses may use this file in
## accordance with the commercial license agreement provided with the
## Software or, alternatively, in accordance with the terms contained in
## a written agreement between you and The Qt Company. For licensing terms
## and conditions see http://www.qt.io/terms-conditions. For further
## information use the contact form at http://www.qt.io/contact-us.
##
## GNU Lesser General Public License Usage
## Alternatively, this file may be used under the terms of the GNU Lesser
## General Public License version 2.1 or version 3 as published by the Free
## Software Foundation and appearing in the file LICENSE.LGPLv21 and
## LICENSE.LGPLv3 included in the packaging of this file. Please review the
## following information to ensure the GNU Lesser General Public License
## requirements will be met: https://www.gnu.org/licenses/lgpl.html and
## http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
##
## As a special exception, The Qt Company gives you certain additional
## rights. These rights are described in The Qt Company LGPL Exception
## version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
##
## $QT_END_LICENSE$
##
#############################################################################
booted_simulator=$(xcrun simctl list devices | grep -v unavailable | grep Booted | perl -lne 'print $1 if /\((.*?)\)/')
echo "IPHONESIMULATOR_DEVICES = $booted_simulator"
xcodebuild test -scheme $1 -destination 'id=0' -destination-timeout 1 2>&1| sed -n 's/{ \(platform:.*\) }/\1/p' | while read destination; do
id=$(echo $destination | sed -n -E 's/.*id:([^ ,]+).*/\1/p')
echo $destination | tr ',' '\n' | while read keyval; do
key=$(echo $keyval | cut -d ':' -f 1 | tr '[:lower:]' '[:upper:]')
val=$(echo $keyval | cut -d ':' -f 2)
echo "%_$id: DESTINATION_${key} = $val"
if [ $key = 'PLATFORM' ]; then
if [ "$val" = "iOS" ]; then
echo "IPHONEOS_DEVICES += $id"
elif [ "$val" = "iOS Simulator" -a "$id" != "$booted_simulator" ]; then
echo "IPHONESIMULATOR_DEVICES += $id"
fi
fi
done
echo
done

View File

@ -0,0 +1,96 @@
# We don't want xcodebuild to run in parallel
.NOTPARALLEL:
# Functions
targets = $(foreach target, $(EXPORT_SUBTARGETS), $(target)-$(strip $(1)))
toupper = $(shell echo $1 | tr '[:lower:]' '[:upper:]')
tolower = $(shell echo $1 | tr '[:upper:]' '[:lower:]')
basesdk = $(shell echo $1 | sed 's/[0-9.]*$$//')
# Explicit comma variable
, := ,
# Default targets
first: build
all: build_all
.DEFAULT_GOAL = first
# Top level targets
build: build_first
clean: clean_first
install: install_first
check: check_first
distclean: clean_all
$(EXPORT_SUBTARGETS): % : %-build
# Generic targets
%_first: $(firstword $(call targets, %)) ;
%_all: $(call targets, %) ;
# Actions
%-build: ACTION = build
%-build: xcodebuild-% ;
%-clean: ACTION = clean
%-clean: xcodebuild-% ;
%-install: ACTION = install
%-install: xcodebuild-% ;
# Limit check to a single configuration
%-iphoneos-check: check-iphoneos ;
%-iphonesimulator-check: check-iphonesimulator ;
# SDK
%-iphoneos: SDK = iphoneos
%-iphonesimulator: SDK = iphonesimulator
# Configuration
release-%: CONFIGURATION = Release
debug-%: CONFIGURATION = Debug
# Test and build (device) destinations
ifneq ($(filter check%,$(MAKECMDGOALS)),)
ifeq ($(DEVICES),)
$(info Enumerating test destinations (you may override this by setting DEVICES explicitly), please wait...)
SPECDIR := $(dir $(lastword $(MAKEFILE_LIST)))
DESTINATIONS_INCLUDE = /tmp/ios_destinations.mk
$(shell $(SPECDIR)/ios_destinations.sh $(TARGET) > $(DESTINATIONS_INCLUDE))
include $(DESTINATIONS_INCLUDE)
endif
endif
%-iphonesimulator: DEVICES = $(firstword $(IPHONESIMULATOR_DEVICES))
%-iphoneos: DEVICES = $(IPHONEOS_DEVICES)
IPHONEOS_GENERIC_DESTINATION := "generic/platform=iOS"
IPHONESIMULATOR_GENERIC_DESTINATION := "id=$(shell xcrun simctl list devices | grep -v unavailable | perl -lne 'print $$1 if /\((.*?)\)/' | tail -n 1)"
DESTINATION = $(if $(DESTINATION_ID),"id=$(DESTINATION_ID)",$(value $(call toupper,$(call basesdk,$(SDK)))_GENERIC_DESTINATION))
# Xcodebuild
DESTINATION_MESSAGE = "Running $(call tolower,$(CONFIGURATION)) $(ACTION) \
on '$(DESTINATION_NAME)' ($(DESTINATION_ID))$(if $(DESTINATION_OS),$(,) $(DESTINATION_PLATFORM) $(DESTINATION_OS),)"
xcodebuild-%:
@$(if $(DESTINATION_NAME), echo $(DESTINATION_MESSAGE),)
xcodebuild $(ACTION) -scheme $(TARGET) $(if $(SDK), -sdk $(SDK),) $(if $(CONFIGURATION), -configuration $(CONFIGURATION),) $(if $(DESTINATION), -destination $(DESTINATION) -destination-timeout 1,)
xcodebuild-check-device_%: DESTINATION_ID=$(lastword $(subst _, ,$@))
# Special check target (requires SECONDEXPANSION due to devices)
.SECONDEXPANSION:
check-%: ACTION = test
check-%: $$(foreach device, $$(DEVICES), xcodebuild-check-device_$$(device)) ;
@echo $(if $^, Ran $(call tolower,$(CONFIGURATION)) tests on $(words $^) $(SDK) destination\(s\): $(DEVICES), No compatible test devices found for \'$(SDK)\' SDK && false)
# Determined by device
check-%: SDK =
# Default to debug for testing
check-%: CONFIGURATION = Debug

View File

@ -306,7 +306,7 @@ UnixMakefileGenerator::writeMakeParts(QTextStream &t)
t << "include " << escapeDependencyPath(*it) << endl;
/* rules */
t << "first: all\n";
t << "first:" << (!project->isActiveConfig("no_default_goal_deps") ? " all" : "") << "\n";
t << "####### Implicit rules\n\n";
t << ".SUFFIXES: " << Option::obj_ext;
for(QStringList::Iterator cit = Option::c_ext.begin(); cit != Option::c_ext.end(); ++cit)