10000 Add support for XCFrameworks by amorde · Pull Request #9334 · CocoaPods/CocoaPods · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add support for XCFrameworks #9334

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Dec 6, 2019
Merged
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ To install rele 10000 ase candidates run `[sudo] gem install cocoapods --pre`
* Add support for integrating dependency file in user script phases.
[Dimitris Koutsogiorgas](https://github.com/dnkoutso)
[#9082](https://github.com/CocoaPods/CocoaPods/issues/9082)

* Add support for XCFrameworks using the `vendored_frameworks` Podspec DSL.
[Eric Amorde](https://github.com/amorde)
[#9148](https://github.com/CocoaPods/CocoaPods/issues/9148)

##### Bug Fixes

Expand Down
4 changes: 3 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ GIT

GIT
remote: https://github.com/CocoaPods/Core.git
revision: 5c7c11df7b4d1626292f525e747fd2732666f99d
revision: 5a3a8714d828acec517b9b266e69d1626b080b51
branch: master
specs:
cocoapods-core (1.8.4)
Expand All @@ -16,6 +16,8 @@ GIT
concurrent-ruby (~> 1.1)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
netrc (~> 0.11)
typhoeus (~> 1.0)

GIT
remote: https://github.com/CocoaPods/Molinillo.git
Expand Down
1 change: 1 addition & 0 deletions lib/cocoapods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ module Generator
autoload :CopyResourcesScript, 'cocoapods/generator/copy_resources_script'
autoload :DummySource, 'cocoapods/generator/dummy_source'
autoload :EmbedFrameworksScript, 'cocoapods/generator/embed_frameworks_script'
autoload :PrepareArtifactsScript, 'cocoapods/generator/prepare_artifacts_script'
autoload :FileList, 'cocoapods/generator/file_list'
autoload :Header, 'cocoapods/generator/header'
autoload :InfoPlistFile, 'cocoapods/generator/info_plist_file'
Expand Down
42 changes: 36 additions & 6 deletions lib/cocoapods/generator/embed_frameworks_script.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require 'cocoapods/target/framework_paths'
require 'cocoapods/xcode'

module Pod
module Generator
Expand Down Expand Up @@ -91,8 +91,8 @@ def script
fi

# Use filter instead of exclude so missing patterns don't throw errors.
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \\"- CVS/\\" --filter \\"- .svn/\\" --filter \\"- .git/\\" --filter \\"- .hg/\\" --filter \\"- Headers\\" --filter \\"- PrivateHeaders\\" --filter \\"- Modules\\" \\"${source}\\" \\"${destination}\\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \\"- CVS/\\" --filter \\"- .svn/\\" --filter \\"- .git/\\" --filter \\"- .hg/\\" --filter \\"- Headers\\" --filter \\"- PrivateHeaders\\" --filter \\"- Modules\\" \\"${source}\\" \\"${destination}\\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"

local basename
basename="$(basename -s .framework "$1")"
Expand Down Expand Up @@ -145,8 +145,8 @@ def script

if [[ $STRIP_BINARY_RETVAL == 1 ]]; then
# Move the stripped file into its final destination.
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \\"- CVS/\\" --filter \\"- .svn/\\" --filter \\"- .git/\\" --filter \\"- .hg/\\" --filter \\"- Headers\\" --filter \\"- PrivateHeaders\\" --filter \\"- Modules\\" \\"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\\" \\"${DWARF_DSYM_FOLDER_PATH}\\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \\"- CVS/\\" --filter \\"- .svn/\\" --filter \\"- .git/\\" --filter \\"- .hg/\\" --filter \\"- Headers\\" --filter \\"- PrivateHeaders\\" --filter \\"- Modules\\" \\"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\\" \\"${DWARF_DSYM_FOLDER_PATH}\\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
else
# The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing.
touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM"
Expand Down Expand Up @@ -204,8 +204,38 @@ def script
STRIP_BINARY_RETVAL=1
}

install_artifact() {
artifact="$1"
base="$(basename "$artifact")"
case $base in
*.framework)
install_framework "$artifact"
;;
*.dSYM)
install_dsym "$artifact"
;;
*.bcsymbolmap)
install_bcsymbolmap "$artifact"
;;
*)
echo "error: Unrecognized artifact "$artifact""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

;;
esac
}

copy_artifacts() {
file_list="$1"
while read artifact; do
install_artifact "$artifact"
done <$file_list
}

ARTIFACT_LIST_FILE="${BUILT_PRODUCTS_DIR}/cocoapods-artifacts-${CONFIGURATION}.txt"
if [ -r "${ARTIFACT_LIST_FILE}" ]; then
copy_artifacts "${ARTIFACT_LIST_FILE}"
fi

SH
script << "\n" unless frameworks_by_config.each_value.all?(&:empty?)
frameworks_by_config.each do |config, frameworks_with_dsyms|
next if frameworks_with_dsyms.empty?
script << %(if [[ "$CONFIGURATION" == "#{config}" ]]; then\n)
Expand Down
244 changes: 244 additions & 0 deletions lib/cocoapods/generator/prepare_artifacts_script.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
require 'cocoapods/xcode'

module Pod
module Generator
class PrepareArtifactsScript
# @return [Hash{String => Array<Pod::Xcode::XCFramework>}] Multiple lists of xcframeworks per
# configuration.
#
attr_reader :xcframeworks_by_config

# @return [Pathname] the root directory of the sandbox
#
attr_reader :sandbox_root

# @return [Platform] the platform of the target for which this script will run
#
attr_reader :platform

# @param [Hash{String => Array<Pod::Xcode::XCFramework>] xcframeworks_by_config
# @see #xcframeworks_by_config
#
# @param [Pathname] sandbox_root
# the sandbox root of the installation
#
# @param [Platform] platform
# the platform of the target for which this script will run
#
def initialize(xcframeworks_by_config, sandbox_root, platform)
@xcframeworks_by_config = xcframeworks_by_config
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the class name is PrepareArtifactsScript but it is mostly dealing with XCFrameworks, should we just rename this to PrepareXCFrameworksScript?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially named it something like that but renamed it since this could be used to copy any artifact that needs to be in the build folder before the Compile Sources build phase. not strongly opposed to changing it but feels like generic is better in this case

@sandbox_root = sandbox_root
@platform = platform
end

# Saves the resource script to the given pathname.
#
# @param [Pathname] pathname
# The path where the embed frameworks script should be saved.
#
# @return [void]
#
def save_as(pathname)
pathname.open('w') do |file|
file.puts(script)
end
File.chmod(0o755, pathname.to_s)
end

# @return [String] The contents of the embed frameworks script.
#
def generate
script
end

private

# @!group Private Helpers

# @return [String] The contents of the prepare artifacts script.
#
def script
script = <<-SH.strip_heredoc
#!/bin/sh
set -e
set -u
set -o pipefail

function on_error {
echo "$(realpath -mq "${0}"):$1: error: Unexpected failure"
}
trap 'on_error $LINENO' ERR

if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then
# If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
# frameworks to, so exit 0 (signalling the script phase was successful).
exit 0
fi

# This protects against multiple targets copying the same framework dependency at the same time. The solution
# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")

ARTIFACT_LIST_FILE="${BUILT_PRODUCTS_DIR}/cocoapods-artifacts-${CONFIGURATION}.txt"
cat > $ARTIFACT_LIST_FILE

BCSYMBOLMAP_DIR="BCSymbolMaps"

record_artifact()
{
echo "$1" >> $ARTIFACT_LIST_FILE
}

install_artifact()
{
local source="$1"
local destination="$2"
local record=${3:-false}

# Use filter instead of exclude so missing patterns don't throw errors.
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \\"- CVS/\\" --filter \\"- .svn/\\" --filter \\"- .git/\\" --filter \\"- .hg/\\" \\"${source}\\" \\"${destination}\\""
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" "${source}" "${destination}"

if [[ "$record" == "true" ]]; then
artifact="${destination}/$(basename "$source")"
record_artifact "$artifact"
fi
}

# Copies a framework to derived data for use in later build phases
install_framework()
{
if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
local source="${BUILT_PRODUCTS_DIR}/$1"
elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
elif [ -r "$1" ]; then
local source="$1"
fi

local record_artifact=${2:-true}
local destination="${TARGET_BUILD_DIR}"

if [ -L "${source}" ]; then
echo "Symlinked..."
source="$(readlink "${source}")"
fi

install_artifact "$source" "$destination" "$record_artifact"

if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then
# Locate and install any .bcsymbolmaps if present
find "${source}/${BCSYMBOLMAP_DIR}/" -name "*.bcsymbolmap"|while read f; do
install_artifact "$f" "$destination" "true"
done
fi
}

install_xcframework() {
local basepath="$1"
local embed="$2"
shift
local paths=("$@")

# Locate the correct slice of the .xcframework for the current architectures
local target_path=""
local target_arch="$ARCHS"
local target_variant=""
if [[ "$PLATFORM_NAME" == *"simulator" ]]; then
target_variant="simulator"
fi
if [[ "$EFFECTIVE_PLATFORM_NAME" == *"maccatalyst" ]]; then
target_variant="maccatalyst"
fi
for i in ${!paths[@]}; do
if [[ "${paths[$i]}" == *"$target_arch"* ]] && [[ "${paths[$i]}" == *"$target_variant"* ]]; then
# Found a matching slice
echo "Selected xcframework slice ${paths[$i]}"
target_path=${paths[$i]}
break;
fi
done

if [[ -z "$target_path" ]]; then
echo "warning: [CP] Unable to find matching .xcframework slice in '${paths[@]}' for the current build architectures ($ARCHS)."
return
fi

install_framework "$basepath/$target_path" "$embed"
}

SH
contents_by_config = Hash.new do |hash, key|
hash[key] = ''
end
xcframeworks_by_config.each do |config, xcframeworks|
next if xcframeworks.empty?
xcframeworks.each do |xcframework|
slices = xcframework.slices.select { |f| f.platform.symbolic_name == platform.symbolic_name }
dynamic_slices, static_slices = slices.partition { |slice| Xcode::LinkageAnalyzer.dynamic_binary?(slice.binary_path) }
next if dynamic_slices.empty? && static_slices.empty?
unless dynamic_slices.empty?
args = install_xcframework_args(xcframework.path, dynamic_slices, false)
contents_by_config[config] << %( install_xcframework #{args}\n)
end

unless static_slices.empty?
args = install_xcframework_args(xcframework.path, static_slices, true)
contents_by_config[config] << %( install_xcframework #{args}\n)
end

dsyms = PrepareArtifactsScript.dsym_paths(xcframework.path)
dsyms.each do |path|
source = shell_escape("${PODS_ROOT}/#{path.relative_path_from(sandbox_root)}")
contents_by_config[config] << %( install_artifact #{source} "${TARGET_BUILD_DIR}" "true"\n)
end
end
end

script << "\n" unless contents_by_config.empty?
contents_by_config.keys.sort.each do |config|
contents = contents_by_config[config]
next if contents.empty?
script << %(if [[ "$CONFIGURATION" == "#{config}" ]]; then\n)
script << contents
script << "fi\n"
end

script << "\necho \"Artifact list stored at $ARTIFACT_LIST_FILE\"\n"
script << "\ncat \"$ARTIFACT_LIST_FILE\"\n"
script
end

def shell_escape(value)
"\"#{value}\""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shellwords.escape

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you, I knew there must be a better way

end

def install_xcframework_args(root, slices, static)
args = [shell_escape("${PODS_ROOT}/#{root.relative_path_from(sandbox_root)}")]
embed = static ? 'false' : 'true'
args << shell_escape(embed)
slices.each do |slice|
args << shell_escape(slice.path.relative_path_from(root))
end
args.join(' ')
end

class << self
# @param [Pathname] xcframework_path
# the base path of the .xcframework bundle
#
# @return [Array<Pathname>] all found .dSYM paths
#
def dsym_paths(xcframework_path)
basename = File.basename(xcframework_path, '.xcframework')
dsym_basename = basename + '.dSYMs'
path = xcframework_path.dirname + dsym_basename
return [] unless File.directory?(path)

pattern = path + '*.dSYM'
Dir.glob(pattern).map { |s| Pathname.new(s) }
end
end
end
end
end
Loading
0