I have a common piece of ant code that should run for all targets except one. How is this possible, given that this code is outside any target and I cannot get the target name at this point?
I also am not allowed to pass any extra properties along with this specific target, so that I could wrap this code with an "if" based on whether this property is set.
Create a macro which embeds the common piece of code outside any target. And then call this macro, only when it is required. Since you already know that one target should not run this piece code, don't call the macro from that specific target.
Related
I've got a mixed ObjC/Swift codebase with a very mixed-up structure, and the tests no longer run under Xcode 10.2 (details below). I'm trying to make the minimum structural changes to get tests back, so that I have a safety net for the refactoring that will come next. After taking what seems to me to be the obvious first step, the tests fail to build (details again below).
I'm interested in any of
solutions for the original problem (to get tests running again in the messy setup)
solutions for the problem after refactoring (to get refactored tests building in the somewhat tidier setup)
suggestions for build settings to verify or include in the description here, to get clearer on what's going wrong
I'm not interested in advice about how to structure a fresh project or create new targets with more sensible configurations: extracting code from these targets is also non-trivial, so I really need test coverage back before I start any refactoring.
Original situation (very messy)
project myCompany
app target app which builds module MyCompany
contains both Swift and ObjC code, with dependencies both ways
Defines Module = Yes, Product Module Name = MyCompany
test target myCompanyTests
Defines Module = No, Product Module Name = MyCompany
also contains both Swift and ObjC code
CocoaPods for external dependencies, also a bunch of internal Swift modules with dependencies managed by hand
Test files are included only in the myCompanyTests target, but many code files are included in both app and myCompanyTests targets. I think this and the two targets defining the same module name was probably in order to avoid having to import the app target into tests, because of problems with Swift/ObjC interop (see the next section). Building tests produces warnings about classes implemented in two places:
objc[9724]: Class _TtC12MyCompany12DiaryFetcher is implemented in both
/Users/tikitu/Library/Developer/CoreSimulator/Devices/556CAC28-B90B-4B6B-A87C-1A1450795051/data/Containers/Bundle/Application/495F33C2-F7FC-4AE6-B3FE-6908D6361B55/mycompany-develop.app/mycompany-develop (0x10d724060)
and
/Users/tikitu/Library/Developer/Xcode/DerivedData/mycompany-bifciplpbqaeqqdrmhivpjgnojri/Build/Products/Debug-iphonesimulator/mycompany-develop.app/PlugIns/myCompanyTests.xctest/myCompanyTests (0x13566dc38).
One of the two will be used. Which one is undefined.
As of Xcode 10.2, myCompanyTests builds successfully but running the tests fails with an EXC_BAD_ACCESS in swift_checkMetadataState somewhere inside the UIApplicationMain call. My guess is that this is related to the module-name/files-in-both-targets shenanigans.
First attempt at "fix the obvious mistakes"
As a first attempt to tidy things up somewhat I've done the following:
Remove all non-test files from the myCompanyTests target
Rename myCompanyTests Product Module Name to MyCompanyTests
Add #testable import MyCompany in lots of swift tests
Then I start running into Swift/ObjC interop problems, because I need to call Swift code in the app target from ObjC code in the test target. Things I've tried:
#import "MyCompany-Swift.h" in an objc .m test file
'MyCompany-Swift.h' file not found
#import <MyCompany-Swift.h> in an objc .m test file
'MyCompany-Swift.h' file not found
#import <MyCompany/MyCompany-Swift.h> in an objc .m test file (yes you can see I don't actually understand this mechanism)
'MyCompany/MyCompany-Swift.h' file not found
#import "MyCompanyTests-Swift.h" in all objc .m test files that need access to the app target
the generated file MyCompanyTests-Swift.h includes a line #import MyCompany; which looks very promising!
but that line fails with the error Module 'MyCompany' not found
Especially this last one looks suspicious to me, as I would expect the Xcode generated file should "just work". I guess I've overridden some user setting somewhere that's getting in the way: I'd be delighted with any suggestions for places to check.
I won't accept this (yet) as it's incomplete information, but I believe at least that I know why the fixed-up version is failing: the location of MyCompany-Swift.h isn't in the unit-test target's header search paths, because it's the app target. Reference e.g. https://github.com/jeremy-w/MixedLanguageTesting (which suggests adding an ad-hoc entry to the header search paths).
I believe the issue to be that only a framework target auto-creates the swift-to-objc header file Module-Swift.h: neither the app target nor a test target appear to do so.
I successfully resolved these issues by creating two new framework targets
* MyCompanyStuffThatUsedToBeInApp (containing everything that used to be in the app target except main.h), which does bidirectional swift/objc interop and
* MyCompanyTesting (a framework target imported by the tests, which likewise does bidirectional swift/objc interop for test-only code).
There may still be simpler ways to tidy this up, but this one at least is proven to work.
Context
We have a big project, so the application is separated from some common code. Each one goes in its own repo, and the app uses the common code as a lib via cocoapods.
Situation
Now, we need to toggle some very specific functionality, contained in the lib. But the condition to enable/disable it is a MACRO defined in the application-prefix.pch file (I know, not cool).
Problem
The code in the lib (included as a Pod) doesn't "see" the MACRO defined in the pch file. So, we can't actually toggle this behaviour.
Sum-Up
The only alternative I've seen is to add a post-install hook in our Podfile, but it seems to mostly work with MACROS defined in the Build Settings; while in this case the MACRO is defined in the pch file. And I really really really don't want to add the values in the lib or in the podfile.
Any advice or idea to point me in the right direction will be greatly appreciated!
There's no way to use a macro of an application project in a the source code of a Cocoapod - this is because the entire pod target will be build before the application target even starts building, so there's no way for the pod to know about the macro.
My first instinct would be to use a post-install hook like you mentioned. However, you could also add the macro you need in another pod, and have every target, including pods and the main application, depend on that new pod.
Another, cleaner way: design the pod code to be able to operate with or without the specific functionality that you want to gate on (via, say, a boolean flag). Then, depending on the value of the macro in the application, choose to pass that flag to the pod code as true or false.
I have a custom Framework I use within my normal App target as well as the corresponding UnitTest target. Turns out that confuses the runtime in such way that it is unable to choose the correct implementation since it has multiple choices:
objc[35580]: Class AClass is implemented in both ../MyApp.app/MyApp and ../MyApp.app/MyAppTests. One of the two will be used. Which one is undefined.
That of course leads to weird behavior if you try to check an object's class hierarchy or do any other class related checks.
So it boils down to the following two questions:
I don't see similar logs for e.g. UIKit components, but this framework is also linked to both targets. Have I incorrectly compiled the framework?
Is it just a trivial configuration issue I missed?
PS: I already checked similar posts like 1 or 2, but although everything is configured as described, the problem remains.
You have added the dependency framework to the Tests target. This is flawed thinking. Since your primary application ALSO exports the SAME framework you will receive warnings about duplicated symbols for any classes found in the framework.
By removing your framework from the test target you can resolve the warnings. Remember, you're not losing any functionality by not linking against the same framework in the test target. Trust me, your code is still there.
I ran into a similar problem here: Xcode5: creating new testing target
The key is to create a new unit testing bundle, point it to your original target, and then don't do anything else! If you start including frameworks and source files into the test target, it'll generate these linking errors. The test target is supposed to "inject" the test classes into the actual target, not create a new separate target on it's own. So you just need to import the header files in your test class, and write your test cases.
I think the bundle should only "read" the framework's header files but not build the sources and leave that task to the App (remove the Framework .m files from the UnitTest target).
Right now the App and the UnitTest are both building the Framework, thus the duplicated classes.
My project uses a Settings.h to store common configuration settings like the server url etc. I have been tasked to create a new app from this code where only few parameters such as the server url , icon etc are different.
I have a new target in my xcode workspace for this. I have 2 Settings.h in separate folders like
awesomeProject/Settings/Settings.h
oldProject/Settings/Settings.h
The Settings.h is included in a few places in the project (not loaded via .pch). I have tried setting header search path for both the targets and this didn't work (as in the compiling awesomeProject target included the oldProject's Settings.h).
Is there a way to #include Settings.h based on the target without resorting to sprinkling #ifdef .. #endif constructs ?
If I understand right and you have separate targets to that need to include different header files so you should be able to set the 'Header Search Paths' differently and get the correct file. You said you tried this but you might want to try doing a clean and full rebuild.
I would try to leverage preprocessor to accomplish your goal. One option is to define in build settings for each target macro SETTINGS_H, which will be "awesomeProject/Settings/Settings.h" or "oldProject/Settings/Settings.h" depending on the target. Then in source code you can #include SETTINGS_H. Of course, you might need to tweak header search path to be able to find each header.
Honestly speaking, #include SETTINGS_H is a tad ugly for my taste, so I prefer another approach. You can keep single "Settings.h" file, but use macros for constant values. For example, in "Settings.m"
NSString *const kServerURL = SERVER_URL;
and in target build settings add to GCC_PREPROCESSOR_DEFINITIONS
SERVER_URL='#"http://stackoverflow.com"'
You might need to play with quotation marks to achieve the desired effect. To organize project better, you can move macros' definitions to .xcconfig file and keep separate .xcconfig files for each target.
I currently have a number of special "flavors" of an iPhone application that I need to build. Ideally, I would like to have a scheme for each "flavor" and each scheme would define (or set) one or more preprocessor definitions that I can use to branch on in my code and possibly even preprocess my info.plist file. This can obviously be done with multiple targets, but since I could have many different "flavors" of the app, it would be great to do it with schemes to keep the target count down. My current thought is to add these preprocessor definitions during a pre-action script, but I can not for the life of me find any way to update GCC_PREPROCESSOR_DEFINITIONS. Since it is an environment variable, shouldn't I have access to append onto GCC_PREPROCESSOR_DEFINITIONS?
Worse case scenario you can do a pre-build script for the scheme. You'll have to include the script for every scheme though:
I would prefer to attach it to a Configuration:
Then you can easily add preprocessor macros for the various configurations, like I have here for Debug:
The <ProjectName>_Prefix.pch file is a great place to put macros that effect the whole program, like I have here:
In my example we're effectively turning off console output when not in debug mode, providing a little speed boost.
To meet my requirement of allowing schemes to set preprocessor definitions, the best solution I have come up with is to have scheme pre-action and post-action scripts modify a xcconfig file. This file, in turn, updates the build configuration, setting the preprocessor definitions and will even allow me to define preprocessor definitions to conditionally modify the info.plist. If anyone else goes down this route, make sure you take into account how this file is handled by source control.
This article's question and associated answers was helpful to me: How to append values in xcconfig variables?
How about defining multiple targets and defining pre-processor macros in the target-specific build options? Then you need only have one scheme, and you can build all the targets in one shot, all with their own specific build configurations.
If I understood your question correctly, you are looking to add some of user defined preprocessor macros to your source code, there is a way to add them in your target using Xcode. (e.g. GCC_PREPROCESSOR_DEFINITIONS = USE_TAPJOY )
Step 1) Decide marco name e.g USE_TAPJOY
Step 2) Go to target-> select tab "Build Setting" (Make sure all tab is enabled)
Step 3) in search box search for "Preprocessor Macro")
Step 4) Check for Debug/Release section
Step 5) Enter your Marco there
Step 6) Use this macro in your source code as below
For conditional include
#ifdef USE_TAPJOY
#import <Tapjoy/Tapjoy.h>
#endif
For conditional source code
#ifdef USE_TAPJOY // Tapjoy Connect Notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(tjcConnectSuccess:)
name:TJC_CONNECT_SUCCESS
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(tjcConnectFail:)
name:TJC_CONNECT_FAILED
object:nil];
#endif
Good luck