7

As the topic says, I want to be able to run a specific command during build, and have its output be the definition of a preprocessor macro.

Right now, I have several user-defined variables (in project.pbxproj) and I am able to use their values to fill macro definitions, as follows:

GCC_PREPROCESSOR_DEFINITIONS = (
"STRINGIFY(x)=@#x",
"_MACRO=STRINGIFY(${MACRO})",
);
MACRO = foo;

I can set MACRO to a different value per scheme (e.g Debug vs. Release) which is very useful. But, I cannot figure out how to set it by running a command.

3 Answers 3

11

I can think of 3 options:

Environment variable: If you build from command line you can export a variable (export ENVMACRO=superfoo) before invoking the build command and use it in an Xcode configuration file OTHER_CFLAGS=-DMACRO=$(ENVMACRO). You need to configure the target with the .xcconfig file.

Run Script Build Phase: Custom script that generates a header file.

MACROVALUE=$(run-command-to-obtain-value)
echo "#define MACRO ($MACROVALUE)" > $(SRCROOT)/path/to/header.h

In my tests you need an empty header file to be able to compile properly. There are other options like modifying an existing file using sed or any other command.

Custom Build Rule: Custom script that process an input file and creates an output file. Similar to Run Script build phase but slightly better because it will run the script only when the input file has been modified. For example, create a .macro file and process it to update a header file.

In Xcode > Target > Build rules, add new custom rule.

Process: *.macro

Custom script:

HEADER="${SRCROOT}/Config.h"
cd ${SRCROOT}
echo "// Do not edit" > $HEADER
cat "${INPUT_FILE_PATH}" | while read line
do
    macro="`echo $line | cut -d= -f1`"
    cmd="`echo $line | cut -d= -f2-`"
    value=$($cmd)
    echo "#define ${macro} @\"${value}\"" >> $HEADER
done
echo "// Updated on "`date` >> $HEADER

Output files: $(SRCROOT)/Project.h

Project.macro contains pairs MACRO=one-liner-command. Like these two non-sense examples:

COMMIT=git log --pretty=format:%h -n 1
BUILDYEAR=date +%Y

Generated file will look like:

// Do not edit
#define COMMIT @"c486178"
#define BUILDYEAR @"2011"
// Updated on Sat Oct 29 14:40:41 CEST 2011

Each time Project.macro changes, the generated header will be updated.

Sign up to request clarification or add additional context in comments.

1 Comment

thanks -- the second option is essentially what i've gone with -- in my case, i wanted the command to run at every build. the only wrinkle is that i don't write to the actual header file if its contents aren't identical to the newly generated contents (to avoid the dependency change). i do especially like the third option as well though.
0

If anyone is curious, the solution I've used is to add a new build phase that runs a script that manually generates a header file with the macros that I want. It's not elegant, and I would still prefer something better, but it works.

1 Comment

Congrats on the solution. Make sure to mark your answer as 'accepted' so that other may learn from your success. Cheers~
0

I think a better solution would be to declare just one preprocessor macro in the project build settings (e.g. DEBUG for debug, RELEASE for release) and then in your Prefix.pch file you can check for the value in order to decide what other macros to define, e.g.:

// Use NSLOG and NSASSERT so that they are only output in debug mode
#ifdef DEBUG

//#warning You are running in Debug mode
#define NSLOG NSLog
#define NSASSERT NSAssert
#define NSASSERT1 NSAssert1

#else

#define NSLOG if(false)NSLog
#define NSASSERT(X, Y)  \
if(!(X)) {          \
NSLogOkay(Y);       \
}                   
#define NSASSERT1(X, Y, Z)  \
if(!(X)) {              \
NSLogOkay(Y, Z);        \
}                       

#endif

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.