Saturday 12 March 2022

Which SDK? choices, choices ...

This note is about configuring GCC on macOS so that your built compiler can be used independently of which Software Development Kit (SDK) is installed.

Since Big Sur, neither include files nor system libraries are to be found where you’d expect them (/usr/include and /usr/lib respectively). Instead, they’re in the appropriate SDK, which is to be found either in the Xcode application or in the much smaller Command Line Utilities.

If you look in /Applications/Xcode.app/Contents/Developer/Platforms/, there are 7 platforms available aside from MacOSX (iPhoneOS, for example). This means that an Xcode developer can choose the platform they’re developing for without separate downloads (OK, hard disk space is cheap, but 18 GB!), and the compilers and linker know how to deal with the choice they make (via xcode-select --switch under the hood, I think, but according to Xcode settings). GCC doesn’t.

This is the build script I’m using, with the configure call:

script_loc=`cd $(dirname $0) && pwd -P`

. $script_loc/common.sh

XCODE=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
CLU=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk

$GCC_SRC/configure                                                      \
  --prefix=$PREFIX                                                      \
  --without-libiconv-prefix                                             \
  --disable-libmudflap                                                  \
  --disable-libstdcxx-pch                                               \
  --disable-libsanitizer                                                \
  --disable-libcc1                                                      \
  --disable-libcilkrts                                                  \
  --disable-multilib                                                    \
  --disable-nls                                                         \
  --enable-languages=c,c++,ada                                          \
  --host=$BUILD                                                         \
  --target=$BUILD                                                       \
  --build=$BUILD                                                        \
  --without-isl                                                         \
  --with-build-sysroot="$(xcrun --show-sdk-path)"                       \
  --with-sysroot=                                                       \
  --with-specs="%{!sysroot=*:--sysroot=%:if-exists-else($XCODE $CLU)}"  \
  --with-build-config=no                                                \
  --disable-bootstrap

make -w -j3

make install

The external environment variables in this script are set to

  • GCC_SRC, where the GCC sources are checked out
  • PREFIX, /opt/gcc-12.0.1-2
  • BUILD, x86_64-apple-darwin21

Internally,

  • XCODE is where the MacOSX SDK is to be found under the Xcode application (if it’s installed)
  • CLU is where the MacOSX SDK is to be found under the Command Line Utilities (again, if installed)

As for the configuration switches,

  • --host is the machine I’m building for
  • --target is the machine I want GCC to produce code for
  • --build is the machine I’m building on
  • --with-build-sysroot says where the SDK is
  • --with-sysroot says where to find the SDK on the host machine - deliberately unspecified
  • --with-specs is the magic! see below
  • --disable-bootstrap because this is an experimental build, and if it’s going to fail it’ll probably do it in the first pass

--with-specs

The GCC “specs” language is documented here. It’s dauntingly baroque.

gcc is a driver program. It performs its job by invoking a sequence of other programs to do the work of compiling, assembling and linking. GCC interprets its command-line parameters and uses these to deduce which programs it should invoke, and which command-line options it ought to place on their command lines. This behavior is controlled by spec strings. In most cases there is one spec string for each program that GCC can invoke, but a few programs have multiple spec strings to control their behavior. The spec strings built into GCC can be overridden by using the -specs= command-line switch to specify a spec file.

We could change the C preprocessor’s spec by -specs=cpp-spec, where cpp-spec contains

%rename cpp old_cpp
*cpp:
%{!sysroot=*:-isysroot \
  %:if-exists-else( \
   /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
   /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk \
   )} \
  %(old_cpp)

which inserts our changes in front of the built-in ones.

  • %rename cpp old_cpp saves the built-in version.
  • *cpp: replaces the built-in version with everything up to the next blank line.
  • %{!sysroot=*:foo} says only to do foo if there’s no --sysroot= on the command line.
  • -isysroot sdk says that the system include files are to be found under sdk/usr/include.
  • %:if-exists-else(path-a path-b) substitutes path-a if that path exists, otherwise path-b.

And you’ll have noticed that the two paths given are those for the two possible SDKs (Xcode, and the Command Line Utilities).

Unfortunately, that approach would force users to give the -specs= switch every time they invoked the compiler (with gprbuild or gnatmake, that would be -cargs -specs=).

However! the --with-specs= line in the configure call above doesn’t mention a particular program whose spec string is to be changed, instead it bursts straight into the %{!sysroot=* part. In between there being nothing in the “specs” language to discuss this, and nothing in the configure information to say how --with-specs actually works , one can only say that “specs” processing has enough smarts to figure out where the option has to be applied.

Experimental procedure

I built GCC 12.0.1 at commit eb5edcf of 2022-03-11 on lockheed (x86_64-apple-darwin21 (Intel Monterey), with both Xcode and the Command Line Utilities installed). It used the Xcode SDK.

I then installed the binary on temeraire (aarch64-apple-darwin21 (M1 Monterey), with just the Command Line Utilities); after authorising Rosetta it worked out of the box, and used the Command Line Utilities SDK.

Still on temeraire, I used that compiler to build a full distrbution, less GDB, using these scripts.

I then installed the full distribution back on lockheed, and built D'Hondt calculator and Analytical Engine, successfully in both cases.

No comments:

Post a Comment