bazelbuild / rules_apple

Bazel rules to build apps for Apple platforms.
Apache License 2.0
511 stars 268 forks source link

Strip symbols and bitcode from Swift standard libraries when building without bitcode #327

Closed keith closed 4 years ago

keith commented 5 years ago

Currently since rules_apple is incompatible with bitcode, we should mirror Xcode's behavior which is to strip the dylibs of bitcode and symbols to reduce their size. I'm not entirely sure how this affects final download size, but the "strip swift symbols" checkbox when archive from Xcode does this.

More specifically Xcode invokes ipatool with this value, which happens to be a ruby script in Xcode. Here's the relevant logic:

# What we do next depends on whether or not the Mach-O has bitcode.
if shouldCompileBitcode
  # Because we have to compile Mach-Os in dependency order, we defer the actual compilation.
  puts "  #{"|  " * level}.#{machoImage.arch} (compile)" if options.verbosity >= 2
  machoImagesToCompile << machoImage
elsif machoImage.shouldStripBitcode(options)
  # But we can strip bitcode from the binary right away (no need to defer).  We do so in-place.
  puts "  #{"|  " * level}.#{machoImage.arch} (strip)" if options.verbosity >= 2
  CmdSpec.new(locate_tool("bitcode_strip", [machoImage.platform.toolsPath]), [ "-r", "-o", machoImageOutputPath, machoImageInputPath ]).run(level)
  # Strip Swift symbols from Swift dylibs
  if machoImage.machoFile.isSwiftRuntimeDylib && ipa.stripSwiftSymbols
    CmdSpec.new(locate_tool("strip", [machoImage.platform.toolsPath]), [ "-ST", machoImageOutputPath ]).run(level)
  end
else
  # Otherwise, the Mach-O image doesn't have bitcode, so we strip swift symbols.

  # Strip Swift symbols from Swift dylibs
  if machoImage.machoFile.isSwiftRuntimeDylib && ipa.stripSwiftSymbols
    CmdSpec.new(locate_tool("strip", [machoImage.platform.toolsPath]), [ "-ST", "-o", machoImageOutputPath, "-", machoImageInputPath ]).run(level)
    verb = "strip-swift-dylib"
  else
    FileUtils.cp(machoImageInputPath, machoImageOutputPath)
    verb = "copy"
  end

  puts "  #{"|  " * level}.#{machoImage.arch} (#{verb})" if options.verbosity >= 2
end

Mainly the logic here is:

keith commented 5 years ago

For our project for a single architecture (arm64) this has a massive impact on ipa size:

Before:

% du -sh Lyft.ipa
108M    Lyft.ipa
% du -sh SwiftSupport
41M     SwiftSupport

After:

% du -sh Lyft.ipa
81M     Lyft.ipa
% du -sh SwiftSupport
7.8M    SwiftSupport
keith commented 5 years ago

I assume this won't end up impacting app store builds since I assume apple will re-package those no matter what, but this would affect enterprise builds

sergiocampama commented 5 years ago

We might be able to do something inside the swift_stdlib_tool.sh tool, maybe pass an argument to do the stripping of the bitcode support.

brentleyjones commented 4 years ago

We've recently run into this as well. It would be really nice if rules_swift stripped the Swift Symbols.

brentleyjones commented 4 years ago

To be clear, it should be more than swift_stdlib_tool.sh, since that would only strip the dylibs. We should also strip dynamic frameworks.

For example, HTMLEntities is a dynamic framework we use and it's 300kb when copied/stripped by Xcode but 1.4mb when handled by Bazel.

brentleyjones commented 4 years ago

We are now naively doing this with a ipa_post_processor:

#!/bin/bash

set -eo pipefail

readonly work_dir="$1"

# Strip  swiftlibs
find "$work_dir/Payload/" -type f -name "*.dylib" -exec sh -c 'xcrun bitcode_strip -r -o {} {}' \;

# Strip Frameworks
find "$work_dir/Payload/" -type d -name "*.framework" -exec sh -c 'xcrun bitcode_strip -r -o {}/$(basename {} .framework) {}/$(basename {} .framework)' \;
find "$work_dir/Payload/" -type d -name "*.framework" -exec sh -c 'xcrun strip -ST {}/$(basename {} .framework)' \;

The end result of this is a smaller IPA, but had no effect on reported sizes in the App Store.

brentleyjones commented 4 years ago

@thii I think #881 only strips bitcode, correct? What about the swift symbols? In my above example I call

find "$work_dir/Payload/" -type d -name "*.framework" -exec sh -c 'xcrun strip -ST {}/$(basename {} .framework)' \;

to strip Swift Symbols from imported frameworks.

thii commented 4 years ago

I totally forgot about it. rules_apple doesn't currently have something equivalent to Xcode's "strip swift symbols" checkbox. How does adding a new define for it sound? (stripping bitcode doesn't mean also to strip symbols, right?)

brentleyjones commented 4 years ago

A new flag sounds good. I like your approach in #979.

keith commented 4 years ago

Do we need a flag or could it be safe to opt everyone into this?

brentleyjones commented 4 years ago

Xcode has a checkbox, so I would prefer to give the same option?

keith commented 4 years ago

I think in general I would prefer that we don't add options until they have a solid use case. Also that reduces the shear number of options people have to know about, and reduces the diff from upstream. So in general if we can do the "right" thing for everyone I would prefer that until it's no longer the case.