which carthage: /usr/local/bin/carthagecarthage version: 0.29.0xcodebuild -version: Xcode 9.4 Build version 9F1027a--no-build?: No--no-use-binaries?: No--use-submodules?: No--cache-builds?: No--new-resolver?: NoCartfile
No cart file. We use the following command to build:
carthage build --platform ios --no-skip-current $verbose_arg
Carthage Output
Actual outcome
Carthage did not respect STRIP_STYLE = non-global option for the project during the build because of the following code:
Carthage/Source/CarthageKit/Xcode.swift:716
...
// Disable the "Strip Linked Product" build
// setting so we can later generate a dSYM
"STRIP_INSTALLED_PRODUCT=NO",
...
As a consequence, the strip within Carthage is executed without -x option and in some cases, we get a huge unexpected binary. Of course, we can strip it later with a proper strip options, but it's an overhead (.dSYM becomes useless, needs time to execute, etc.). Also, these frameworks cannot be stripped properly within application target (for Release, etc.) because we do not have the information about STRIP_STYLE setting for each prebuilt framework at this point.
Expected outcome
Carthage should(?) respect STRIP_STYLE option for frameworks, call strip with proper options and skip .dSYM generation if necessary. From my point of view, .xcconfig itself (or project settings) must be the source of truth.
At least, let's show a warning message or information note during the build if Carthage is going to ignore some inherited options.
You should be using carthage copy-frameworks, that will strip your binaries during copy
Hey @blender, it shouldn't and doesn't. Because of two things:
STRIP_STYLE is unavailable for prebuilt frameworks on this stage because it's usually called within application build phases as I mentioned in the ticket;private func shouldStripDebugSymbols() -> Bool {
return getEnvironmentVariable("COPY_PHASE_STRIP")
.map { $0 == "YES" }.value ?? false
}
strip:arguments: [ "strip", "-S", "-o", binaryURL.path, binaryURL.path]
But for STRIP_STYLE = non-global we have to use -x option as mentioned in the man page for strip:
-x Remove all local symbols (saving only global symbols).
@dive, you’re probably aware, but just for context on the decision to set STRIP_INSTALLED_PRODUCT to NO, quoting @abrindam:
This change is necessary since Carthage moved to using the "archive" command to build. Most products have
STRIP_INSTALLED_PRODUCTset to true, but this does not kick in unless you haveDEPLOYMENT_POSTPROCESSINGset to true as well, and that is false for most products. However, using "archive" instead of "build" magically overridesDEPLOYMENT_POSTPROCESSINGto true, resulting in the product being stripped of debug symbols. As a result dSYM generation produces effectively no information for builds that are built with "archive" (e.g. OSX and iOS device, but not iOS simulator)This also restores the 0.25 behavior that built frameworks are not stripped. Prior to fix, stripping was happening with the archive command, which may have caused additional issues that were not reported. With this change, we are back to the old behavior and stripping is done at framework copy time.
The decision of what flags to include in stripDebugSymbols did consider the -x flag, but a resolution wasn't (at that point) found that could bridge the gap between STRIP_STYLE being known at compile time and having to later strip downloads of that compilation product.
But, I've just had the thought, what if we bridge that gap by writing an extended attribute for STRIP_STYLE on the .framework — which, by my estimation, should get included by /usr/bin/zip — and reading that in stripDebugSymbols and adding flags as a result?
But for STRIP_STYLE = non-global we have to use -x
as to why -x is missing:
https://github.com/Carthage/Carthage/pull/2361#issuecomment-370429346
https://github.com/Carthage/Carthage/pull/2361#issuecomment-370436508
Hey @jdhealy. Thanks for the excellent resume on the issue. Yeah, I'm aware of these conversations that's why I've raised this question again as an enhancement.
I like the idea of setxattr. The solution looks good but a little unobvious that probably does not matter in this case. But we need to be sure that setxattr are stored in Git properly.
Of course, we can try a brute force solution and embed a simple generated .xconfig into the framework on Carthage build phase with all necessary information about overridden options and then read this file on copy-frameworks to configure strip options properly. We'll have to remove this .xconfig from the framework as well before code_sign but it's not an issue at to me. But I like the idea of setxattrmuch more.
@blender, the problem is that we cannot add parameters to copy-frameworks blindly. We have to restore strip options for each framework before applying. See the proposed solution with setxattr above.
But we need to be sure that setxattr are stored in Git properly.
I would say we have have to make use that this property is preserved through zip/unzip/git and whatever other tools used in the process. While I'm ok with the solution I also think that it's the most risky.
Of course, we can try a brute force solution and embed a simple generated .xconfig into the framework on Carthage build phase with all necessary information about overridden options and then read this file on copy-frameworks to configure strip options properly. We'll have to remove this .xconfig from the framework as well before code_sign but it's not an issue at to me.
I'm not 100% this is acceptable given carthage's philosophy, people will suddenly find a rogue file in their projects. However either this or a plist seems more obvious (and more compatible) to me than setxattr.
What is the drawback of just adding -x?
[ "strip", "-S", "-x", "-o", binaryURL.path, binaryURL.path]
I'm not 100% this is acceptable given carthage's philosophy, people will suddenly find a rogue file in their projects. However either this or a
plistseems more obvious (and more compatible) to me thansetxattr.
Yeah, there are several pros and cons for both solutions. But I prefer an obvious way either.
What is the drawback of just adding
-x?
STRIP_STYLE has three different values: all, non-global and debugging; strip parameters depend on it:
all: All Symbols - Completely strips the binary, removing the symbol table and relocation information. Passes flag -s to strip (aka "Save the symbol table entries for the global symbols");non-global: Non-Global Symbols - Strips non-global symbols, but saves external symbols. Passes flag -x to strip (aka "Remove all local symbols (saving only global symbols)");debugging: Debugging Symbols - Strips debugging symbols, but saves local and global symbols. Passes flag -S to strip (aka "Remove the debugging symbol table entries").So, it's too dangerous to enable -x by default because sometimes it leads to unexpected behaviour inside a framework and, more importantly, I prefer the option where we respect developer's settings for the project.
But we need to be sure that
setxattrare stored in Git properly.
Not quite straight from the source, but extended attributes are not encoded into git repositories.
But, consider: users — if made aware that xattrs are being written — can choose to workaround: by using zips from binary or GitHub releases, by documenting it in team guidelines, by forking Carthage, even by not building with Carthage and just using it a resolver and checkout tool.
So, we could make users aware with output when xattrs will be written, and include in the output a hyperlink to a page outlining workaround options.
If that seems too noisy, we could gate by certain calendar date after which it no longer gets output.
Both the line about the change and also that hyperlink to a page outlining workarounds would, of course, be included in the release notes.
But, consider: users — if made aware that xattrs are being written — can choose to workaround: by using zips from
binaryor GitHub releases, by documenting it in team guidelines, by forking Carthage, even by not building with Carthage and just using it a resolver and checkout tool.
Yeah, but at least it will not solve the issue with local pre-built binaries. For example, we build one of our frameworks with Carthage locally and store it within the repository. Right now we have a custom build script that applies our custom strip attributes, but it would be nice to have a universal solution.
So, we could make users aware with output when xattrs will be written, and include in the output a hyperlink to a page outlining workaround options.
It would be more informative if we will notify users about overridden build options by Carthage during the build phase as to me.
I think we can implement an additional parameter for Carthage that will preserve all user-defined build options inside a framework and will use this information during copy-frameworks if its available. The parameter will be optional and will not affect the current behaviour for Carthage. Something like carthage build --preserve-build-options. What do you think, @jdhealy?
Also a bit of philosophy :) As @blender mentioned above while discussing the brute force solution with simple embedded .xcconfig to restore proper attributes in copy-frameworks:
I'm not 100% this is acceptable given carthage's philosophy...
But, from my point of view, the current behaviour contradicts the following postulate described in "Differences between Carthage and CocoaPods" because we override user-defined options explicitly and without any notice:
Ultimately, we created Carthage because we wanted the simplest tool possible—a dependency manager that gets the job done without taking over the responsibility of Xcode, and without creating extra work for framework authors.
Ok, let me take one step back here
It would be more informative if we will notify users about overridden build options by Carthage during the build phase as to me.
As far and I understand the only override Carthage is doing is STRIP_INSTALLED_PRODUCT=NO to get dSYMs generated. This in turn disables whatever strip options you have set because they are not applied.
This change is necessary since Carthage moved to using the "archive" command to build. Most products have STRIP_INSTALLED_PRODUCT set to true, but this does not kick in unless you have DEPLOYMENT_POSTPROCESSING set to true as well, and that is false for most products. However, using "archive" instead of "build" magically overrides DEPLOYMENT_POSTPROCESSING to true, resulting in the product being stripped of debug symbols. As a result dSYM generation produces effectively no information for builds that are built with "archive" (e.g. OSX and iOS device, but not iOS simulator)
So, are we sure there is no way to get the dSYM generated here?
@dive Can you try to archive the framework in question and check if it's stripped by Xcode as you expect and if the dSYM is generated? I have the suspicion that one of the two will not happen.
I am open to anything but imho:
xattr won't survive long.xcconfig in people's frameworks is too me too big of an intrusionTechnically we can also just generate another file that sits along side the frameworks in /Carthage/Build/... where we can shove all sorts of meta. I also though about (ab)using the .version file but it seems wrong to me.
ping
@blender, hey! Sorry for the delay but I have no time to check it right now. Maybe next week. At this stage, we decided to switch back to xcodebuild for our needs because there is too much "magic" here. But I will return to this problem as soon as I have free time and we will think of something. Also, I'm going to check your theories about STRIP_INSTALLED_PRODUCT, DEPLOYMENT_POSTPROCESSING and dSYM generations.
P.S. also I've noticed that Carthage ignores DEBUG_INFORMATION_FORMAT = dwarf option and generates dSYM even when it's explicitly noted that we do not need them for the module.
My background: I'm extremely new to Carthage and tried it a month ago but decided to use /usr/bin/xcrun xcodebuild directly because of this issue.
High-level question: Why is Carthage overriding settings?
Answer (my understanding): Because archive is being used and stripping prevents full dsym generation.
Response: archive can strip and provide a full dsym.
Desire: See OP's @dive Expected outcome above.
Note: carthage copy-frameworks doesn't suffice.
It was previously stated not stripping is necessary due to using archive in order to generate a full dsym.
Most products have STRIP_INSTALLED_PRODUCT set to true [and archive] overrides DEPLOYMENT_POSTPROCESSING to true, resulting in the product being stripped of debug symbols. As a result dSYM generation produces effectively no information for builds that are built with "archive"
I believe this is incorrect. If I'm misunderstanding something it would be helpful to get clarification.
To test, I archived in Xcode and via command line, which I'll focus on. It did its thing, compiled, linked, CreateUniversalBinary.... Then GenerateDSYMFile, some stuff like copy, Touch the framework, Strip the framework, some finishing stuff, then Touch the dsym last. This results in a framework sized as expected (small) because symbols have been stripped out and a full dSYM, which can be used to desymbolicate. Therefore archive can be used with stripping and getting a full dsym. Note, the dsym is generated before stripping.
My understanding of the reasoning for overridding settings is because of flags, so let's examine them.
Strip Style has already been gone over so I won't paste it here, and based on Desire should be respected (I use -x).
DEPLOYMENT_POSTPROCESSING "Deployment Postprocessing"
If enabled, indicates that binaries should be stripped and file mode, owner, and group information should be set to standard values.
COPY_PHASE_STRIP "Strip Debug Symbols During Copy"
Specifies whether binary files that are copied during the build, such as in a Copy Bundle Resources or Copy Files build phase, should be stripped of debugging symbols. It does not cause the linked product of a target to be stripped—use STRIP_INSTALLED_PRODUCT for that.
STRIP_INSTALLED_PRODUCT "Strip Linked Product"
If enabled, the linked product of the build will be stripped of symbols when performing deployment postprocessing.
None of these should affect the dsym generation when the dsym settings are correct.
For transparency I've included my archive command to compare against Carthage. I can provide parts of logs or more info if requested but it has to be specific because I have to remove proprietary info.
Me: /usr/bin/xcrun xcodebuild -project /Users/sheehanm/Documents/repos/.../TEST.xcodeproj -scheme TEST1 -configuration Release -sdk iphoneos archive -archivePath <SOME_PATH>
Carthage: /usr/bin/xcrun xcodebuild -project /Users/sheehanm/Documents/repos/.../TEST.xcodeproj -scheme TEST1 -configuration Release -sdk iphoneos ONLY_ACTIVE_ARCH=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= CARTHAGE=YES archive -archivePath <SOME_PATH> SKIP_INSTALL=YES GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=NO CLANG_ENABLE_CODE_COVERAGE=NO STRIP_INSTALLED_PRODUCT=NO
Why does Carthage take it upon itself to override things that should be controlled by the build settings?
@sheehanm You're absolutely right, in my opinion. Using "archive" does not prevent dSYM generation.
I'm the one who implemented the overriding of setting to facilitate dSYM generation in it's current form. When I did the implementation, I explicitly pointed out there were two directions we could go:
The response was that people wanted the library built by Carthage to always have symbols, regardless of the library's own build settings. So we went with #1.
See https://github.com/Carthage/Carthage/issues/2280#issuecomment-356825509
I think you make a persuasive argument that we should have gone in the other direction, to be honest. Specifically, Carthage has always valued simplicity and avoided magic and we might be trending too far away from that here.
I do think we really need to answer the fundamental question, which is, "do users expect the frameworks build by Carthage to be stripped of symbols, even if the author of that framework has explicitly enabled symbol stripping?" I think we can really only move forward if we answer that question.
Why does Carthage takes it upon itself to override things that should be controlled by the build settings?
You can read yourself the motivation for the overrides.
Carthage has always valued simplicity and avoided magic and we might be trending too far away from that here.
I agree.
So a potential fix to this would be to run https://github.com/Carthage/Carthage/blob/master/Source/CarthageKit/Xcode.swift#L588 only if DEPLOYMENT_POSTPROCESSING is NO ?
BTW Almofire has DEPLOYMENT_POSTPROCESSING=NO and the value does not change while archiving
https://github.com/Carthage/Carthage/issues/2280#issuecomment-356825509
However, DEPLOYMENT_POSTPROCESSING seems to be magically true when you use archive, leading to stripping.
I just tried and I think we're on the right path here. I'll try to make a branch so that you guys can check.
here is the branch: https://github.com/blender/Carthage/tree/fix/2485-respect-strip-style
Don't rely on this in production because. It is not the final version and it would need some heavy reworking to make everything work as expected.
some example issues:
DEPLOYMENT_POSTPROCESSING=NO no dSYMS are generated at all.i386 or x86_64Is this something that can help with reducing framework sizes ? We are having nearly 50 dependencies in our project and start of this month iPA size was 77 MB and end of this month it became 159 MB. We inspected iPA files and all this size is because of frameworks increased by 3 times in span of 6-7 test builds. And this increase is only for Carthage built frameworks. Any idea what could happen ?
Is this something that can help with reducing framework sizes ?
Yes
We inspected iPA files and all this size is because of frameworks increased by 3 times in span of 6-7 test builds.
Not all symbols are stripped. If you have specified a different strip style other than -S (debug symbols) then you could be experiencing a binary size increase.
Changes have happened over 3 versions of Carthage (0.28.0, 0.29.0 and 0.30.1). I don't know how often you build but I'm fairly certain binary size should be constant given a specific version of Carthage .
For 0.30.1 try checking if your application target has "COPY_PHASE_STRIP" to Yes. Also you should be using carthage copy-frameworks
We build almost everyday and it keeps on increasing the binary size with new builds since start of this month. Our app size doubled in 1 month just with this framework sizes and no idea what was wrong or what we had done different to cause this. We are on 0.30.1 version of Carthage. This is the strip options we use in our project.

I think we're all on the same page here acknowledging that the more code is in your frameworks the bigger the size?
That said, if the strip style of your frameworks is "All Symbols" carthage unfortunately does not respect that at the moment. It only strips debug symbols during carthage copy-frameworks if __Strip Debug Symbols During Copy__ a.k.a COPY_PHASE_STRIP=YES is set in the project where you use carthage copy-frameworks (your app most probably). Note that in the paragraph above I've made a distinction between the app and frameworks.
So then, in your app set "Strip Debug Symbols During Copy" to yes.
If that is not enough, you have to strip your frameworks manually for all symbols at the moment.
Most helpful comment
My background: I'm extremely new to Carthage and tried it a month ago but decided to use
/usr/bin/xcrun xcodebuilddirectly because of this issue.High-level question: Why is Carthage overriding settings?
Answer (my understanding): Because archive is being used and stripping prevents full dsym generation.
Response: archive can strip and provide a full dsym.
Desire: See OP's @dive Expected outcome above.
Note:
carthage copy-frameworksdoesn't suffice.It was previously stated not stripping is necessary due to using archive in order to generate a full dsym.
I believe this is incorrect. If I'm misunderstanding something it would be helpful to get clarification.
To test, I archived in Xcode and via command line, which I'll focus on. It did its thing, compiled, linked, CreateUniversalBinary.... Then GenerateDSYMFile, some stuff like copy, Touch the framework, Strip the framework, some finishing stuff, then Touch the dsym last. This results in a framework sized as expected (small) because symbols have been stripped out and a full dSYM, which can be used to desymbolicate. Therefore archive can be used with stripping and getting a full dsym. Note, the dsym is generated before stripping.
My understanding of the reasoning for overridding settings is because of flags, so let's examine them.
Strip Style has already been gone over so I won't paste it here, and based on Desire should be respected (I use -x).
DEPLOYMENT_POSTPROCESSING"Deployment Postprocessing"If enabled, indicates that binaries should be stripped and file mode, owner, and group information should be set to standard values.
COPY_PHASE_STRIP"Strip Debug Symbols During Copy"Specifies whether binary files that are copied during the build, such as in a Copy Bundle Resources or Copy Files build phase, should be stripped of debugging symbols. It does not cause the linked product of a target to be stripped—use
STRIP_INSTALLED_PRODUCTfor that.STRIP_INSTALLED_PRODUCT"Strip Linked Product"If enabled, the linked product of the build will be stripped of symbols when performing deployment postprocessing.
None of these should affect the dsym generation when the dsym settings are correct.
For transparency I've included my archive command to compare against Carthage. I can provide parts of logs or more info if requested but it has to be specific because I have to remove proprietary info.
Me:
/usr/bin/xcrun xcodebuild -project /Users/sheehanm/Documents/repos/.../TEST.xcodeproj -scheme TEST1 -configuration Release -sdk iphoneos archive -archivePath <SOME_PATH>Carthage:
/usr/bin/xcrun xcodebuild -project /Users/sheehanm/Documents/repos/.../TEST.xcodeproj -scheme TEST1 -configuration Release -sdk iphoneos ONLY_ACTIVE_ARCH=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= CARTHAGE=YES archive -archivePath <SOME_PATH> SKIP_INSTALL=YES GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=NO CLANG_ENABLE_CODE_COVERAGE=NO STRIP_INSTALLED_PRODUCT=NOWhy does Carthage take it upon itself to override things that should be controlled by the build settings?