The problem

The hybrid solutions provided by Flutter are directly dependent on the Flutter engineering and the Flutter environment. Non-flutte team members cannot develop without the Flutter environment, which increases the cost of team cooperation.

expect

The default mix of Flutter depends not only on the Flutter products in the Flutter project, but also on the Xcode_backend. sh script in the Flutter SDK. We want to be able to make sure that when projects are mixed, team members who are not developing a Flutter can be completely removed from the flutter without the need for the Flutter project code and install the Flutter environment. The team members who wrote Flutter were able to mix them up to make it easier to develop and debug.

With this goal in mind, let’s analyze the blending process step by step.

Clarify rely on

What does the iOS project rely on with Flutter

Look at the picture, look at the picture, this is the Runner workspace generated by Flutter compilation. All iOS dependent Flutter products are in this Flutter folder. To introduce these guys in turn:

  • Symlinks package of a Flutter is an index of folders that point to packages in the local PUB cache. Each package contains an iOS native POD repository in the package’s iOS folder. Therefore, the dependencies of the Flutter package can be directly imported into the POD.

  • App. Framework compiled from Dart code of the Flutter project is just the framework. When integrating, you can make your own local POD library or directly copy into the APP package, and then sign.

  • AppFrameworkInfi. Plist some irrelevant configuration information about a Flutter, ignored

  • The Engine Flutter rendering engine is also a local POD repository

  • Flutter_assets resource files, images, etc. of Flutter can be copied into app package during integration

  • FlutterPluginRegistrant Fluttter tripartite package registration code, which is required to import tripartite packages, is also a local POD repository

  • Generated. Xcconfig Contains path information and configuration information about Flutter. The entire file is imported into the various *.xcConfig profiles of the iOS project. This configuration information is used in the flutter compilation embedded script xcode_backend.sh introduced in Xcode runscript. Of course, you can also modify the script to remove the dependency on this file.

  • The podHelper. rb ruby script contains a cocoapod hook that imports all of the local library dependencies of Flutter during pod installation and writes “import Generated. Such as # include ‘… /Generated.xcconfig’);

Script analysis

In essence, the prerequisite for clarifying dependencies is to read the script, which is posted in advance so that you can better understand the process when analyzing the script.

The default mix solution flow is

  1. Add the script to your Podfile
#Flutter engineering path
flutter_application_path = 'flutter_project_dir'
# Read Podhelper.rb Ruby code in the current directory
eval(File.read(File.join(flutter_application_path, '.ios'.'Flutter'.'podhelper.rb')), binding)
Copy the code
  1. Add the Run Script script
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build 
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
Copy the code

Then pod install.

The secret of everything ㊙️ is in these two scripts.

Analysis podhelper. Rb

The Ruby script is just over seventy lines long, and since not everyone is familiar with Ruby scripts, I commented it out in detail:

Parse file contents into dictionary array
# file content format A=B newline C=D type
# if A = B
# C=D
# 1.
# {"A"="B","C"="D"}

def parse_KV_file(file, separator='=')
    file_abs_path = File.expand_path(file)
    if! File.exists? file_abs_pathreturn [];
    end
    pods_array = []
    skip_line_start_symbols = ["#"."/"]
    File.foreach(file_abs_path) { |line|
        next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
        plugin = line.split(pattern=separator)
        if plugin.length == 2
            podname = plugin[0].strip()
            path = plugin[1].strip()
            podpath = File.expand_path("#{path}", file_abs_path)
            pods_array.push({:name => podname, :path => podpath});
         else
            puts "Invalid plugin specification: #{line}"
        end
    }
    return pods_array
end


This is a function that resolves the Generated. Xcconfig file in the iOS dependency directory Generated by the Flutter project
The # FLUTTER_ROOT directory is the root of the FLUTTER SDKf that you installed
def flutter_root(f)
    generated_xcode_build_settings = parse_KV_file(File.join(f, File.join('.ios'.'Flutter'.'Generated.xcconfig')))
    if generated_xcode_build_settings.empty?
        puts "Generated.xcconfig must exist. Make sure `flutter packages get` is executed in ${f}."
        exit
    end
    generated_xcode_build_settings.map { |p|
        if p[:name] == 'FLUTTER_ROOT'
            return p[:path]
        end
    }
end


# The code entry is here
# flutter project directory, if there is no value, go up two steps to the folder (i.e. the whole iOS project generated by flutter)
flutter_application_path ||= File.join(__dir__, '.. '.'.. ')
The framework directories, engine libraries, compiled code libraries, and almost all iOS project dependencies that Flutter generates are placed here
framework_dir = File.join(flutter_application_path, '.ios'.'Flutter')

# Flutter Engine directory
engine_dir = File.join(framework_dir, 'engine')

If the engine directory does not exist, go to the Flutter SDK directory and make a copy. The engine is a local POD library
# file.join, join File directories
if! File.exist? (engine_dir)This is the debug version of the Flutter engine directory. The last level of release is "ios-release" and the ios-profile version is ios-profile
    debug_framework_dir = File.join(flutter_root(flutter_application_path), 'bin'.'cache'.'artifacts'.'engine'.'ios')
    FileUtils.mkdir_p(engine_dir)
    FileUtils.cp_r(File.join(debug_framework_dir, 'Flutter.framework'), engine_dir)
    FileUtils.cp(File.join(debug_framework_dir, 'Flutter.podspec'), engine_dir)
end

# This should be familiar to everyone
Load the POD library of the Flutter engine
pod 'Flutter', :path => engine_dir
Load the register library of the Flutter tripartite library
pod 'FlutterPluginRegistrant', :path => File.join(framework_dir, 'FlutterPluginRegistrant')

A folder containing shortcuts to all three libraries of flutter, eventually indexed to the directory of each library in the PUB cache
symlinks_dir = File.join(framework_dir, '.symlinks')
FileUtils.mkdir_p(symlinks_dir)
# Parse the.flutter-plugins file to retrieve the tripartite libraries currently used by the Flutter project
plugin_pods = parse_KV_file(File.join(flutter_application_path, '.flutter-plugins'))
Load every POD library used by the current project
plugin_pods.map { |r|
    symlink = File.join(symlinks_dir, r[:name])
    FileUtils.rm_f(symlink)
    File.symlink(r[:path], symlink)
    pod r[:name], :path => File.join(symlink, 'ios')}# Change ENABLE_BITCODE to NO for all pod libraries, including pod libraries referenced by native code
# import the Generated. Xcconfig file in the. Xcconfig file of each POD library
This file contains a list of variables that can be used by flutter. This file is used in the xcode_backend.sh script
post_install do |installer|
    installer.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings['ENABLE_BITCODE'] = 'NO'
            xcconfig_path = config.base_configuration_reference.real_path
            File.open(xcconfig_path, 'a+') do |file|
                file.puts "#include \"#{File.realpath(File.join(framework_dir, 'Generated.xcconfig'))}\""
            end
        end
    end
end

Copy the code

To summarize, this Ruby script does a few things

  • Introducing Flutter engine
  • Introduce the registration code for the Flutter tripartite library
  • Import all tripartite libraries of Flutter
  • Write an import to the Generated. Xcconfig file in the configuration file of each POD library
  • Change the ENABLE_BITCODE of the POD library

So far, the Dart code base and the resources that flutter introduces are missing, which are implemented in the xcode_backend.sh script. This script is available in the Packages/Flutter_tools /bin of the Flutter SDK

Take a look at all the code again, with detailed comments:

#! /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 "The $1" > $SCRIPT_OUTPUT_STREAM_FILE
  fi
}

EchoError() {
  echo "$@"> 1 & 2}Verify that the resource in the path exists
AssertExists() {
  if[[!-e "The $1"]].then
    if [[ -h "The $1"]].then
      EchoError "The path The $1 is a symlink to a path that does not exist"
    else
      EchoError "The path The $1 does not exist"
    fi
    exit- 1fi
  return0}BuildApp() {
  #xcode project root, SOURCE_ROOT this variable comes from the Xcode project environment
  local project_path="${SOURCE_ROOT}/.."

#FLUTTER_APPLICATION_PATH flutter project directory from the Generated. Xcconfig file
# If FLUTTER_APPLICATION_PATH is not empty, assign to project_PATH
  if [[ -n "$FLUTTER_APPLICATION_PATH"]].then
    project_path="${FLUTTER_APPLICATION_PATH}"
  fi

The application entry file directory for flutter
  local target_path="lib/main.dart"
  if [[ -n "$FLUTTER_TARGET"]].then
    target_path="${FLUTTER_TARGET}"
  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.
Get the compile mode
Set variables according to the compile mode
# artifact_variant is used when copying the FLUTTER engine later, to determine the version of the engine
The Flutter engine is already integrated in PodHelper.rb, but depending on the version introduced by the build mode of the Flutter project itself, it may be different
# So in this script I want to reintroduce the engine of the corresponding pattern

  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 varaible."
      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 appriate 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- 1fi

  # Detailed address of the Flutter engine
  local framework_path="${FLUTTER_ROOT}/bin/cache/artifacts/engine/${artifact_variant}"

  AssertExists "${framework_path}"
  AssertExists "${project_path}"

The target directory of flutter
  local derived_dir="${SOURCE_ROOT}/Flutter"
  if [[ -e "${project_path}/.ios"]].then
    derived_dir="${project_path}/.ios/Flutter"
  fi
  RunCommand mkdir -p -- "$derived_dir"
  AssertExists "$derived_dir"


  RunCommand rm -rf -- "${derived_dir}/App.framework"

  local local_engine_flag=""
  local flutter_framework="${framework_path}/Flutter.framework"
  local flutter_podspec="${framework_path}/Flutter.podspec"

If a local engine exists, the engine will use this path, and subsequent copy engines will copy from this directory
  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- 1fi
    local_engine_flag="--local-engine=${LOCAL_ENGINE}"
    flutter_framework="${LOCAL_ENGINE}/Flutter.framework"
    flutter_podspec="${LOCAL_ENGINE}/Flutter.podspec"
  fi

Copy the Flutter engine to the dependent directory
  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"
    RunCommand find "${derived_dir}/engine/Flutter.framework" -type f -exec chmod a-w "{}" \;
  else
    RunCommand rm -rf -- "${derived_dir}/Flutter.framework"
    RunCommand cp -r -- "${flutter_framework}" "${derived_dir}"
    RunCommand find "${derived_dir}/Flutter.framework" -type f -exec chmod a-w "{}" \;
  fi

Change the script execution directory to the FLUTTER project to execute the flutter command
  RunCommand pushd "${project_path}" > /dev/null

  AssertExists "${target_path}"

Whether verbose log output is required
  local verbose_flag=""
  if [[ -n "$VERBOSE_SCRIPT_LOGGING"]].then
    verbose_flag="--verbose"
  fi
# flutter build directory
  local build_dir="${FLUTTER_BUILD_DIR:-build}"

Whether to detect weidget creation, release mode does not support this parameter
  local track_widget_creation_flag=""
  if [[ -n "$TRACK_WIDGET_CREATION"]].then
    track_widget_creation_flag="--track-widget-creation"
  fi

Execute flutter build aot ios... Compile the Dart code into App. framework
Generate dSYM files
# Strip debug symbol table

Static const int Moo = 88; This code is called App.Framework,
Use JIT mode snapshots directly
  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- 1fi
    Execute the build command for flutter

    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}"                                                 \
      ${local_engine_flag}                                                  \
      ${track_widget_creation_flag}

    if[[$?-ne0]];then
      EchoError "Failed to build ${project_path}."
      exit- 1fi
    StreamOutput "done"

    local app_framework="${build_dir}/aot/App.framework"

    RunCommand cp -r -- "${app_framework}" "${derived_dir}"

    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"
Generate dSYM files
    RunCommand xcrun dsymutil -o "${build_dir}/dSYMs.noindex/App.framework.dSYM" "${app_framework}/App"
    if[[$?-ne0]];then
      EchoError "Failed to generate debug symbols (dSYM) file for ${app_framework}/App."
      exit- 1fi
    StreamOutput "done"

    StreamOutput "├ ─ Stripping the debug symbols..."
# Strip debug symbol table
    RunCommand xcrun strip -x -S "${derived_dir}/App.framework/App"
    if[[$?-ne0]];then
      EchoError "Failed to strip ${derived_dir}/App.framework/App."
      exit- 1fi
    StreamOutput "done"

  else
    RunCommand mkdir -p -- "${derived_dir}/App.framework"

    # Build stub for all requested architectures.
    local arch_flags=""
    Get schema parameters for the current debug mode
    The emulator is x86_64
    The real machine depends on the actual architecture ARMV7 or ARM64
    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} \ -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

    Embedded # Info. Plist
  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

Debug mode contains a JIT compiled snapshot of the Flutter code. Dart code is not included in app.framework
  StreamOutput "├ ─ Assembling Flutter resources..."
  RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics             \
    ${verbose_flag}                                                         \
    build bundle                                                            \
    --target-platform=ios                                                   \
    --target="${target_path}"\ -${build_mode}                                                         \
    --depfile="${build_dir}/snapshot_blob.bin.d"                            \
    --asset-dir="${derived_dir}/flutter_assets"                             \
    ${precompilation_flag}                                                  \
    ${local_engine_flag}                                                    \
    ${track_widget_creation_flag}

  if[[$?-ne0]];then
    EchoError "Failed to package ${project_path}."
    exit- 1fi
  StreamOutput "done"
  StreamOutput " └─Compiling, linking and signing..."

  RunCommand popd > /dev/null

  echo "Project ${project_path} built and packaged successfully."
  return0}# Returns the CFBundleExecutable for the specified framework directory.
GetFrameworkExecutablePath() {
  local framework_dir="The $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="The $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="The $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.
# Mainly do the following things:
# duplicate flutter_implies to app pack
Copy the Flutter engine to the app package
# Copy the dart code to compile the product app.framework into the app package
# Sign both frameworks
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}"

  # Copy the flutter_assets to the Application's resources.
  AssertExists "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/"
  RunCommand cp -r -- "${flutter_ios_out_folder}/flutter_assets" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/"

  # 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 function entry
# This is easy to understand with the script in Xcode Run srcript
# compile, embed
#"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
#"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed

if [[ $# == 0 ]]; then # Execute the BuildApp function with no arguments
  # Backwards-compatibility: if no args are provided, build.
  BuildApp
else Otherwise, execute the case statement
  case The $1 in
    "build")
      BuildApp ;;
    "thin")
      ThinAppFrameworks ;;
    "embed")
      EmbedFlutterFrameworks ;;
  esac
fi

Copy the code

Again, to sum up, the shell script does the following

  • Compile the Dart code into App.framework (not debug mode), compilestatic const int Moo = 88;For app. framework (guess this line of code is JIT/AOT mode switch flag)
  • Re-import the corresponding mode version of the Flutter engine (debug/profile/release)
  • Compile flutter resources (Flutter_implies), if the debug resources contain JIT mode code snapshots
  • Embed resources, frames, and signatures into the iOS app package

This section is mostly posted code, if it is simple to talk about the process may not be very good to understand, detailed we still directly read the script. If you can’t read the script, you can get an idea by looking at the comments.

plan

With the dependencies and processes sorted out, it’s finally time to talk about solutions. Look back and hope together

  • Non-flutter developers can be completely removed from the flutter environment
  • Flutter developers still rely on flutter in the same way

At this point, we still want to be able to do a little better, is able to achieve the two modes switch. I’ve drawn a rough picture, but you’ll see.

The approximate solution is as follows:

  • Completely free from the Flutter environment :(solid line flow section in figure) use scripts to remove all dependent compilation results from the Flutter project and place them in the iOS project directory. IOS Native relies directly on this directory and is no longer compiled, which means that it can be removed from the Flutter environment. (The environment can be just release, because the environment that has escaped the Flutter does not debug the Flutter code.)

  • Direct dependence on Flutter engineering :(dotted line flow in figure) when pod is directly dependent on Flutter engineering, its dependence on Flutter refers directly to Flutter engineering. Xcode_backend. sh will recompile the Flutter code, Flutter resources and embed the APP. The Flutter engine will also re-embed a version of the corresponding mode.

Problems existing in the scheme

Directly dependent on the Flutter project, this is much the same, directly or indirectly directed to the Flutter project. The focus here is on completely disentanglement from the Flutter environment.

Remote Flutter scheme represented by salted fish

The salty Fish team itself mentioned the following problems

  1. The remote dependencies library was not updated in time because the Flutter project was updated.
  2. It is easy to forget to update remote dependencies during build integration, resulting in a build that does not integrate the latest Flutter functionality.
  3. When Flutter is developed on multiple lines simultaneously, version management is chaotic and remote libraries may be overwritten.
  4. At least one student is required to continue to follow up the release, and the labor cost is high. In view of these problems, we introduced our team’s CI automation framework to solve them from two aspects: (We will share a post about CI automation framework later.) On the one hand, automation reduces labor costs and reduces human error through automation. The other side is to do a good version control, automated form of version control. Specific operations: First, the compilation and release of the remote library corresponding to the Flutter project will be automatically completed before the construction of the pure Native project every time, without manual intervention in the whole process. Secondly, during the development and test phase, a five-segment version number is used, with the last digit automatically increasing. This ensures that the version numbers of all Flutter libraries developed in parallel during the test phase will not conflict. Finally, in the release stage, a three-paragraph or four-paragraph version number should be adopted, which can be consistent with the APP version number to facilitate follow-up tracing.
Our plan

This solution resolves the problem of native dependencies on the Flutter library version number compared to the salted fish solution. Flutter dependencies placed in the native are directly attributed to the native management and do not require a separate version. This dependency takes the code published by the Flutter developer, usually the latest compilation of Flutter code for the corresponding branch. If iOS dev corresponds to Flutter dev, version-management will be much simpler.

However, there are also other problems with Flutter, such as its dependence on update and delay, which need further investigation and practice.

Welcome big brother to give directions, if you have any questions, you can put forward in the comments to discuss together.


Read more:

[] Flutter trial report (www.jianshu.com/p/b32538e68…

How does Flutter translate into iOS application packages?


Reference article: Fultter best Practices for continuous Integration in hybrid engineering