[Xcode 10.1, MacOS 10.14.1]

I have a project that uses bmake (could be any make though) and the Makefile provides a number of targets. I would like to use Xcode to build host and clean the build folder, but I'm having trouble working out how configure Xcode to allow me to this.

From the command line, I would build using bmake host and clean using bmake clean. The reason I'm using Xcode for this is because I like to use an IDE for debugging.

In Project -> Info (External Build Tool Configuration), I have:

Build Tool  :  /usr/local/bin/bmake
Arguments   :  host
Directory   :  None     <- I'm using the current path

With these settings, Product -> Build builds my target, but Product -> Clean Build Folder does nothing even though Xcode reports that the clean succeeded.

In order to actually do a clean, I either need to define another target with the Arguments field set to clean and then switch between targets when building/cleaning, or, use a single target and change the argument field depending on whether I'm building or cleaning. (A really clumsy way of going about it.)

If I leave Arguments with it's default value $(ACTION) all targets get built (except clean), and cleaning does nothing useful.

I've read https://stackoverflow.com/questions/15652316/setup-xcode-for-using-external-compiler but that question does not address this problem.

Is there a better way of doing this?

  • 1
    For those voting to close this question to SO, I don't see this as being a coding question. I'm reading it as a question for how to do something in the Xcode application and, if correct, that should be on-topic for this site. – fsb May 22 '19 at 14:23
  • Lelt's see whether this gets answered in the next day or two. If not, please flag for mod attention so we can migrate the question to SO. – nohillside May 22 '19 at 15:18

The approach we are using:

  1. Run external build system (i.e. Make or CMake) via custom script from "External Build System" target or just as a "Run script" build phase.
  2. At some point of custom script execution save a text file (say CMakeStatus.txt) to BUILT_PRODUCTS_DIR, which will be used as a flag indicating that Xcode did clean.
  3. In your custom script check that CMakeStatus.txt is present or not. If not, then rebuild your external build system.

Here is example of using Cmake and rebuilding it if Xcode did a clean.

#!/usr/bin/env ruby -w

require 'fileutils'

# system("printenv | sort")

AppProjectName = ENV['PROJECT_NAME']
AppPlatform = ENV['PLATFORM_NAME']
if AppProjectName.nil?
   raise "❌ Variable \"PROJECT_NAME\" is not passed to script."
if AppBuildRootDir.nil?
   raise "❌ Variable \"AWL_CMAKE_BUILDS\" is not passed to script."
if AppBuildDirPath.nil?
   raise "❌ Variable \"BUILT_PRODUCTS_DIR\" is not passed to script."
if AppPlatform.nil?
   raise "❌ Variable \"PLATFORM_NAME\" is not passed to script."
if AppBuildConfig.nil?
   raise "❌ Variable \"CONFIGURATION\" is not passed to script."

WhiteListedVars = ["PATH", "HOME"]
ENV.keys.each { |key|
   if !WhiteListedVars.include? key
      ENV[key] = nil
AppNewEnvVars = `bash -l -c printenv`.strip.split("\n")
AppNewEnvVars.each { |var|
   components = var.split("=", 2)
   key = components[0]
   value = components[1]
   ENV[key] = value

# system("printenv | sort")

AppCmakeBuildDirPath = "#{AppBuildRootDir}/#{AppProjectName}"
AppCmakeCacheFilePath = "#{AppCmakeBuildDirPath}/CMakeCache.txt"
AppCmakeStatusFilePath = "#{AppBuildDirPath}/CMakeStatus.txt"

if !File.exist?(AppCmakeStatusFilePath) && Dir.exist?(AppCmakeBuildDirPath)
   puts "✅ Deleting previous build at \"#{AppCmakeBuildDirPath}\"..."

if !File.exist?(AppCmakeCacheFilePath)
   puts "✅ Generating Cmake for Platform \"#{AppPlatform}\"..."
   cmd = "cmake -G Xcode -B \"#{AppCmakeBuildDirPath}\" -DCMAKE_Swift_COMPILER_FORCED=true -DCMAKE_BUILD_TYPE=#{AppBuildConfig} "
   if AppPlatform == "macosx"
      cmd += "-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13"
   puts cmd
   system cmd
   File.write(AppCmakeStatusFilePath, 'This file used just as a status flag to clean Cmake.')

puts "✅ Building Cmake for Platform \"#{AppPlatform}\" ..."

cmd = "set -euf -o pipefail && cd \"#{AppCmakeBuildDirPath}\" && cmake --build \"#{AppCmakeBuildDirPath}\" -- -quiet CONFIGURATION_BUILD_DIR=\"#{AppBuildDirPath}\" "
if AppPlatform == "macosx"
   cmd += "-destination 'platform=OS X,arch=x86_64'"
elsif AppPlatform == "iphonesimulator"
   if AppTargetDeviceId.nil?
      raise "❌ Variable \"TARGET_DEVICE_IDENTIFIER\" is not passed to script."
   cmd += "-sdk iphonesimulator -destination 'platform=iOS Simulator,id=#{AppTargetDeviceId}'"
   if AppTargetDeviceId.nil?
      cmd += "-sdk iphoneos -destination generic/platform=iOS"
      cmd += "-sdk iphoneos -destination 'platform=iOS,id=#{AppTargetDeviceId}'"

# cmd += " | xcpretty"
puts cmd
system(cmd) or exit 1

I have a Makefile-based project with a clean target and I was able to get that target to run in Xcode 10.3 with the standard Shift++K shortcut by choosing "Legacy Build System" from FileProject Settings…

"Build System" selection under File -> Project Settings in Xcode 10.3

Now choose ProductClean, or use the hotkey. Go to the Report navigator (+9).

Right click the somewhere in the middle of the most recent "Clean" results report and choose "Expand All Transcripts" in order to see the verbose output from the invocation of make, including all environment variables set by Xcode:

Xcode Report navigator for 'Clean' report

By scrolling all the way down to the bottom of this expanded output, you'll be able to see your make clean target running.

Hope this helps!

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .