Retrieve branch name inside iOS project - ios

An app I'm building contains a debug view for development purposes and can only be presented if the app is built in the debug environment.
What I would also like to accomplish is that I would like to be able to present the branch name inside that debug mode which the built was taken from. Since various builds are being deployed all the time, it would greatly help as to get a clear idea about which branch the build came from.
I tried some approaches(e.g. Swift scripting but realized it doesn't perform on iOS, etc.) with no luck in the end.
Does anyone have any ideas as to how to achieve this? Since this feature would not make it to the AppStore it's not bounded by Apple's rules so I'm open for private frameworks.

From this article you could use this script in a build phase to insert git information into Info.plist, which you can then query using Swift.
#!/bin/sh
git_version=$(git log -1 --format="%h")
git_branch=$(git symbolic-ref --short -q HEAD)
git_tag=$(git describe --tags --exact-match 2>/dev/null)
build_time=$(date)
git_branch_or_tag="${git_branch:-${git_tag}}"
info_plist="${BUILT_PRODUCTS_DIR}/${EXECUTABLE_FOLDER_PATH}/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion '${git_branch_or_tag}-${git_version}'" "${info_plist}"
/usr/libexec/PlistBuddy -c "Set :BuildTime '${build_time}'" "${info_plist}"

As Edmund Dipple' answer points out, this article shows how to get the git branch into your info.plist. I'm giving a new answer, because it took me a while to understand how it works and maybe it's good to have it complete here on SO.
First, open the app’s Info.plist and add an empty row with key = BuildTime and type = String.
Then, in your project target's Build Phases, click on the plus sign to add a New Run Script phase:
#!/bin/sh
git_version=$(git log -1 --format="%h")
git_branch=$(git symbolic-ref --short -q HEAD)
git_tag=$(git describe --tags --exact-match 2>/dev/null)
git_branch_or_tag="${git_branch:-${git_tag}}"
git_branch_or_tag_version="${git_branch:-${git_tag}}-${git_version}"
info_plist="${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}"
/usr/libexec/PlistBuddy -c "Set :BuildBranch '${git_branch_or_tag_version}'" "${info_plist}"
Finally, the result can be retrieved in Swift like this:
Bundle.main.infoDictionary?["BuildBranch"] as? String
EXTRA 1
If you need the build info more than once, you could write this extension:
import Foundation
extension Bundle {
var buildBranch: String? {
return infoDictionary?["BuildBranch"] as? String
}
var buildTime: String? {
return infoDictionary?["BuildTime"] as? String
}
}
Then you can nicely query:
Bundle.main.buildBranch
and
Bundle.main.buildTime
Just remember to properly unwrap, as these are optionals.
EXTRA 2
I had a problems getting info.plist path right.
Option 1:
info_plist="${BUILT_PRODUCTS_DIR}/${EXECUTABLE_FOLDER_PATH}/Info.plist"
This info.plistpath given in the linked article didn't work in my project.
Option 2:
info_plist="${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}"
In my build phase script, this info.plist path worked reliable.
However, I found that even though there was no error in the build log, the newly set entry got deleted after the first run. It worked only right after clean build folder. I haven't found out why. Maybe a build timing issue?
Option 3:
info_plist="${PROJECT_DIR}/${INFOPLIST_FILE}"
Setting info.plist on the project level was more persistent for me. But it's not a clean solution to have a branch name in this info.plist "template".
Maybe Option 2 is the best.

Related

Xcode does not simply run tests - no scheme and/or test plan

Objective
Running simple unit-test for an iOS app.
Setup
Xcode (Version 14.1)
Sample Test:
import XCTest
#testable import SDKTestAppSwift
class SDKTestAppSwiftTests: XCTestCase {
func testExample() throws {
print("Test: testExample")
// This is an example of a functional test case.
let base = "asdfghjkl123"
XCTAssert(base == "asdfghjkl123")
}
}
Next to the ..
func testExample() throws {
.. Xcode provides a nice little 'start single test'-button.
I used to klick it to start the test, locally (Xcode IDE on mac).
This used to work properly.
Problem
Klicking the 'start single test'-button, an overlay appears:
For the sake of this question to be machine-readable:
There is no scheme and/or test plan that contains every test you are trying to run.
Create a new scheme and/or test plan containing the tests you want to run.
To close the overlay an 'OK'-Button is delivered only.
Misleading solution (?!)
Just before there was no such scheme needed.
I wonder why would that changes 'suddenly'.
From my point ov view Scheme/Testplans are executed 'on startup' of the device-emulator (or likewise), which is not my objective in the first place.
However - creating a TestPlan via:
File > New > File... > TestPlan
Adding the SDKTestAppSwiftTests-class using the +-Button.
... no success ... :-/
Several other attempts
It seems my system is not the only one facing likewise issues.
Problems with naming of test plans
xcodebuild: Tests cannot be run because the test plan “Scheme” could not be read
No matter if there is a 'test-plan' and whatever name I assigned it - this is no solution.
Default test plan
https://developer.apple.com/forums/thread/133495
I seriously do not know how to create a 'default test plan' if it was not the way decribed before.
Quit Xcode and delete 'old configurations'
Xcode: No Scheme
xcodebuild says does not contain scheme
Something about files with old configurations which have to be deleted
In this case I am not sure which file and where to find it exactly.
Conclusion
Even though one of the obove may help you, for me the issue is not solved, yet.
Anyways
Thanks for reading & sharing - any help is appreciated :)
For me I fixed this through my scheme → Edit Scheme → Test → Plus Sign (Add Test Target) and then adding my unit test target.
There is another solution ...
First - Manage Schemes
Second - Create new Scheme
Third -> Celebrate, it's done .. :)

Parameter of the dxgettext command line to "Add likely ignores to ignore po file"

The dxgettext Extract Translations GUI has a switch to Add likely ignores to ignore po file but I don't see the correspondent parameter when calling dxgettext as a command line.
I'm building a batch file doing several tasks when preparing a new release and I would like that the translations extraction step behaves similarly than when called from the UI, moving to a separate file the strings that will clearly not need to be translated.
These are the parameters that I'm using:
dxgettext -b MyProjectPath --delphi --nonascii -r --useignorepo --preserveUserComments
Thank you.
I had the same problem as you do. Tho solve it for my OpenSource image organizer application, I use the following batch file to extract the strings from the sources and remove all strings to be ignored:
c:\Utils\dxgettext -b . --delphi --nonascii --no-wrap -o:msgid -o .
c:\Utils\msgremove default.po -i OvbImgOrganizerLanguageIgnore.po -o OvbImgOrganizerLanguage.pot --no-wrap
del OvbImgOrganizerLanguageDefaultBak.po
ren default.po OvbImgOrganizerLanguageDefaultBak.po
This batch is run with current directory being the source code directory.
That dialog is provided by the GUI ggdxgettext tool.
By the look of it, the dxgettext command line tool does this automatically by default:
item := dom.order.Objects[j] as TPoEntry;
ignoreitem:=ignorelist.Find(item.MsgId);
if ignoreitem=nil then begin
newitem:=TPoEntry.Create;
newitem.Assign(item);
if not IsProbablyTranslatable( newitem,
nil,
nil) then
ignorelist.Add(newitem)
else
FreeAndNil (newitem);
end else begin
ignoreitem.AutoCommentList.Text:=item.AutoCommentList.Text;
end;
But I am not quite sure since I haven't tried to analyze the program flow.
The sources are available on SourceForge, so you can check yourself.

How to issue Message Before Build--or seq problems

I'm trying to add helpful messages for arbitrary builds. If the build fails the user can, for example, install the package with different arguments.
My interface idea is to provide a function, build-with-message, that would be called with something like this:
build-with-message
''Building ${pkg.name}. Alternative invocations are: ..''
pkg
My implementation is based on builtins.seq
build-with-message = msg : pkg :
seq
(self.runCommand "issue-message" {} ''mkdir $out; echo ${msg}'')
pkg;
When I build a package with build-with-message I never see the message. My hunch is that seq evaluates the runCommand far enough to see that a set is returned and moves on to building the package. I tried with deepSeq as well, but a deepSeq build fails on runCommand. I also tried calling out some attributes from the runCommand, e.g.
(self.runCommand "issue-message" {} ''mkdir $out; echo ${msg}'').drvPath
(self.runCommand "issue-message" {} ''mkdir $out; echo ${msg}'').out
My thought being that calling for one of these would prompt the rest of the build. Perhaps I'm not calling the right attribute, but in any case the ones I've tried don't work.
So:
Is there a way to force the runCommand to build in the above scenario?
Is there already some builtin that just lets me issue messages on top of arbitrary builds?
Here's me answering my own question again, consider this a warning.
Solution:
I've in-lined some numbered comments to help with the explanation.
build-with-message = msg : pkg :
let runMsg /*1*/ = self.runCommand "issue-message"
{ version = toString currentTime; /*2*/ } ''
cat <<EOF
${msg}
EOF
echo 0 > $out /*3*/
'';
in seq (import runMsg /*4*/) pkg; /*5*/
Explanation:
runMsg is the derivation that issues the message.
Adding a version based on the current time ensures that the build of runMsg will not be in /nix/store. Otherwise, each unique message will only be issued for the first build.
After the message is printed, a 0 is saved to file as the output of the derivation.
The import loads runMsg--a derivation, and therefore serialized as the path $out. Import expects a nix expression, which in this case is just the number 0 (a valid nix expression).
Now, since the runMsg output will not be available until after it has been built, the seq command will build it (issuing the message) and then build pkg.
Discussion:
I take note of Robert Hensing's comment to my question--this may not be something Nix was not intended for. I'm not arguing against that. Moving on.
Notice that issuing a message like so will add a file to your nix store for every message issued. I don't know if the message build will be garbage collected while pkg is still installed, so there's the possibility of polluting the nix store if such a pattern is overused.
I also think it's really interesting that the result of the runMsg build was to install a nix expression. I suppose this opens the door to doing useful things.

How do I get output files for a given Bazel target?

Ideally, I'd like a list of output files for a target without building. I imagine this should be possible using cquery which runs post-analysis, but can't figure out how.
Here's my output.cquery
def format(target):
outputs = target.files.to_list()
return outputs[0].path if len(outputs) > 0 else "(missing)"
You can run this as follows:
bazel cquery //a/b:bundle --output starlark \
--starlark:file=output.cquery 2>/dev/null
bazel-out/darwin-fastbuild/bin/a/b/something-bundle.zip
For more information on cquery.
What exactly do you mean by "output files" here? Do you mean that you'd like to know the files generated if you build the target on the command line?
At what point would you like to have this information? Do you really want to invoke a bazel query command to acquire this information, or would you like it during analysis? I don't think there's a way, using bazel query, to get the exact expected absolute path of output files (or even the workspace-relative path, for example, bazel-out/foo/bar/baz.txt)
It may be a bit more involved than you want, but Requesting Output Files
has some information about specifying output files in Starlark, with a brief bit about acquiring information about your dependencies' output files (See DefaultInfo
I made a slight improvement to Engene's answer, since a target's output might be multiple:
bazel cquery --output=starlark \
--starlark:expr="'\n'.join([f.path for f in target.files.to_list()])" \
//foo:bar

How to debug slow Swift compile times

I have a Swift SpriteKit project with about 25 smallish files. The time to compile this project is 30-45 seconds! It's pure Swift with no ObjC.
I've watched the compile in the Report Navigator to try to find a single file that's taking the time. But, it's not really a single file. It's always the last file in the list that seems to take all the time. But that file can be different between compiles and still takes all the time.
The step right after the last file is Merge xxx.swiftmodule, which happens quickly, but I'm not sure if because it comes right after the slowness it might be related.
I've searched and tried various approaches to find the culprit. I read this post: Why is Swift compile time so slow? and tried those approaches. I've done a command line build using CTRL-\, but that does not show any useful information about the slowness.
I've gone through my project looking for places where type inference might be getting tripped up, but haven't found much (and really, if that was the case, I'd expect a single file to be the culprit).
Does anyone have any other suggestions for tracking this down? Coming from an Objective-C project that compiles nearly instantly, this is driving me crazy.
EDIT
I worked on this a bit more and am including a screen shot of the build output, noting where the slowness happens. The thing is, if I comment out the code in the slow file, then the file before it in the list becomes the slow one (where it was fine before). If I comment that code out, then the one before that becomes the slow one, etc.
After more digging and debugging, I found the problem. It turns out it actually is type inference, as other posts have suggested. Part of the problem is that I did not notice that the build output in the Report Navigator shows an arrow for files still compiling and a checkmark for those that are done (my color blindness played a role). So I thought they were all finished compiling when one was not.
In any case, I read the article here: GM release of Xcode 6 compile and did a command line build of the entire project, which showed me the file that was the issue. Then, using the CTRL-\ approach noted above, I found the culprit.
It turns out that this line was the issue:
ourWindowCopy.position = CGPoint(x: ((26.5 + theXOffset) * self.multiplierWidth) + (4.0 * CGFloat(randomRow) * 2.0 * self.multiplierWidth), y: ((41.0 + theYOffset) * self.multiplierHeight) + (5.75 * CGFloat(randomColumn) * 2.0 * self.multiplierHeight))
I know it seems a mess - I was playing around with a bunch of different options early on and hadn't yet gone back to simplify. I replaced it with this (and will of course replace the literals as well as simply further):
let floatRandomRow = CGFloat(randomRow)
let floatRandomCol = CGFloat(randomColumn)
let pointX: CGFloat = ((26.5 + theXOffset) * self.multiplierWidth) + (4.0 * floatRandomRow * 2.0 * self.multiplierWidth)
let pointY: CGFloat = ((41.0 + theYOffset) * self.multiplierHeight) + (5.75 * floatRandomCol * 2.0 * self.multiplierHeight)
ourWindowCopy.position = CGPoint(x: pointX, y: pointY)
Now the compile speed is very fast!
I'm not sure there's really any new information here, but wanted to close this out with a solution in case someone runs across it.
There is a hidden option in Swift compiler that prints out the exact time intervals that compiler takes to compile every single function: -Xfrontend -debug-time-function-bodies.
Simple run the following in terminal and analyze results:
xcodebuild -workspace App.xcworkspace -scheme App clean build OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" | grep [1-9].[0-9]ms | sort -nr > culprits.txt
Awesome Brian Irace wrote brilliant article about it Profiling your Swift compilation times.
Building on other answers, the following commands are helpful...
To see a sorted list of functions by build time:
xcodebuild -workspace [Your Workspace Name].xcworkspace -scheme [Your Build Scheme Name] clean build OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" | grep .[0-9]ms | grep -v ^0.[0-9]ms | sort -nr > culprits.txt
To see a sorted list of functions that take over 1s to build:
xcodebuild -workspace [Your Workspace Name].xcworkspace -scheme [Your Build Scheme Name] clean build OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" | grep [1-9].[0-9]ms | sort -nr > culprits.txt
You can get a list of scheme names like so (this command runs a clean first):
xcodebuild -workspace [Your Workspace Name].xcworkspace -list
Helpful Articles about why your build times may be long:
https://medium.com/#RobertGummesson/regarding-swift-build-time-optimizations-fc92cdd91e31
https://medium.com/swift-programming/swift-build-time-optimizations-part-2-37b0a7514cbe
https://medium.com/compileswift/swift-build-time-reanalysis-optimisations-a875a4168f7a
Swift time compilation
Swift Compiler Performance
Measure and Tools
You should clean and build your project to have a better results for comparing.
Xcode Build time output.
//Terminal command
defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES
Output:
Build With Timing Summary.
//Xcode
Product -> Perform Action -> Build With Timing Summary
Output:
Report Navigator -> <build> -> Recent
Type check warnings
Trigger a warning in Xcode for any function/expressions which is more than a user-defined limit to type-check
Build Settings -> Other Swift Flags(OTHER_SWIFT_FLAGS)
-Xfrontend -warn-long-function-bodies=200
-Xfrontend -warn-long-expression-type-checking=200
Output:
Instance method 'foo()' took 200ms to type-check (limit: 200ms)
Record a time which is taken to compile every function/expression
Build Settings -> Other Swift Flags(OTHER_SWIFT_FLAGS)
-Xfrontend -debug-time-function-bodies
-Xfrontend -debug-time-expression-type-checking
Output:
Report Navigator -> <build> -> Expand All Transcripts
-Xfrontend -debug-time-function-bodies
-Xfrontend -debug-time-expression-type-checking
BuildTimeAnalyzer-for-Xcode is based on this flag. Also it has interesting column which is called Occurrences - how many times compiler is type checking it(e.g. lazy properties, closures)
XCLogParser analize logs
xclogparser parse --project Experiments2 --reporter html --rootOutput "/Users/User/Desktop/temp"
You are able to open Xcodelog with .xcactivitylog extension by changing to .zip and unzip it. After it you can open it as a text file
/Users/User/Library/Developer/Xcode/DerivedData/Experiments2-fejelsaznlrfewdtvyfyhhbgxlwl/Logs/Build/00D3B76E-B61B-4FB8-AE0B-1FAD5AF3F452.xcactivitylog
XCMetrics(by Spotify) as a next step of collecting log data
Notes:
-debug-time-compilation was removed
Experiments were made on Xcode v13.3.1. Do not forget to clean project before measure
You are able to set flag for specyfic target(e.g. in modularized project)

Resources