-
Notifications
You must be signed in to change notification settings - Fork 12
/
make-fmwk.sh
executable file
·845 lines (754 loc) · 36.6 KB
/
make-fmwk.sh
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
#!/bin/bash
# Constants
VERSION_NBR=2.0.2
SCRIPT_NAME=`basename $0`
# Directory from which the script is executed
EXECUTION_DIR=`pwd`
BUILD_DIR_COMMON="$EXECUTION_DIR/build"
BUILD_DIR_32_BITS="$BUILD_DIR_COMMON/32-bits"
BUILD_DIR_64_BITS="$BUILD_DIR_COMMON/64-bits"
DEFAULT_BOOTSTRAP_FILE="$EXECUTION_DIR/bootstrap.txt"
DEFAULT_OUTPUT_DIR="$HOME/StaticFrameworks"
DEFAULT_PUBLIC_HEADERS_FILE="$EXECUTION_DIR/publicHeaders.txt"
FORCE_LINK_TAG="FILE_FORCE_LINK"
# Global variables
param_bootstrap_file=""
param_code_version=""
param_framework_name=""
param_log_dir=""
param_lock_output=false
param_omit_version_in_name=false
param_output_dir=""
param_project_name=""
param_public_headers_file=""
param_sdk_version=""
param_cleanup_build_products=false
param_target_name=""
param_scheme_name=""
param_force_xcodebuild=false
build_tool="xcodebuild"
log_dir=""
output_dir=""
project_name=""
sdk_version=""
target_section=false
default_target_name=""
scheme_section=false
default_scheme_name=""
configuration_section=false
primary_configuration_name=""
# User manual
usage() {
echo ""
echo "This script compiles and packages a static library project into a reusable static"
echo "framework for iOS projects. The script must be launched from the directory containing"
echo "the .xcodeproj of a static library project, and produces a .staticframework directory"
echo "which can then be easily added to a project. This directory contains a .framework"
echo "containing binaries and headers, as well as a .bundle for resources."
echo ""
echo "When creating a .staticframework, you can specify which scheme or target must be used."
echo "If you provide none, the script uses the first listed scheme (if any), otherwise the"
echo "first target. By default, the script only builds the configuration used for command-line"
echo "builds (called primary configuration below), as set in the Xcode project settings. You"
echo "can provide additional optional configurations to be built along the primary one. The"
echo "corresponding binaries will be bundled in the .framework as well. When linking against"
echo "the .framework, use -framework FrameworkName to link against the primary binary, or"
echo "-framework FrameworkName,ConfigurationName for another available binary."
echo ""
echo "A file listing all headers to be made public is required as input of the script. Based"
echo "on this file, the script generates a global framework header, usually to be imported in"
echo "precompiled header files. This file is used as umbrella headers for LLVM modules, which"
echo "makes static frameworks compatible with Swift projects as well."
echo ""
echo "In some cases, the Objective-C linker will never be able to link code from a library"
echo "(e.g. source files containing a category for a class defined outside the library)."
echo "In such cases linkage is usually forced by setting the -ObjC linker flag in the target"
echo "settings of the client project. This forces all library object files to be loaded, for"
echo "all libraries used by this project. Since this can lead to larger executable files than"
echo "necessary (and to more project configuration steps), this script provides a way to mark"
echo "only those files for which linkage must be forced (bootstrap file, boostrap.txt by default)."
echo "Note that the bootstrap file is not required when the source code is packed into the static"
echo "framework."
echo ""
echo "To avoid conflicting names when merging framework resources with other framework resources"
echo "or with application resources, all resource files should be prefixed with the name of the"
echo "framework, or embedded into a bundle (which is better, especially when those resources are"
echo "localized)"
echo ""
echo "By default the generated .staticframework file is saved under a common ~/StaticFrameworks"
echo "directory acting as a framework repository. You can still change the output directory using"
echo "the -o option. Other build products are still saved under the build directory."
echo ""
echo "If xctool is available on your system, it will be used instead of xcodebuild. The -t parameter"
echo "is currently not supported for xctool, if you really need it force xcodebuild to be used by"
echo "setting the -X flag"
echo ""
echo "Usage: $SCRIPT_NAME [-p project_name][-k sdk_version] [-t target_name] [-u code_version]"
echo " [-o output_dir] [-l log_dir] [-f public_headers_file] [-b bootstrap_file] [-K] [-n]"
echo " [-s] [-S scheme_name] [-v] [-h] [-L] [-X] [additional_configuration_name ...]"
echo ""
echo "Optional parameters:"
echo " additional_configuration_name The name of an additional configuration"
echo " to build"
echo ""
echo "Options:"
echo " -b: Path to the bootstrap file. This file lists all source files for"
echo " which linkage must be forced, one per line, e.g.:"
echo " file1.m"
echo " file2.m"
echo " ..."
echo " fileN.m"
echo " If omitted, the script looks for a file named bootstrap.txt in"
echo " the project directory (if any exists)"
echo " -f: A file containing the list of headers used to create the framework"
echo " public interface, one per line:"
echo " header1.h"
echo " header2.h"
echo " ..."
echo " headerN.h"
echo " If omitted, the script looks for a file namedpublicHeaders.txt in"
echo " the project directory"
echo " -h: Display this documentation"
echo " -k: By default the compilation is made against the most recent version"
echo " of the iOS SDK. Use this option to use a specific version number,"
echo " e.g. 7.0"
echo " -K: Cleanup build products if successful"
echo " -l: Output directory for log files (build directory if omitted)"
echo " -L: Lock the .staticframework output files to prevent from accidental"
echo " changes"
echo " -n: By default, if the code version is specified it is appended to the"
echo " framework name. This allows projects to be bound to specific framework"
echo " versions. Use -n to disable this behavior"
echo " -N: The name of the .staticframework (if not specified, defaults to scheme,"
echo " target or project name, depending on which is available"
echo " -o Output directory where the .staticframework will be saved. If not"
echo " specified, ~/StaticFrameworks is used"
echo " -p: If you have multiple projects in the same directory, indicate which"
echo " one must be used using this option (without the .xcodeproj extension)"
echo " -s: The scheme to use"
echo " -t: The target to use"
echo " -u Tag identifying the version of the code which has been compiled. Added"
echo " to the framework info.plist and appended to the framework name (if -n"
echo " is not used)"
echo " -v: Print the script version number"
echo " -X: Force using xcodebuild"
echo ""
}
# Set internal field separator for for loops; otherwise for loops on paths arrays won't work
# if a path contains a space (since the default IFS contains space as well)
# TODO: Should be more local or replaced by find -exec {}. This can have undesired global effects
# we should strive to avoid
IFS=$'\n'
# Checking that the name of a file, given its path, begins with a given prefix
# @param $1 the resource path
# @param $2 the prefix
check_prefix() {
file_name="${1##*/}"
echo "$file_name" | grep "^${2}_" &> /dev/null
if [ "$?" -ne "0" ]; then
echo "[WARN] The resource file $file_name should be prefixed with ${2}_ to avoid conflicts with other resources"
fi
}
# Check if a value exists in an array
# @param $1 mixed Needle
# @param $2 array Haystack
# @return Success (0) if value exists, Failure (1) otherwise
# Usage: contains_element "$needle" "${haystack[@]}"
# See: http://fvue.nl/wiki/Bash:_Check_if_array_element_exists
contains_element() {
local hay needle=$1
shift
for hay; do
[[ $hay == $needle ]] && return 0
done
return 1
}
# Processing command-line parameters
while getopts b:f:hk:Kl:LnN:o:p:s:t:u:vX OPT; do
case "$OPT" in
b)
param_bootstrap_file="$OPTARG"
;;
f)
param_public_headers_file="$OPTARG"
;;
h)
usage
exit 0
;;
k)
param_sdk_version="$OPTARG"
;;
K)
param_cleanup_build_products=true
;;
l)
param_log_dir="$OPTARG"
;;
L)
param_lock_output=true
;;
n)
param_omit_version_in_name=true
;;
N)
param_framework_name="$OPTARG"
;;
o)
param_output_dir="$OPTARG"
;;
p)
param_project_name="$OPTARG"
;;
s)
param_scheme_name="$OPTARG"
;;
t)
param_target_name="$OPTARG"
;;
u)
param_code_version="$OPTARG"
;;
v)
echo "$SCRIPT_NAME version $VERSION_NBR"
exit 0
;;
X)
param_force_xcodebuild=true
;;
\?)
usage
exit 1
;;
esac
done
# Read specified configurations
additional_configuration_names=()
shift `expr $OPTIND - 1`
for arg in "$@"; do
additional_configuration_names+=("$arg")
done
# xctool
if ! $param_force_xcodebuild; then
# Use xctool when available
which xctool > /dev/null
if [ "$?" -eq "0" ]; then
echo "[INFO] xtool has been found and will be used for compilation"
build_tool="xctool"
fi
fi
# Public headers file
if [ -z "$param_public_headers_file" ]; then
public_headers_file="$DEFAULT_PUBLIC_HEADERS_FILE"
else
public_headers_file="$param_public_headers_file"
fi
# Check that the public header file exists
if [ ! -f "$public_headers_file" ]; then
echo "[ERROR] The public header file $public_headers_file does not exist"
exit 1
fi
# Bootstrap file
if [ -z "$param_bootstrap_file" ]; then
bootstrap_file="$DEFAULT_BOOTSTRAP_FILE"
else
# Check that the specified file exists
if [ ! -f "$param_bootstrap_file" ]; then
echo "[ERROR] The specified bootstrap file $param_bootstrap_file does not exist"
exit 1
fi
bootstrap_file="$param_bootstrap_file"
fi
# If project name not specified, find it
if [ -z "$param_project_name" ]; then
xcodeproj_list=`ls -1 | grep ".xcodeproj"`
# Not found
if [ "$?" -ne "0" ]; then
echo "[ERROR] No .xcodeproj directory found"
exit 1
fi
# Several projects found
if [ `echo "$xcodeproj_list" | wc -l` -ne "1" ]; then
echo "[ERROR] Several .xcodeproj directories found; use the -p option for disambiguation"
exit 1
fi
# Extract the project name, stripping off the .xcodeproj
project_name=`echo "$xcodeproj_list" | sed 's/.xcodeproj//g'`
# Else check that the project specified exists
else
if [ ! -d "$EXECUTION_DIR/$param_project_name.xcodeproj" ]; then
echo "[ERROR] The project $param_project_name does not exist"
exit 1
fi
project_name="$param_project_name"
fi
# If no SDK version specified, use the latest available
if [ -z "$param_sdk_version" ]; then
# The showsdks command seems to return SDKs from the oldest to the most recent one. Just
# keep the last line and extract the version
sdk_version=`xcodebuild -showsdks | grep iphoneos | tail -n 1 | sed -E 's/^.*iphoneos([0-9.]*)\s*$/\1/g'`
# Check that the SDK specified exists
else
xcodebuild -showsdks | grep -w "iphoneos$param_sdk_version" > /dev/null
if [ "$?" -ne "0" ]; then
echo "[ERROR] Incorrect SDK version, or SDK version not available on this computer"
exit 1
fi
sdk_version="$param_sdk_version"
fi
# Output directory
if [ -z "$param_output_dir" ]; then
output_dir="$DEFAULT_OUTPUT_DIR"
else
output_dir="$param_output_dir"
fi
# Create the output directory if it does not exist
if [ ! -d "$output_dir" ]; then
mkdir -p "$output_dir"
if [ "$?" -ne "0" ]; then
echo "[ERROR] Cannot create output directory"
exit 1
fi
fi
# Log directory (same as build directory if not specified)
if [ ! -z "$param_log_dir" ]; then
log_dir="$param_log_dir"
else
log_dir="$BUILD_DIR_COMMON"
fi
# Create the log directory if it does not exist
if [ ! -d "$log_dir" ]; then
mkdir -p "$log_dir"
fi
# Cannot have both a parameter and a scheme set (the scheme specifies the target to build)
if [[ ! -z "$param_scheme_name" && ! -z "$param_target_name" ]]; then
echo "[ERROR] A scheme and a target cannot be simultaneously provided"
exit 1
fi
# Extract default target, scheme and available configuration list (trim whitespaces at begin / end of each line)
available_configuration_names=()
project_info_arr=(`xcodebuild -project $project_name.xcodeproj -list | sed -e 's/^ *//' -e 's/ *$//'`)
for project_info_line in ${project_info_arr[@]}
do
if [ "$project_info_line" = "Targets:" ]; then
target_section=true
elif [ "$project_info_line" = "Schemes:" ]; then
scheme_section=true
elif [ "$project_info_line" = "Build Configurations:" ]; then
configuration_section=true
elif [[ "$project_info_line" == "If no build configuration"* ]]; then
configuration_section=false
# Parse 'If no build configuration is specified and -scheme is not passed then "Primary configuration name" is used.''
primary_configuration_name=`echo "$project_info_line" | sed -E 's/^.*"(.*)".*$/\1/g'`
elif $target_section; then
default_target_name="$project_info_line"
target_section=false
elif $scheme_section; then
default_scheme_name="$project_info_line"
scheme_section=false
elif $configuration_section; then
if [ ! -z "$project_info_line" ]; then
available_configuration_names+=("$project_info_line")
fi
fi
done
# The primary configuration is used to build the main binary. Required
if [ -z "$primary_configuration_name" ]; then
echo "[ERROR] No primary configuration was found"
exit 1
else
echo "[INFO] The primary configuration $primary_configuration_name will be used to build the main framework binary"
fi
# Collect valid configuration names to build
valid_configuration_names=("$primary_configuration_name")
for additional_configuration_name in ${additional_configuration_names[@]}
do
# Do not add the same configuration twice
contains_element "$additional_configuration_name" "${valid_configuration_names[@]}"
if [ "$?" -eq "0" ]; then
continue
fi
# Check whether the configuration is available or not
contains_element "$additional_configuration_name" "${available_configuration_names[@]}"
if [ "$?" -eq "1" ]; then
echo "[INFO] The configuration '$additional_configuration_name' does not exist. Skipped"
continue
fi
valid_configuration_names+=("$additional_configuration_name")
done
# Target and scheme resolution:
# - if a scheme has been provided, use it
# - if a target has been provided, use it
# - if neither a scheme nor a target have been provided, use the default scheme, if any, otherwise the default target
if [ ! -z "$param_scheme_name" ]; then
scheme_name="$param_scheme_name"
elif [ ! -z "$param_target_name" ]; then
target_name="$param_target_name"
elif [ ! -z "$default_scheme_name" ]; then
scheme_name="$default_scheme_name"
echo "[INFO] Use $default_scheme_name as default scheme for compilation"
elif [ ! -z "$default_target_name" ]; then
target_name="$default_target_name"
echo "[INFO] Use $default_target_name as default target for compilation"
else
echo "[ERROR] No scheme or target available"
exit 1
fi
# The framework name is set using the most specific information available (scheme, then target, then project name)
if [ ! -z "$param_framework_name" ]; then
framework_name="$param_framework_name"
elif [ ! -z "$scheme_name" ]; then
framework_name="$scheme_name"
elif [ ! -z "$target_name" ]; then
framework_name="$target_name"
else
framework_name="$project_name"
fi
# Append the version number
if [ ! -z "$param_code_version" ]; then
if $param_omit_version_in_name; then
framework_full_name="$framework_name"
else
framework_full_name="$framework_name-$param_code_version"
fi
# Warns if no version specified (good practice)
else
echo "[INFO] You should provide a code version for better traceability; use the -u option"
framework_full_name="$framework_name"
fi
# Create framework
echo "[INFO] Creating framework pseudo-bundle..."
# Framework directory
framework_output_dir="$output_dir/$framework_full_name.staticframework"
# Cleanup framework if it already existed
if [ -d "$framework_output_dir" ]; then
echo "[INFO] Framework already exists. Cleaning up first..."
# Restore write permissions (if any were set)
find "$output_dir" -path "*/$framework_full_name.staticframework/*" -exec chmod u+w {} \;
rm -rf "$framework_output_dir"
fi
# Create the main framework directory
mkdir -p "$framework_output_dir"
if [ "$?" -ne "0" ]; then
echo "[ERROR] Cannot create static framework"
exit 1
fi
# Creation of the bootstrap code for selective forced linking
echo "[INFO] Generating bootstrap code..."
bootstrap_output_dir="$framework_output_dir/Bootstrap"
mkdir -p "$bootstrap_output_dir"
bootstrap_output_file="$bootstrap_output_dir/${framework_name}_bootstrap.m"
echo -e "// This file is automatically generated; please do not modify\n" > "$bootstrap_output_file"
echo "#import <Foundation/Foundation.h>" >> "$bootstrap_output_file"
# Bootstrap function name is framework name without characters forbidden for function names
framework_name_clean=`echo "$framework_name" | sed 's/[-+]//g'`
# Begin of the bootstrap function code
bootstrap_function="__attribute__((unused)) void ${framework_name_clean}_bootstrap(void);"
bootstrap_function="$bootstrap_function\n__attribute__((unused)) void ${framework_name_clean}_bootstrap(void)\n{"
# Add bootstrapping code to all classes listed in the bootstrap file (if any)
if [ -f "$bootstrap_file" ]; then
bootstrapped_files_arr=(`cat "$bootstrap_file" | grep -v '^$'`)
for bootstrapped_file in ${bootstrapped_files_arr[@]}
do
# Locate the source file
bootstrapped_path=`find "$EXECUTION_DIR" -name "$bootstrapped_file" -not -path "*/build/*"`
if [ -z "$bootstrapped_path" ]; then
echo "[WARN] The source file $bootstrapped_file appearing in the bootstrap file $bootstrap_file does not exist"
continue
fi
# Make a backup copy first, we will add some dummy class to the source file to force linkage with it
cp "$bootstrapped_path" "$bootstrapped_path.backup"
# Extract file name witout extension
file_name=`basename $bootstrapped_path`
file_name=${file_name%.*}
# Class name is file name without characters forbidden for class names
file_name_clean=`echo "$file_name" | sed 's/[-+]//g'`
# Add a dummy class declaration and definition to the source file. This definition is added at the end of the file
# to leave line numbers intact in comparison to the original source files (useful if the original code logs source
# file line numbers for debugging purposes)
linker_class_interface="\n@interface ${file_name_clean}_Linker : NSObject\n+ (void)link;\n@end\n"
linker_class_implementation="\n@implementation ${file_name_clean}_Linker\n+ (void)link {}\n@end\n"
echo -e "$linker_class_interface" >> "$bootstrapped_path"
echo -e "$linker_class_implementation" >> "$bootstrapped_path"
# Repeat dummy class interface in bootstrap file; this way we avoid the need for a common header file listing all
# dummy classes
echo -e "$linker_class_interface" >> "$bootstrap_output_file"
# Call the static dummy class method from the bootstrap function
bootstrap_function="$bootstrap_function\n\t[${file_name_clean}_Linker link];"
done
else
echo "[INFO] No bootstrap file has been provided"
fi
# End of the bootstrap function; add it to the bootstrap source file
bootstrap_function="$bootstrap_function\n}"
echo -e "$bootstrap_function" >> "$bootstrap_output_file"
# Run the builds (with bootstrap code if any). Overrides some project settings so that all binary flavors are built. The deployment target must match the kind of
# binary built, otherwise compilation fails
build_failure=false
# Build for each configuration
for configuration_name in ${valid_configuration_names[@]}
do
configuration_build_dir_32_bits="$BUILD_DIR_32_BITS/$configuration_name"
configuration_build_dir_64_bits="$BUILD_DIR_64_BITS/$configuration_name"
echo "[INFO] Building $project_name simulator binaries (32-bits) for $configuration_name configuration (SDK $sdk_version)..."
eval "$build_tool -configuration $configuration_name -project $project_name.xcodeproj ${target_name:+-target $target_name} -sdk iphonesimulator$sdk_version IPHONEOS_DEPLOYMENT_TARGET=5.0 \
${scheme_name:+-scheme $scheme_name} CONFIGURATION_BUILD_DIR='$configuration_build_dir_32_bits' PRODUCT_NAME=Static-i386 ARCHS=i386 VALID_ARCHS=i386" &> "$log_dir/$framework_full_name-$configuration_name-i386.buildlog"
if [ "$?" -ne "0" ]; then
echo "[INFO] i386 build failed. Check the logs"
build_failure=true
fi
echo "[INFO] Building $project_name device binaries (32-bits) for $configuration_name configuration (SDK $sdk_version)..."
eval "$build_tool -configuration $configuration_name -project $project_name.xcodeproj ${target_name:+-target $target_name} -sdk iphoneos$sdk_version IPHONEOS_DEPLOYMENT_TARGET=5.0 \
${scheme_name:+-scheme $scheme_name} CONFIGURATION_BUILD_DIR='$configuration_build_dir_32_bits' PRODUCT_NAME=Static-armv ARCHS='armv6 armv7 armv7s' VALID_ARCHS='armv6 armv7 armv7s'" &> "$log_dir/$framework_full_name-$configuration_name-armv.buildlog"
if [ "$?" -ne "0" ]; then
echo "[INFO] armv build failed. Check the logs"
build_failure=true
fi
echo "[INFO] Building $project_name simulator binaries (64-bits) for $configuration_name configuration (SDK $sdk_version)..."
eval "$build_tool -configuration $configuration_name -project $project_name.xcodeproj ${target_name:+-target $target_name} -sdk iphonesimulator$sdk_version PHONEOS_DEPLOYMENT_TARGET=7.0 \
${scheme_name:+-scheme $scheme_name} CONFIGURATION_BUILD_DIR='$configuration_build_dir_64_bits' PRODUCT_NAME=Static-x64 ARCHS=x86_64 VALID_ARCHS=x86_64" &> "$log_dir/$framework_full_name-$configuration_name-x64.buildlog"
if [ "$?" -ne "0" ]; then
echo "[INFO] x64 build failed. Check the logs"
build_failure=true
fi
echo "[INFO] Building $project_name device binaries (64-bits) for $configuration_name configuration (SDK $sdk_version)..."
eval "$build_tool -configuration $configuration_name -project $project_name.xcodeproj ${target_name:+-target $target_name} -sdk iphoneos$sdk_version IPHONEOS_DEPLOYMENT_TARGET=7.0 \
${scheme_name:+-scheme $scheme_name} CONFIGURATION_BUILD_DIR='$configuration_build_dir_64_bits' PRODUCT_NAME=Static-arm64 ARCHS=arm64 VALID_ARCHS=arm64" &> "$log_dir/$framework_full_name-$configuration_name-arm64.buildlog"
if [ "$?" -ne "0" ]; then
echo "[INFO] arm64 build failed. Check the logs"
build_failure=true
fi
done
# Restore the original source code without bootstrapping code
if [ -f "$bootstrap_file" ]; then
bootstrapped_files_arr=(`cat "$bootstrap_file" | grep -v '^$'`)
for bootstrapped_file in ${bootstrapped_files_arr[@]}
do
# Locate the source file
bootstrapped_path=`find "$EXECUTION_DIR" -name "$bootstrapped_file" -not -path "*/build/*"`
if [ -z "$bootstrapped_path" ]; then
# We already issued a warning previously, just skip
continue
fi
# Restore the original file
mv "$bootstrapped_path.backup" "$bootstrapped_path"
done
fi
# Exit on failure
if $build_failure; then
exit 1
fi
# Create .framework directory
dot_framework_output_dir="$framework_output_dir/$framework_name.framework"
# .framework files have in general a complicated internal structure for supporting multiple versions
# (see http://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPFrameworks/Tasks/CreatingFrameworks.htm).
# In the case of a static library framework, we do not need this whole structure since the .framework we define is
# not standardized (in fact it just bears the .framework extension, but it is neither a framework, nor a bundle). To be
# able to use it with Xcode we just need two conditions to be fulfilled:
# a) The universal binary to link against must be located at the root of the framework
# b) A Headers directory must exist, containing all public headers for the library
# This way, when the .framework file is added to an Xcode project, the header files will immediately be available
# and the linker will find the static library.
headers_output_dir="$dot_framework_output_dir/Headers"
mkdir -p "$headers_output_dir"
# Packing static libraries as universal binaries. For the linker to be able to find the static universal binaries in the
# framework bundle, one universal binary (we here use the binary corresponding to the primary configuration we identified)
# must bear the exact same name as the framework. Other binaries NameSuffix can be linked against using -framework,Suffix,
# see ld documentation. Note that the the suffix is case-insensitive, i.e. -framework,suffix or -framework,suffiX work as
# well, for example
echo "[INFO] Packing binaries..."
for configuration_name in ${valid_configuration_names[@]}
do
configuration_build_dir_32_bits="$BUILD_DIR_32_BITS/$configuration_name"
configuration_build_dir_64_bits="$BUILD_DIR_64_BITS/$configuration_name"
if [ "$configuration_name" == "$primary_configuration_name" ]; then
binary_name="$framework_name"
else
binary_name="$framework_name$configuration_name"
fi
lipo -create "$configuration_build_dir_32_bits/"*.a "$configuration_build_dir_64_bits/"*.a -o "$dot_framework_output_dir/$binary_name"
done
# Load the public header file list into an array (remove blank lines if anys)
echo "[INFO] Copying public header files..."
public_headers_arr=(`cat "$public_headers_file" | grep -v '^$'`)
# Locate each public file and add it to the framework bundle
for header_file in ${public_headers_arr[@]}
do
# Header files are also copied into the build directories, need to omit those files
header_path=`find "$EXECUTION_DIR" -name "$header_file" -not -path "*/build/*"`
if [ -z "$header_path" ]; then
echo "[WARN] The header file $header_file appearing in $public_headers_file does not exist"
continue
fi
# Copy the header into the bundle
cp "$header_path" "$headers_output_dir" > /dev/null
# The copy might fail, most notably if the header file appears several times in the source tree
if [ "$?" -ne "0" ]; then
echo "[WARN] Failed to copy $header_path; does this file appear once in your source tree?"
continue
fi
done
# Create global header file contents
global_header_file_contents=`printf "// This section was automatically generated\n"`
# The precompiled header file (if any) is header for all files; start the global header with its contents
precompiled_header_files=(`find "$EXECUTION_DIR" -name "*.pch"`)
# One precompiled header file should exist, not several. Anyway, looping over the results does not hurt,
# and correctly deals with the case where no such file exists
for precompiled_header_file in ${precompiled_header_files[@]}
do
# Remove leading and trailing whitespaces
precompile_header_file_imports=`cat "$precompiled_header_file" | grep "#import" | sed -e 's/^ *//' -e 's/ *$//'`
global_header_file_contents=`printf "$global_header_file_contents\n$precompile_header_file_imports"`
done
# Include all public headers
for header_file in ${public_headers_arr[@]}
do
# Dp not include the header with the same name as the framework (see below)
if [ "$header_file" != "$framework_name.h" ]; then
global_header_file_contents=`printf "$global_header_file_contents\n#import <$framework_name/$header_file>"`
fi
done
# Trick to avoid the final \n being stripped by printf: Add x, remove x
global_header_file_contents=`printf "$global_header_file_contents\n// End of the automatically generated section\n\n"; printf x`
global_header_file_contents=${global_header_file_contents%x}
# Create a global header file bearing the name of the framework
global_header_file="$headers_output_dir/$framework_name.h"
if [ ! -f "$global_header_file" ]; then
echo "$global_header_file_contents" > "$global_header_file"
# Warn if a public header with this name already exists, and prepend with the global header file contents
else
echo "[INFO] A public header file bearing the same name as the framework already exists; global imports will be added "
echo " at the top of the existing header"
echo "$global_header_file_contents" | cat - "$global_header_file" > tempFile && mv tempFile "$global_header_file"
fi
# Packing resource file
resources_output_dir="$framework_output_dir/Resources"
mkdir -p "$resources_output_dir"
# Copy all resource files. Since a resource file can be almost anything, we define a resource file:
# - neither to be a hidden file, nor to be contained in a hidden directory (use find . -not -path "*/.*"). This
# in particular filters out revision control system files
# - not to be contained in the build directory
# - not to be a source file (.m, .h, .c, .C, .cpp, .mm or .pch)
# - not to be contained in the <ProjectName>.xcodeproj folder
# - not the file listing public headers
# - not the bootstrap file
# - not in a .bundle: The .bundle itself is considered as a single resource file
# All those files are put in a common flat directory. Localized resources need a special treatment, see below. Note
# that the exclusion patterns below do not remove directories (since they end up with /*), but since cp is used
# (and not cp -r) they won't be copied. Had we simply used "*/build*"-like patterns, then directories like */buildxyz
# would have been excluded as well, which is incorrect
echo "[INFO] Copying resource files..."
resource_files=(`find "$EXECUTION_DIR" \
-not -path "*/.*" \
-not -path "*/build/*" \
-not -path "*.xcodeproj/*" \
-not -path "*.lproj/*" \
-not -path "*/*.bundle/*" \
-not -name "*.m" \
-not -name "*.mm" \
-not -name "*.c" \
-not -name "*.cpp" \
-not -name "*.h" \
-not -name "*.pch" \
-not -name "Makefile" \
-not -path "$public_headers_file" \
-not -name "$public_headers_file" \
-not -path "$bootstrap_file" \
-not -name "$bootstrap_file"`)
for resource_file in ${resource_files[@]}
do
# .bundle directories are copied as is
resource_file_name=`basename $resource_file`
resource_file_extension=${resource_file_name##*.}
if [ "$resource_file_extension" = "bundle" ]; then
cp -r "$resource_file" "$framework_output_dir/$resource_file_name" &> /dev/null
else
cp "$resource_file" "$resources_output_dir" &> /dev/null
# Those files which could be copied are resources (not directories, e.g.); check that their name begin with a prefix (strongly advised)
if [ "$?" -eq "0" ]; then
check_prefix "$resource_file" "$framework_name"
fi
fi
done
# Copy localized resources, preserving the directory structure
echo "[INFO] Copying localized resource files..."
localized_resource_files=(`find . -path "*.lproj/*" -not -path "*/.*" -not -path "*/build/*" -not -path "*/*.bundle/*"`)
for localized_resource_file in ${localized_resource_files[@]}
do
# Tokenize the path
path_tokens_arr=(`echo "$localized_resource_file" | tr "/" "\n"`)
# Find the localization directory name
token_nbr=${#path_tokens_arr[*]}
localization_dir_name="${path_tokens_arr[$token_nbr-2]}"
# Create the localization directory if it does not exist
framework_localization_dir_name="$resources_output_dir/$localization_dir_name"
if [ ! -d "$framework_localization_dir_name" ]; then
mkdir -p "$framework_localization_dir_name"
fi
# Copy the resource file
cp "$localized_resource_file" "$framework_localization_dir_name"
# Those files which could be copied are resources; check that their name begin with a prefix (strongly advised)
if [ "$?" -eq "0" ]; then
check_prefix "$localized_resource_file" "$framework_name"
fi
done
# Delete Resources directory if empty
rmdir "$resources_output_dir" 2> /dev/null
# Create the modulemap file (see http://clang.llvm.org/docs/Modules.html)
echo "[INFO] Creating modulemap file..."
modules_dir="$dot_framework_output_dir/Modules"
mkdir "$modules_dir"
modules_file="$modules_dir/module.modulemap"
echo "framework module $framework_name {" > "$modules_file"
echo " umbrella header \"$framework_name.h\"" >> "$modules_file"
echo " requires objc" >> "$modules_file"
echo " export *" >> "$modules_file"
echo "}" >> "$modules_file"
# Extract the deployment target information. This information is important since unresolved symbols vary depending on which iOS
# minimal version is required. This can lead to issues when linking static libraries with a project if their deployment targets
# are not compatible
deployment_target=`xcodebuild -showBuildSettings | grep IPHONEOS_DEPLOYMENT_TARGET | sed -E 's/.*= (.*)/\1/g'`
# Add a manifest file
echo "[INFO] Creating manifest file..."
manifest_file="$dot_framework_output_dir/$framework_name.plist"
echo -e "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" >> "$manifest_file"
echo -e "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" >> "$manifest_file"
echo -e "<plist version=\"1.0\">" >> "$manifest_file"
echo -e "<dict>" >> "$manifest_file"
echo -e "\t<key>Project compiled</key>" >> "$manifest_file"
echo -e "\t<string>$project_name</string>" >> "$manifest_file"
if [ ! -z "$param_code_version" ]; then
echo -e "\t<key>Code version</key>" >> "$manifest_file"
echo -e "\t<string>$param_code_version</string>" >> "$manifest_file"
fi
if [ ! -z "$scheme_name" ]; then
echo -e "\t<key>Scheme name</key>" >> "$manifest_file"
echo -e "\t<string>$scheme_name</string>" >> "$manifest_file"
fi
if [ ! -z "$target_name" ]; then
echo -e "\t<key>Target name</key>" >> "$manifest_file"
echo -e "\t<string>$target_name</string>" >> "$manifest_file"
fi
echo -e "\t<key>Primary configuration</key>" >> "$manifest_file"
echo -e "\t<string>$primary_configuration_name</string>" >> "$manifest_file"
echo -e "\t<key>iOS SDK version used</key>" >> "$manifest_file"
echo -e "\t<string>$sdk_version</string>" >> "$manifest_file"
echo -e "\t<key>Deployment target</key>" >> "$manifest_file"
echo -e "\t<string>$deployment_target</string>" >> "$manifest_file"
echo -e "\t<key>make-fmwk version</key>" >> "$manifest_file"
echo -e "\t<string>$VERSION_NBR</string>" >> "$manifest_file"
echo -e "\t<key>Creation date and time</key>" >> "$manifest_file"
echo -e "\t<string>`date`</string>" >> "$manifest_file"
echo -e "</dict>" >> "$manifest_file"
echo -e "</plist>" >> "$manifest_file"
# Lock all .staticframework contents to prevent the user from accidentally editing them within Xcode
if $param_lock_output; then
find "$output_dir" -path "*/$framework_full_name.staticframework/*" -exec chmod a-w {} \;
fi
# Cleanup build products
if $param_cleanup_build_products; then
echo "[INFO] Cleanup build files..."
rm -rf "$BUILD_DIR_COMMON"
fi
# Done
echo "[INFO] Done creating $framework_full_name.staticframework"