为啥在使用 Flutter 模块构建 iOS 原生应用时修复“Frameworks/Flutter.framework: Permission denied”?
Posted
技术标签:
【中文标题】为啥在使用 Flutter 模块构建 iOS 原生应用时修复“Frameworks/Flutter.framework: Permission denied”?【英文标题】:Why to fix "Frameworks/Flutter.framework: Permission denied" while building iOS native app with flutter module?为什么在使用 Flutter 模块构建 iOS 原生应用时修复“Frameworks/Flutter.framework: Permission denied”? 【发布时间】:2020-01-25 06:18:43 【问题描述】:我创建了一个 ios 单视图应用程序 (hostapp) 并将其转换为 pod 工作区。运行良好。
然后创建了flutter模块(clientapp),提供给iOS团队。运行良好,独立。
关闭了 hostapp 和 clientapp 的 Bitcode,因为它会导致其他问题。
现在通过 podfile 将 clientapp 添加到 hostapp,没有内部代码更改。在 hostapp 中安装 pod 后,我尝试构建它。然后它抛出/Users/iVoIP/Library/Developer/Xcode/DerivedData/hostapp-czxdwjjefjvrnhhdvbmaqeczngbh/Build/Products/Debug-iphonesimulator/hostapp.app/Frameworks/Flutter.framework: Permission denied
/Users/iVoIP/Library/Developer/Xcode/DerivedData/hostapp-czxdwjjefjvrnhhdvbmaqeczngbh/Build/Products/Debug-iphonesimulator/hostapp.app/Frameworks/
chmod 更改为 777
PhaseScriptExecution [CP]\ Embed\ Pods\ Frameworks /Users/iVoIP/Library/Developer/Xcode/DerivedData/hostapp-czxdwjjefjvrnhhdvbmaqeczngbh/Build/Intermediates.noindex/hostapp.build/Debug-iphonesimulator/hostapp.build/Script-4899A4A5D85C0D737A68EFD6.sh (in target 'hostapp' from project 'hostapp')
cd /Users/iVoIP/projects/xcode/hostapp
/bin/sh -c /Users/iVoIP/Library/Developer/Xcode/DerivedData/hostapp-czxdwjjefjvrnhhdvbmaqeczngbh/Build/Intermediates.noindex/hostapp.build/Debug-iphonesimulator/hostapp.build/Script-4899A4A5D85C0D737A68EFD6.sh
mkdir -p /Users/iVoIP/Library/Developer/Xcode/DerivedData/hostapp-czxdwjjefjvrnhhdvbmaqeczngbh/Build/Products/Debug-iphonesimulator/hostapp.app/Frameworks
rsync --delete -av --filter P .*.?????? --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "/Users/iVoIP/projects/xcode/hostapp/Pods/../../clientapp/.ios/Flutter/engine/Flutter.framework" "/Users/iVoIP/Library/Developer/Xcode/DerivedData/hostapp-czxdwjjefjvrnhhdvbmaqeczngbh/Build/Products/Debug-iphonesimulator/hostapp.app/Frameworks"
building file list ... done
Flutter.framework/
Flutter.framework/Flutter
Flutter.framework/Info.plist
Flutter.framework/icudtl.dat
Flutter.framework/_CodeSignature/
Flutter.framework/_CodeSignature/CodeResources
sent 94440038 bytes received 120 bytes 188880316.00 bytes/sec
total size is 94428117 speedup is 1.00
Stripped /Users/iVoIP/Library/Developer/Xcode/DerivedData/hostapp-czxdwjjefjvrnhhdvbmaqeczngbh/Build/Products/Debug-iphonesimulator/hostapp.app/Frameworks/Flutter.framework/Flutter of architectures: armv7 arm64
Code Signing /Users/iVoIP/Library/Developer/Xcode/DerivedData/hostapp-czxdwjjefjvrnhhdvbmaqeczngbh/Build/Products/Debug-iphonesimulator/hostapp.app/Frameworks/Flutter.framework with Identity -
/usr/bin/codesign --force --sign - --preserve-metadata=identifier,entitlements '/Users/iVoIP/Library/Developer/Xcode/DerivedData/hostapp-czxdwjjefjvrnhhdvbmaqeczngbh/Build/Products/Debug-iphonesimulator/hostapp.app/Frameworks/Flutter.framework'
/Users/iVoIP/Library/Developer/Xcode/DerivedData/hostapp-czxdwjjefjvrnhhdvbmaqeczngbh/Build/Products/Debug-iphonesimulator/hostapp.app/Frameworks/Flutter.framework: replacing existing signature
/Users/iVoIP/Library/Developer/Xcode/DerivedData/hostapp-czxdwjjefjvrnhhdvbmaqeczngbh/Build/Products/Debug-iphonesimulator/hostapp.app/Frameworks/Flutter.framework: Permission denied
Command PhaseScriptExecution failed with a nonzero exit code
【问题讨论】:
【参考方案1】:升级到 Flutter 最新版本来解决这个问题。
如果因为x原因无法升级,请打开Flutter SDK的packages/flutter_tools/bin/xcode_backend.sh
并替换为以下代码:
#!/bin/bash
# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
RunCommand()
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
echo "♦ $*"
fi
"$@"
return $?
# When provided with a pipe by the host Flutter build process, output to the
# pipe goes to stdout of the Flutter build process directly.
StreamOutput()
if [[ -n "$SCRIPT_OUTPUT_STREAM_FILE" ]]; then
echo "$1" > $SCRIPT_OUTPUT_STREAM_FILE
fi
EchoError()
echo "$@" 1>&2
AssertExists()
if [[ ! -e "$1" ]]; then
if [[ -h "$1" ]]; then
EchoError "The path $1 is a symlink to a path that does not exist"
else
EchoError "The path $1 does not exist"
fi
exit -1
fi
return 0
BuildApp()
local project_path="$SOURCE_ROOT/.."
if [[ -n "$FLUTTER_APPLICATION_PATH" ]]; then
project_path="$FLUTTER_APPLICATION_PATH"
fi
local target_path="lib/main.dart"
if [[ -n "$FLUTTER_TARGET" ]]; then
target_path="$FLUTTER_TARGET"
fi
local derived_dir="$SOURCE_ROOT/Flutter"
if [[ -e "$project_path/.ios" ]]; then
derived_dir="$project_path/.ios/Flutter"
fi
# Default value of assets_path is flutter_assets
local assets_path="flutter_assets"
# The value of assets_path can set by add FLTAssetsPath to AppFrameworkInfo.plist
FLTAssetsPath=$(/usr/libexec/PlistBuddy -c "Print :FLTAssetsPath" "$derived_dir/AppFrameworkInfo.plist" 2>/dev/null)
if [[ -n "$FLTAssetsPath" ]]; then
assets_path="$FLTAssetsPath"
fi
# Use FLUTTER_BUILD_MODE if it's set, otherwise use the Xcode build configuration name
# This means that if someone wants to use an Xcode build config other than Debug/Profile/Release,
# they _must_ set FLUTTER_BUILD_MODE so we know what type of artifact to build.
local build_mode="$(echo "$FLUTTER_BUILD_MODE:-$CONFIGURATION" | tr "[:upper:]" "[:lower:]")"
local artifact_variant="unknown"
case "$build_mode" in
*release*) build_mode="release"; artifact_variant="ios-release";;
*profile*) build_mode="profile"; artifact_variant="ios-profile";;
*debug*) build_mode="debug"; artifact_variant="ios";;
*)
EchoError "========================================================================"
EchoError "ERROR: Unknown FLUTTER_BUILD_MODE: $build_mode."
EchoError "Valid values are 'Debug', 'Profile', or 'Release' (case insensitive)."
EchoError "This is controlled by the FLUTTER_BUILD_MODE environment variable."
EchoError "If that is not set, the CONFIGURATION environment variable is used."
EchoError ""
EchoError "You can fix this by either adding an appropriately named build"
EchoError "configuration, or adding an appropriate value for FLUTTER_BUILD_MODE to the"
EchoError ".xcconfig file for the current build configuration ($CONFIGURATION)."
EchoError "========================================================================"
exit -1;;
esac
# Archive builds (ACTION=install) should always run in release mode.
if [[ "$ACTION" == "install" && "$build_mode" != "release" ]]; then
EchoError "========================================================================"
EchoError "ERROR: Flutter archive builds must be run in Release mode."
EchoError ""
EchoError "To correct, ensure FLUTTER_BUILD_MODE is set to release or run:"
EchoError "flutter build ios --release"
EchoError ""
EchoError "then re-run Archive from Xcode."
EchoError "========================================================================"
exit -1
fi
local framework_path="$FLUTTER_ROOT/bin/cache/artifacts/engine/$artifact_variant"
AssertExists "$framework_path"
AssertExists "$project_path"
RunCommand mkdir -p -- "$derived_dir"
AssertExists "$derived_dir"
RunCommand rm -rf -- "$derived_dir/App.framework"
local flutter_engine_flag=""
local local_engine_flag=""
local flutter_framework="$framework_path/Flutter.framework"
local flutter_podspec="$framework_path/Flutter.podspec"
if [[ -n "$FLUTTER_ENGINE" ]]; then
flutter_engine_flag="--local-engine-src-path=$FLUTTER_ENGINE"
fi
if [[ -n "$LOCAL_ENGINE" ]]; then
if [[ $(echo "$LOCAL_ENGINE" | tr "[:upper:]" "[:lower:]") != *"$build_mode"* ]]; then
EchoError "========================================================================"
EchoError "ERROR: Requested build with Flutter local engine at '$LOCAL_ENGINE'"
EchoError "This engine is not compatible with FLUTTER_BUILD_MODE: '$build_mode'."
EchoError "You can fix this by updating the LOCAL_ENGINE environment variable, or"
EchoError "by running:"
EchoError " flutter build ios --local-engine=ios_$build_mode"
EchoError "or"
EchoError " flutter build ios --local-engine=ios_$build_mode_unopt"
EchoError "========================================================================"
exit -1
fi
local_engine_flag="--local-engine=$LOCAL_ENGINE"
flutter_framework="$FLUTTER_ENGINE/out/$LOCAL_ENGINE/Flutter.framework"
flutter_podspec="$FLUTTER_ENGINE/out/$LOCAL_ENGINE/Flutter.podspec"
fi
local bitcode_flag=""
if [[ $ENABLE_BITCODE == "YES" ]]; then
bitcode_flag="--bitcode"
fi
if [[ -e "$project_path/.ios" ]]; then
RunCommand rm -rf -- "$derived_dir/engine"
mkdir "$derived_dir/engine"
RunCommand cp -r -- "$flutter_podspec" "$derived_dir/engine"
RunCommand cp -r -- "$flutter_framework" "$derived_dir/engine"
else
RunCommand rm -rf -- "$derived_dir/Flutter.framework"
RunCommand cp -- "$flutter_podspec" "$derived_dir"
RunCommand cp -r -- "$flutter_framework" "$derived_dir"
fi
RunCommand pushd "$project_path" > /dev/null
AssertExists "$target_path"
local verbose_flag=""
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
verbose_flag="--verbose"
fi
local build_dir="$FLUTTER_BUILD_DIR:-build"
local track_widget_creation_flag=""
if [[ -n "$TRACK_WIDGET_CREATION" ]]; then
track_widget_creation_flag="--track-widget-creation"
fi
if [[ "$build_mode" != "debug" ]]; then
StreamOutput " ├─Building Dart code..."
# Transform ARCHS to comma-separated list of target architectures.
local archs="$ARCHS// /,"
if [[ $archs =~ .*i386.* || $archs =~ .*x86_64.* ]]; then
EchoError "========================================================================"
EchoError "ERROR: Flutter does not support running in profile or release mode on"
EchoError "the Simulator (this build was: '$build_mode')."
EchoError "You can ensure Flutter runs in Debug mode with your host app in release"
EchoError "mode by setting FLUTTER_BUILD_MODE=debug in the .xcconfig associated"
EchoError "with the $CONFIGURATION build configuration."
EchoError "========================================================================"
exit -1
fi
RunCommand "$FLUTTER_ROOT/bin/flutter" --suppress-analytics \
$verbose_flag \
build aot \
--output-dir="$build_dir/aot" \
--target-platform=ios \
--target="$target_path" \
--$build_mode \
--ios-arch="$archs" \
$flutter_engine_flag \
$local_engine_flag \
$bitcode_flag
if [[ $? -ne 0 ]]; then
EchoError "Failed to build $project_path."
exit -1
fi
StreamOutput "done"
local app_framework="$build_dir/aot/App.framework"
RunCommand cp -r -- "$app_framework" "$derived_dir"
if [[ "$build_mode" == "release" ]]; then
StreamOutput " ├─Generating dSYM file..."
# Xcode calls `symbols` during app store upload, which uses Spotlight to
# find dSYM files for embedded frameworks. When it finds the dSYM file for
# `App.framework` it throws an error, which aborts the app store upload.
# To avoid this, we place the dSYM files in a folder ending with ".noindex",
# which hides it from Spotlight, https://github.com/flutter/flutter/issues/22560.
RunCommand mkdir -p -- "$build_dir/dSYMs.noindex"
RunCommand xcrun dsymutil -o "$build_dir/dSYMs.noindex/App.framework.dSYM" "$app_framework/App"
if [[ $? -ne 0 ]]; then
EchoError "Failed to generate debug symbols (dSYM) file for $app_framework/App."
exit -1
fi
StreamOutput "done"
StreamOutput " ├─Stripping debug symbols..."
RunCommand xcrun strip -x -S "$derived_dir/App.framework/App"
if [[ $? -ne 0 ]]; then
EchoError "Failed to strip $derived_dir/App.framework/App."
exit -1
fi
StreamOutput "done"
fi
else
RunCommand mkdir -p -- "$derived_dir/App.framework"
# Build stub for all requested architectures.
local arch_flags=""
read -r -a archs <<< "$ARCHS"
for arch in "$archs[@]"; do
arch_flags="$arch_flags-arch $arch "
done
RunCommand eval "$(echo "static const int Moo = 88;" | xcrun clang -x c \
$arch_flags \
-fembed-bitcode-marker \
-dynamiclib \
-Xlinker -rpath -Xlinker '@executable_path/Frameworks' \
-Xlinker -rpath -Xlinker '@loader_path/Frameworks' \
-install_name '@rpath/App.framework/App' \
-o "$derived_dir/App.framework/App" -)"
fi
local plistPath="$project_path/ios/Flutter/AppFrameworkInfo.plist"
if [[ -e "$project_path/.ios" ]]; then
plistPath="$project_path/.ios/Flutter/AppFrameworkInfo.plist"
fi
RunCommand cp -- "$plistPath" "$derived_dir/App.framework/Info.plist"
local precompilation_flag=""
if [[ "$CURRENT_ARCH" != "x86_64" ]] && [[ "$build_mode" != "debug" ]]; then
precompilation_flag="--precompiled"
fi
StreamOutput " ├─Assembling Flutter resources..."
RunCommand "$FLUTTER_ROOT/bin/flutter" \
$verbose_flag \
build bundle \
--target-platform=ios \
--target="$target_path" \
--$build_mode \
--depfile="$build_dir/snapshot_blob.bin.d" \
--asset-dir="$derived_dir/App.framework/$assets_path" \
$precompilation_flag \
$flutter_engine_flag \
$local_engine_flag \
$track_widget_creation_flag
if [[ $? -ne 0 ]]; then
EchoError "Failed to package $project_path."
exit -1
fi
StreamOutput "done"
StreamOutput " └─Compiling, linking and signing..."
RunCommand popd > /dev/null
echo "Project $project_path built and packaged successfully."
return 0
# Returns the CFBundleExecutable for the specified framework directory.
GetFrameworkExecutablePath()
local framework_dir="$1"
local plist_path="$framework_dir/Info.plist"
local executable="$(defaults read "$plist_path" CFBundleExecutable)"
echo "$framework_dir/$executable"
# Destructively thins the specified executable file to include only the
# specified architectures.
LipoExecutable()
local executable="$1"
shift
# Split $@ into an array.
read -r -a archs <<< "$@"
# Extract architecture-specific framework executables.
local all_executables=()
for arch in "$archs[@]"; do
local output="$executable_$arch"
local lipo_info="$(lipo -info "$executable")"
if [[ "$lipo_info" == "Non-fat file:"* ]]; then
if [[ "$lipo_info" != *"$arch" ]]; then
echo "Non-fat binary $executable is not $arch. Running lipo -info:"
echo "$lipo_info"
exit 1
fi
else
lipo -output "$output" -extract "$arch" "$executable"
if [[ $? == 0 ]]; then
all_executables+=("$output")
else
echo "Failed to extract $arch for $executable. Running lipo -info:"
lipo -info "$executable"
exit 1
fi
fi
done
# Generate a merged binary from the architecture-specific executables.
# Skip this step for non-fat executables.
if [[ $#all_executables[@] > 0 ]]; then
local merged="$executable_merged"
lipo -output "$merged" -create "$all_executables[@]"
cp -f -- "$merged" "$executable" > /dev/null
rm -f -- "$merged" "$all_executables[@]"
fi
# Destructively thins the specified framework to include only the specified
# architectures.
ThinFramework()
local framework_dir="$1"
shift
local plist_path="$framework_dir/Info.plist"
local executable="$(GetFrameworkExecutablePath "$framework_dir")"
LipoExecutable "$executable" "$@"
ThinAppFrameworks()
local app_path="$TARGET_BUILD_DIR/$WRAPPER_NAME"
local frameworks_dir="$app_path/Frameworks"
[[ -d "$frameworks_dir" ]] || return 0
find "$app_path" -type d -name "*.framework" | while read framework_dir; do
ThinFramework "$framework_dir" "$ARCHS"
done
# Adds the App.framework as an embedded binary and the flutter_assets as
# resources.
EmbedFlutterFrameworks()
AssertExists "$FLUTTER_APPLICATION_PATH"
# Prefer the hidden .ios folder, but fallback to a visible ios folder if .ios
# doesn't exist.
local flutter_ios_out_folder="$FLUTTER_APPLICATION_PATH/.ios/Flutter"
local flutter_ios_engine_folder="$FLUTTER_APPLICATION_PATH/.ios/Flutter/engine"
if [[ ! -d $flutter_ios_out_folder ]]; then
flutter_ios_out_folder="$FLUTTER_APPLICATION_PATH/ios/Flutter"
flutter_ios_engine_folder="$FLUTTER_APPLICATION_PATH/ios/Flutter"
fi
AssertExists "$flutter_ios_out_folder"
# Embed App.framework from Flutter into the app (after creating the Frameworks directory
# if it doesn't already exist).
local xcode_frameworks_dir=$BUILT_PRODUCTS_DIR"/"$PRODUCT_NAME".app/Frameworks"
RunCommand mkdir -p -- "$xcode_frameworks_dir"
RunCommand cp -Rv -- "$flutter_ios_out_folder/App.framework" "$xcode_frameworks_dir"
# Embed the actual Flutter.framework that the Flutter app expects to run against,
# which could be a local build or an arch/type specific build.
# Remove it first since Xcode might be trying to hold some of these files - this way we're
# sure to get a clean copy.
RunCommand rm -rf -- "$xcode_frameworks_dir/Flutter.framework"
RunCommand cp -Rv -- "$flutter_ios_engine_folder/Flutter.framework" "$xcode_frameworks_dir/"
# Sign the binaries we moved.
local identity="$EXPANDED_CODE_SIGN_IDENTITY_NAME:-$CODE_SIGN_IDENTITY"
if [[ -n "$identity" && "$identity" != "\"\"" ]]; then
RunCommand codesign --force --verbose --sign "$identity" -- "$xcode_frameworks_dir/App.framework/App"
RunCommand codesign --force --verbose --sign "$identity" -- "$xcode_frameworks_dir/Flutter.framework/Flutter"
fi
# Main entry point.
# TODO(cbracken): improve error handling, then enable set -e
if [[ $# == 0 ]]; then
# Backwards-compatibility: if no args are provided, build.
BuildApp
else
case $1 in
"build")
BuildApp ;;
"thin")
ThinAppFrameworks ;;
"embed")
EmbedFlutterFrameworks ;;
esac
fi
您可以找到此代码here (flutter repo)。
了解read here。
我提供的解决方案是here。
【讨论】:
【参考方案2】:看到这个问题:https://github.com/flutter/flutter/issues/40146
--- a/packages/flutter_tools/bin/xcode_backend.sh
+++ b/packages/flutter_tools/bin/xcode_backend.sh
@@ -141,7 +141,7 @@ BuildApp()
mkdir "$derived_dir/engine"
RunCommand cp -r -- "$flutter_podspec" "$derived_dir/engine"
RunCommand cp -r -- "$flutter_framework" "$derived_dir/engine"
- RunCommand find "$derived_dir/engine/Flutter.framework" -type f -exec chmod a-w "" \;
+ RunCommand find "$derived_dir/engine/Flutter.framework" -type f -iname '.h' -exec chmod a-w "" \;
else
RunCommand rm -rf -- "$derived_dir/Flutter.framework"
RunCommand cp -r -- "$flutter_framework" "$derived_dir"
【讨论】:
【参考方案3】:在此路径flutter/packages/flutter_tools/bin
的 Flutter 文件夹中找到 xcode_backend.sh
文件,并在第 140 行将其更改为
RunCommand find "$derived_dir/engine/Flutter.framework" -type f -iname '.h' -exec chmod a-w "" \;
这样就可以了!!
【讨论】:
如你所说改变了,但还是一样。我需要重新创建项目或其他任何东西吗? 更改该行后,删除 derivedData 并清理 xcode 项目...检查一次!如果这不起作用,请发布 xcode_backend.sh 文件内容和颤振医生命令输出! 感谢您的帮助。转移到 master 分支解决了这个问题。 git issue 谢谢!!救了我的一天!就我而言,这条线已经存在,但它仍然无法与 flutter_blue 一起使用。我已将其更改为 xxx 而不是 a-w 并且有效以上是关于为啥在使用 Flutter 模块构建 iOS 原生应用时修复“Frameworks/Flutter.framework: Permission denied”?的主要内容,如果未能解决你的问题,请参考以下文章