Skip to content

Instantly share code, notes, and snippets.

@CognitiveDisson
Last active March 28, 2021 00:56
Show Gist options
  • Save CognitiveDisson/0ac4c8031ee60c0ec16de848e576ff42 to your computer and use it in GitHub Desktop.
Save CognitiveDisson/0ac4c8031ee60c0ec16de848e576ff42 to your computer and use it in GitHub Desktop.
Build universal frameworks
#!/bin/sh
# Print all the commands in script before their execution
# export PS4='+ 6: '
set -x
# Force the script to stop if any unhandled error occurs (i.e. an error not handled with `||`)
# set -e
# Force the script to stop if any uninitialized variable is used
set -u
# Force the script to bypass error result through all pipes if any pipe fails
set -o pipefail
# Get path to a folder containing the script
SCRIPT_ROOT=`cd "$(dirname "$0")" && pwd`
workspaceName="${SCRIPT_ROOT}/DependenciesBuilder.xcworkspace"
schemeName="Pods-DependenciesBuilder"
configurationName="Release"
inputPath="${SCRIPT_ROOT}/DerivedData.ignored"
outputPath="${SCRIPT_ROOT}/UniversalOutput.ignored"
podspecsPath="${SCRIPT_ROOT}/../"
# Navigate to script directory
cd "$(dirname "$0")"
# Prepare folders
rm -rf $inputPath
rm -rf $outputPath
# Update project pods
pod install --project-directory="${SCRIPT_ROOT}" || exit 1
# Build frameworks for all architectures
xcodebuild \
-workspace $workspaceName \
-scheme $schemeName \
-configuration $configurationName \
-sdk iphoneos \
-derivedDataPath $inputPath \
build | xcpretty || exit 1
xcodebuild \
-workspace $workspaceName \
-scheme $schemeName \
-configuration $configurationName \
-sdk iphonesimulator \
-derivedDataPath $inputPath \
build | xcpretty || exit 1
frameworksCreated=0
dsymsCreated=0
if [ ! -d "${outputPath}" ]; then
echo "Create output folder"
mkdir "${outputPath}"
fi;
devicePath="${inputPath}/Build/Products/${configurationName}-iphoneos"
simulatorPath="${inputPath}/Build/Products/${configurationName}-iphonesimulator"
writePodspec() {
local name="${1:?}"
local module_name="${2:?}"
local version="${3:?}"
local podspecName="${name}.podspec"
local podspecPath="${podspecsPath}/${podspecName}"
if [ -e "${podspecPath}" ]; then
echo "Podspec ${podspecName} already exists"
return
fi
echo "podspecPath: $podspecPath"
{
echo "Pod::Spec.new do |s|"
echo " s.name = '${name}'"
if [[ "${name}" != "${module_name}" ]]; then
echo " s.module_name = '${module_name}'"
fi
echo " s.version = '${version}'"
echo " s.summary = 'Prebuilded ${name}'"
echo " s.homepage = 'http://.../prebuilded-frameworks/${name}'"
echo " s.license = 'MIT'"
echo " s.author = { '$(git config --global user.name)' => '$(git config --global user.email)' }"
echo " s.source = { :git => 'ssh://.../prebuilded-frameworks.git', :branch => 'master' }"
echo " s.platform = :ios, '9.0'"
echo " s.ios.deployment_target = '9.0'"
echo " s.requires_arc = true"
echo " s.vendored_frameworks = '${name}/${module_name}.framework'"
echo "end"
} > "${podspecPath}"
}
for deviceFolder in $(find $devicePath -mindepth 1 -maxdepth 1); do
frameworkName=$(basename "${deviceFolder}")
echo "Process ${frameworkName} == ${schemeName}.framework"
tmpName=$(echo $frameworkName | tr '_' '-')
if [ "${tmpName}" == "${schemeName}.framework" ]; then
continue;
fi;
simulatorFolder="$simulatorPath/$frameworkName"
deviceFrameworkPath=$(find $deviceFolder -mindepth 1 -maxdepth 1 | grep '.*\.framework$')
simulatorFrameworkPath=$(find $simulatorFolder -mindepth 1 -maxdepth 1 | grep '.*\.framework$')
if [ ! -d "$deviceFrameworkPath" ]; then continue; fi;
echo "Process $(tput bold)$frameworkName$(tput sgr0)"
if [ ! -d "$simulatorFrameworkPath" ]; then
echo " Could not find simulator framework"
continue
fi;
binaryName="$(basename $deviceFrameworkPath)"
binaryName="${binaryName%.*}"
deviceBinaryPath="$deviceFrameworkPath/$binaryName"
simulatorBinaryPath="$simulatorFrameworkPath/$binaryName"
if [ ! -f "$deviceBinaryPath" ]; then
echo " Could not find binary file for device"
continue
fi;
if [ ! -f "$simulatorBinaryPath" ]; then
echo " Could not find binary file for simulator"
continue
fi;
echo " Copy device framework"
cp -r "${deviceFrameworkPath}" "${outputPath}"
frameworkFileName="$(basename $deviceFrameworkPath)"
universalBinaryPath="${outputPath}/$frameworkFileName/$binaryName"
similatorSwiftModulesPath="${simulatorFrameworkPath}/Modules/$frameworkName.swiftmodule"
universalSwiftModulesPath="${outputPath}/$frameworkFileName/Modules/"
if [ -d "$similatorSwiftModulesPath" ]; then
echo " Copy simulator swiftModules"
cp -r "$similatorSwiftModulesPath" "$universalSwiftModulesPath"
fi;
echo " Merge framework architectures"
lipo -create $deviceBinaryPath $simulatorBinaryPath -o $universalBinaryPath
echo "success lipo"
let "frameworksCreated++"
deviceDsymPath=$(find $deviceFolder -mindepth 1 -maxdepth 1 | grep '.*\.framework.dSYM$')
simulatorDsymPath=$(find $simulatorFolder -mindepth 1 -maxdepth 1 | grep '.*\.framework.dSYM$')
echo "success find dsym"
if [ ! -d "$deviceDsymPath" ]; then
echo " Could not find dSYM for device"
continue
fi;
if [ ! -d "$simulatorDsymPath" ]; then
echo " Could not find dSYM for simulator"
continue
fi;
echo " Copy device dSYM"
cp -r "${deviceDsymPath}" "${outputPath}"
deviceDwafrPath="$deviceDsymPath/Contents/Resources/DWARF/$binaryName"
simulatorDwafrPath="$simulatorDsymPath/Contents/Resources/DWARF/$binaryName"
universalDwarfPath="${outputPath}/$frameworkFileName.dSYM/Contents/Resources/DWARF/$binaryName"
echo " Merge dSYM architectures"
lipo -create $deviceDwafrPath $simulatorDwafrPath -o $universalDwarfPath
let "dsymsCreated++"
frameworkModuleName="${frameworkFileName%.*}"
frameworkName=$(echo $frameworkModuleName | tr '_' '-')
version=$(cat Podfile.lock | grep -e "-[[:space:]]${frameworkName}.*[[:space:]]([0-9]\+" | sed 's#.*(##' | sed 's#).*##' | head -1)
frameworkPath="${outputPath}/${frameworkFileName}"
dwarfPath="${outputPath}/${frameworkFileName}.dSYM"
frameworkDirectory="${podspecsPath}/${frameworkName}"
mkdir -p $frameworkDirectory
frameworkDestination="${frameworkDirectory}/${frameworkModuleName}.framework"
dwarfDestination="${frameworkDirectory}/${frameworkModuleName}.framework.dSYM"
rm -rf $frameworkDestination
rm -rf $dwarfDestination
mv $frameworkPath $frameworkDestination
mv $dwarfPath $dwarfDestination
writePodspec "${frameworkName}" "${frameworkModuleName}" "${version}"
done
# Clean folders
rm -rf $inputPath
rm -rf $outputPath
echo "$(tput bold)Done! $frameworksCreated frameworks created with $dsymsCreated dSYMs $(tput sgr0)"
@daukabase
Copy link

In 91 line you specify only one vendored framework. What if there are several dependencies for compiled pod?
For example, you are importing RxCocoa which also has two dependencies as RxRelay & RxSwift. There are will be an error Missing required module RxRelay, because we didn't add RxRelay to list of vendored frameworks

@daukabase
Copy link

In 91 line you specify only one vendored framework. What if there are several dependencies for compiled pod?
For example, you are importing RxCocoa which also has two dependencies as RxRelay & RxSwift. There are will be an error Missing required module RxRelay, because we didn't add RxRelay to list of vendored frameworks

There is one solution for it, is to scan Podfile.lock and get needed dependency. I am interesting in your solution for that problem

@CognitiveDisson
Copy link
Author

CognitiveDisson commented Mar 28, 2021

Hi @daukabase. In this code a vendored framework is the framework that we are trying to pre-build, not dependencies. As you can see from the code, there is nothing about dependencies there. All dependency linking flags will already be set in the binary after the build. You just need to include all dependencies in the final build.

Example: We are trying to pre-build the RxCocoa framework. The podspec file looks like this:

Pod::Spec.new do |spec|
  spec.name = 'RxCocoa'
  ...
  spec.dependency 'RxRelay'
  spec.dependency 'RxSwift'
end

If we run this script, we will get another podspec for the prebuilded framework.

Pod::Spec.new do |spec|
  spec.name = 'RxCocoa'
  ...
  s.vendored_frameworks    = 'RxCocoa/RxCocoa.framework'"
end

What we need to do is to make it work eventually, it also runs this script for other dependencies - RxRelay & RxSwift, and integrate them to Podfile.

target 'MyApp' do
  pod 'RxCocoa', :path => 'RxCocoa'
  pod 'RxRelay', :path => 'RxRelay'
  pod 'RxSwift', :path => 'RxSwift'
end

The only problem is that you need to specify all the transitive dependencies for the framework you are trying to pre-build in order to specify the path to them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment