Skip to content

Conversation

kambala-decapitator
Copy link
Contributor

@kambala-decapitator kambala-decapitator commented Jul 20, 2025

Changelog: Fix: [XcodeBuild] place all intermediate build files in the cache build folder
Changelog: Feature: [XcodeBuild] allow passing build configuration and arbitrary command line params
Docs: conan-io/docs#4171

  • Refer to the issue that supports this Pull Request.
  • If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.
  • I've read the Contributing guide.
  • I've followed the PEP8 style guides for Python code.
  • I've opened another PR in the Conan docs repo to the develop branch, documenting this one.

Build configuration

Xcode projects come with 2 build configurations by default: Debug and Release. In my recipe I pass the following property as configuration param:

@property
def _buildConfig(self) -> str:
    return self.settings.build_type if self.settings.build_type == "Debug" else "Release"

Maybe this logic should even be moved to Conan?

Arbitrary xcodebuild params

For example, in my recipe I pass the following xcodebuild variables:

  • BUILD_LIBRARY_FOR_DISTRIBUTION=YES - plays an important role in building Swift code as it enables "library evolution" which allows consuming prebuilt binary/module by a later Swift compiler version
  • MACH_O_TYPE=mh_dylib (and EXECUTABLE_EXTENSION=dylib) to make a static library/framework target produce a shared library. The same variables with respective values could be used the other way around - to produce static library from a shared library/framework.
  • RUN_CLANG_STATIC_ANALYZER=NO to disable static analyzer launch (when a project enables it) that is useless for Conan build where I simply want to obtain a binary

A couple of other commonly used xcodebuild params would be -workspace to use .xcworkspace instead of .xcodeproj and -destination to select target device.

Redirecting build artifacts to the cache build folder via SYMROOT+OBJROOT variables

Activated via constructor parameter, off by default for backward compatibility.

Will copy my arguments in favor of this change from #18496

it solves 2 things:

  1. by default, SYMROOT is set to build directory inside the source directory and OBJROOT has the same value as SYMROOT. But projects can override any of those settings in the Xcode project file and you'll never know where the build files are. Since Conan has a dedicated build directory, it's better to keep build artifacts in there. Having build files in the source directory looks even worse to me when no_copy_source is True.

  2. it makes it easy to access build artifacts, here's an example from my recipe:

@property
def _buildConfig(self) -> str:
    return self.settings.build_type if self.settings.build_type == "Debug" else "Release"

def package(self):
    artifactsDir = os.path.join(self.build_folder, f"{self._buildConfig}-{self.settings.os.sdk}")

and now you simply copy .framework / .dylib / .a from the artifactsDir

changing some command layout things with SYMROOT, OBJROOT. These seems should be defined at the project level, or in any case at the toolchain level. Because otherwise, normal flow like conan install + IDE/xcodebuild direct user invocation will do something different

I can't really agree with this.

  1. When building from Xcode IDE, those variables are ignored by default and Xcode places all the stuff in ~/Library/Developer/Xcode/DerivedData/<dir>. They will be used only when project is configured in such a way, but AFAIK it's rarely done nowadays. This is configured in File - Project settings... - Advanced - set to Legacy. This behavior was changed to Legacy like 10 years ago or even more.
Screenshot 2025-07-17 at 09 35 31
  1. Suppose we don't touch those settings. How can a recipe figure out where the build artifacts are? The only reliable way would be to invoke xcodebuild -showBuildSettings to see the actual value of all settings and grep SYMROOT. Do you really expect every recipe to include such a piece of code (or have a Conan helper function)? To me it looks like an unnecessary complication when you can set a reliable path at build time. And also polluting source directory by default which we have already discussed. And one more point here: as soon as I have redirected SYMROOT+OBJROOT, tests started failing because they rely on hardcoded default value, see 2da70f5

@kambala-decapitator
Copy link
Contributor Author

@jcar87 could you have a look?

@memsharded memsharded added this to the 2.20 milestone Aug 1, 2025
@@ -77,7 +77,7 @@ def build(self):
xcode.build("app.xcodeproj")

def package(self):
copy(self, "build/{}/app".format(self.settings.build_type), self.source_folder,
copy(self, "{}/app".format(self.settings.build_type), self.build_folder,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes to existing tests typically evidence a possible breaking behavior.
Is this stylistic, or necessary for the test to pass? If stylistic, better keep the previous test.
if not stylistic, it needs to be clarified as a bug, not as an improvement.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is necessary due to my changes, I've also mentioned it in the PR description:

And one more point here: as soon as I have redirected SYMROOT+OBJROOT, tests started failing because they rely on hardcoded default value, see 2da70f5

Not sure why it should be a bug, if it's not really broken in upstream.

Copy link
Member

@memsharded memsharded Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, any user that has recipes with such a package() method will have their recipes broken.
In general we don't change existing tests code or behavior unless something is declared as a bug fix. Existing tests needs to keep passing as they are, that is what guarantees as much as possible that we are not breaking users code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to activate new behavior via a new conf then?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is too tied to the current recipe, activating a conf that enables this will still break existing recipes. And if recipes need to be ported one by one, then the way to introduce it is via an options new opt-in argument to XcodeBuild constructor or the the XcodeBuild.build() method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, I agree with your points. Added new param to constructor.

@memsharded memsharded requested a review from jcar87 August 1, 2025 09:26
@czoido czoido self-requested a review August 28, 2025 09:16
@czoido
Copy link
Contributor

czoido commented Aug 29, 2025

Hi @kambala-decapitator,

Thanks a lot for your contribution. Overall, it looks good, but we need to add at least some basic tests and do a bit more investigation. I'm moving it to the 2.21 milestone so we can tackle it as soon as we start working on that.

@czoido czoido modified the milestones: 2.20, 2.21 Aug 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants