Jackson Ray Hamilton
Jackson Ray Hamilton

Reputation: 9466

Compiling Objective-C on Debian 8 GNU/Linux with Clang and GNUstep make

On Debian 8 GNU/Linux, I'm trying to compile some Objective-C code from this tutorial using Clang and GNUstep make. I ran the following commands to install a build pipeline:

sudo apt-get install build-essential clang gnustep-devel

These are the files I am using:

GNUmakefile:

CC = clang

include $(GNUSTEP_MAKEFILES)/common.make

TOOL_NAME = Main
Main_OBJC_FILES = main.m Car.m

include $(GNUSTEP_MAKEFILES)/tool.make

main.m:

#import <Foundation/Foundation.h>
#import "Car.h"

int main () {
  @autoreleasepool {
    Car *toyota = [[Car alloc] init];

    [toyota setModel:@"Toyota Corolla"];
    NSLog(@"Created a %@", [toyota model]);

    toyota.model = @"Toyota Camry";
    NSLog(@"Changed the car to a %@", toyota.model);

    [toyota drive];
  }
  return 0;
}

Car.h:

#import <Foundation/Foundation.h>

@interface Car : NSObject {}

@property (copy) NSString *model;

- (void)drive;

@end

Car.m:

#import "Car.h"

@implementation Car {
  double _odometer;
}

@synthesize model = _model;

- (void)drive {
  NSLog(@"Driving a %@. Vrooom!", self.model);
}

@end

I am compiling with:

. /usr/share/GNUstep/Makefiles/GNUstep.sh && make -k

When I compile, I get the following errors:

Car.m:4:10: error: inconsistent number of instance variables specified
  double _odometer;
         ^
Car.m:7:13: error: synthesized property 'model' must either be named the same as a compatible instance variable or must
      explicitly name an instance variable
@synthesize model = _model;

To fix the errors, I need to update the Car interface to:

@interface Car : NSObject {
  NSString * _model;
  double _odometer;
}

And the Car implementation to:

@implementation Car {
  NSString * _model;
  double _odometer;
}

It seems like my current compilation setup is forcing me to write more code than the tutorial suggests is needed in order to compile. In fact, in the tutorial, the author specifically recommends against including "protected instance variables" in the interface:

@interface Car : NSObject {
    // Protected instance variables (not recommended)
}

I feel like I am using an outdated / misconfigured compilation setup. Am I? If so, how can I fix it so I can write code like in the referenced tutorial? (I'd prefer to use GNUstep make rather than the Clang CLI, if possible.)

Upvotes: 3

Views: 1361

Answers (2)

Itai Ferber
Itai Ferber

Reputation: 29764

There is a mistake in the tutorial that you read (or at least, missing information) -- in order to use @synthesize model = _model;, you indeed need to define an instance variable named _model; when you do, the error goes away.

Let's take a step back, though. A long time ago, Objective-C didn't have @property syntax to define properties. This tutorial shows @property (copy) NSString *model;, but doesn't immediately get into what that means. (Read the linked "properties module" to see more from the author about this subject.)

Before @property syntax, you defined all of your instance variables manually:

// In MyClass.h
@interface MyClass : NSObject {
    // Visible instance variables. Can be marked @public, @private, and @protected to prevent access.
}
...
@end

In order to allow users to access your instance variables, it was good practice to write setters and getters (e.g. -model and -setModel:), and to leave your instance variables inaccessible. This follows the OOP model of protecting access to your inner state and allowing access only through a presentable interface.

This could be quite a tedious process, and was a lot of boilerplate (especially with manual memory management, KVO compliance, etc.). @property syntax came along to help with that.

When you declare an @property, it defines an instance variable for you, as well as implicitly creating the getter and setter methods. You can specify the memory management model to use in these getters and setters (e.g. strong, weak, assign, copy, etc.), atomicity, and so on. If you have a property called model, @property will generate a _model instance variable, along with -model and -setModel: (unless you define only a getter).

It used to be, however, that @property did not create an instance variable for you, only the getter and setter. If you wanted an instance variable, you had to either create it yourself, or explicitly request that the compiler do it. This is where the @synthesize line comes in. @synthesize model = _model; tells the compiler that you have created a _model instance variable yourself, and that the model property should use _model as the storage. The leading underscore for the property was simply convention to help differentiate between the property itself (alias for a setter/getter) and the underlying storage.

In any case, those are details of the past. With a modern compiler, you will not need an @synthesize line, as the compiler will generate the instance variable for you. Either add the variable yourself and keep the @synthesize model = _model; line (not recommended), or leave that line out.

Note also that if you do declare your own instance variables, they do not need to be repeated between your @interface and @implementation -- pick a place and put them there. I recommend the @implementation, since they need not be visible, but that is up to you.

Upvotes: 2

Jackson Ray Hamilton
Jackson Ray Hamilton

Reputation: 9466

From this answer I found that my problem might have been that I was not using a "modern" Objective-C runtime.

Instance variable declarations inside the @implementation { } block is a relatively recent Objective-C feature. . . . you also need to be eligible for the "Modern" Objective-C runtime . . .

From this thread I found,

Providing -fobjc-nonfragile-abi will also make some changes to the runtime. On Darwin it will use Apple's 'Modern' runtime, while not specifying it will generate code for the 'Legacy' runtime. If you specify -fobjc-nonfragile-abi and -fgnu-runtime, then clang will generate code for the GNUstep runtime, which you can find in GNUstep svn as libobjc2. This provides all of the ObjC 2 features except for GC, and a few others not found in the Mac runtimes.

So, I tried adding the following to my GNUmakefile:

Main_OBJCFLAGS = -fobjc-nonfragile-abi

But when I tried to compile, I got the error:

fatal error: 'objc/blocks_runtime.h' file not found

From there, I found this thread and concluded that I probably needed "libobjc2".

Disappointingly, I couldn't find a "libobjc2" package for Debian, so I attempted to compile it from source. First I had to install some build dependencies:

sudo apt-get install cmake llvm-dev

Then I had to apply this patch to the llvm-3.5-dev package, because it was causing the libobjc2 compilation to fail:

wget -qO- https://launchpadlibrarian.net/221945609/fix-llvm-3.5-dev-debian.bash | bash

So then I was able to compile libobjc2 with the following commands:

wget http://download.gna.org/gnustep/libobjc2-1.7.tar.bz2
tar jxf libobjc2-1.7.tar.bz2
cd libobjc2-1.7
mkdir Build
cd Build
CC=clang CXX=clang++ cmake -DCMAKE_INSTALL_LIBDIR=lib ..
make -j8
sudo -E make install

After that, I tried to compile the code from the tutorial again. Compilation succeeded (without making any of the changes that I didn't want to make)! However, make complained about a "potential conflict" between one of my ".so" files (I can't recall the exact error), and when I ran my program (with ./obj/Main), I saw the following in the program output:

$ ./obj/Main
Loading two versions of Protocol.  The class that will be used is undefined
Loading two versions of Object.  The class that will be used is undefined
2016-11-19 16:43:24.012 Main[10166] Created a Toyota Corolla
2016-11-19 16:43:24.017 Main[10166] Changed the car to a Toyota Camry
2016-11-19 16:43:24.017 Main[10166] Driving a Toyota Camry. Vrooom!

Apparently, compiling libobjc2 and letting it live alongside the Debian GNUstep packages may not have been "good." So, I took the advice of various online sources and set out to compile GNUstep from source.

First I uninstalled the Debian packages:

sudo apt-get uninstall gnustep-devel && sudo apt-get autoremove

Then I installed the following build dependencies:

sudo apt-get install libffi-dev libicu-dev libgnutls28-dev libxml2-dev libxslt1-dev libtiff5-dev libjpeg-dev libpng-dev libgif-dev libaspell-dev libcups2-dev libaudiofile-dev portaudio19-dev libdispatch-dev

(I can't guarantee that's the complete list of dependencies, only the ones I could find documented; I may have had a few more necessary libraries already installed on my computer as a result compiling unrelated programs.)

Using the following two tutorials (1) (2), adapting them slightly (perhaps unnecessarily), I managed to compile GNUstep.

wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-make-2.6.8.tar.gz | tar xz
wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-base-1.24.9.tar.gz | tar xz
wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-gui-0.25.0.tar.gz | tar xz
wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-back-0.25.0.tar.gz | tar xz

cd gnustep-make-2.6.8
CC=clang ./configure --enable-objc-nonfragile-abi
sudo make install
cd ..

cd gnustep-base-1.24.9
CC=clang OBJCFLAGS="-fblocks -fobjc-nonfragile-abi" ./configure
CC=clang OBJCFLAGS="-fblocks -fobjc-nonfragile-abi" make
sudo make install
cd ..

cd gnustep-gui-0.25.0
CC=clang OBJCFLAGS="-fblocks -fobjc-nonfragile-abi" ./configure
CC=clang OBJCFLAGS="-fblocks -fobjc-nonfragile-abi" make
sudo make install
cd ..

cd gnustep-back-0.25.0
CC=clang OBJCFLAGS="-fblocks -fobjc-nonfragile-abi" ./configure
CC=clang OBJCFLAGS="-fblocks -fobjc-nonfragile-abi" make
sudo make install
cd ..

After that, I compiled my tutorial files again, this time with:

. /usr/local/share/GNUstep/Makefiles/GNUstep.sh && make -k

At last, I no longer get an error about any "conflict", and when I run the program, I no longer see the errors of the form "Loading two versions of . . ."

$ ./obj/Main
2016-11-19 19:56:07.450 Main[10822:10822] Created a Toyota Corolla
2016-11-19 19:56:07.451 Main[10822:10822] Changed the car to a Toyota Camry
2016-11-19 19:56:07.451 Main[10822:10822] Driving a Toyota Camry. Vrooom!

Success!

I've also refactored this solution slightly, into a script. That might be a little "cleaner" and easier to use. I'll paste the latest version here "just in case:"

#!/usr/bin/env bash

# It seems that many modern Objective-C features aren't available without
# libobjc2, which doesn't seem to be available as a Debian package.  Also,
# compiling and using it alongside the Debian GNUstep packages doesn't work too
# well (it seems like they may each provide their own definition of the Protocol
# and Object classes).  Basically, to get a fully-functioning Objective-C
# compilation environment on Debian 8, run this script.

# Please ensure any Debian GNUstep packages are uninstalled before running this
# script.

# Slightly adapted from
# http://wiki.gnustep.org/index.php/GNUstep_under_Ubuntu_Linux for Debian 8.
# Also, uses the latest stable versions of source packages as of this writing
# (hopefully to improve reproducability of success; but feel free to upgrade
# them if you want).

# If this script is successful, you should be able to compile a "main.m" program
# by running "make" in a directory with a "GNUmakefile" with these contents:
#
#     include $(GNUSTEP_MAKEFILES)/common.make
#     TOOL_NAME = Main
#     Main_OBJC_FILES = main.m
#     include $(GNUSTEP_MAKEFILES)/tool.make

# Show prompt function
function showPrompt()
{
    if [ "$PROMPT" = true ] ; then
        echo -e "\n\n"
        read -p "${GREEN}Press enter to continue...${NC}"
    fi
}

# Set colors
GREEN=`tput setaf 2`
NC=`tput sgr0` # No Color

# Set to true to pause after each build to verify successful build and installation
PROMPT=true

# Install Requirements
sudo apt update

echo -e "\n\n${GREEN}Installing dependencies...${NC}"
sudo apt -y install clang ninja cmake libffi-dev libxml2-dev \
     libgnutls28-dev libicu-dev libblocksruntime-dev libkqueue-dev libpthread-workqueue-dev autoconf libtool \
     libjpeg-dev libtiff5-dev libffi-dev libcairo2-dev libx11-dev libxt-dev libxft-dev \
     llvm-dev libdispatch-dev

# https://bugs.launchpad.net/ubuntu/+source/llvm/+bug/1387011/comments/17
wget -qO- https://launchpadlibrarian.net/221945609/fix-llvm-3.5-dev-debian.bash | bash

# Create build directory
mkdir GNUstep-build
cd GNUstep-build

# Set clang as compiler
export CC=clang
export CXX=clang++

# Checkout sources
echo -e "\n\n${GREEN}Downloading sources...${NC}"
mkdir -p libobjc2 && wget -qO- https://github.com/gnustep/libobjc2/archive/v1.8.1.tar.gz | tar xz -C libobjc2 --strip-components=1
mkdir -p make && wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-make-2.6.8.tar.gz | tar xz -C make --strip-components=1
mkdir -p base && wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-base-1.24.9.tar.gz | tar xz -C base --strip-components=1
mkdir -p gui && wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-gui-0.25.0.tar.gz | tar xz -C gui --strip-components=1
mkdir -p back && wget -qO- ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-back-0.25.0.tar.gz | tar xz -C back --strip-components=1

showPrompt

# Build GNUstep make first time
echo -e "\n\n"
echo -e "${GREEN}Building GNUstep-make for the first time...${NC}"
cd make
./configure --enable-debug-by-default --with-layout=gnustep --enable-objc-nonfragile-abi --enable-objc-arc
make -j8
sudo -E make install

. /usr/GNUstep/System/Library/Makefiles/GNUstep.sh
echo ". /usr/GNUstep/System/Library/Makefiles/GNUstep.sh" >> ~/.bashrc

# showPrompt

# Build libobjc2
echo -e "\n\n"
echo -e "${GREEN}Building libobjc2...${NC}"
cd ../libobjc2
rm -Rf build
mkdir build && cd build
cmake ../ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang -DCMAKE_ASM_COMPILER=clang -DTESTS=OFF
cmake --build .
sudo -E make install
sudo ldconfig

export LDFLAGS=-ldispatch

showPrompt

OBJCFLAGS="-fblocks -fobjc-runtime=gnustep-1.8.1"

# Build GNUstep make second time
echo -e "\n\n"
echo -e "${GREEN}Building GNUstep-make for the second time...${NC}"
cd ../../make
./configure --enable-debug-by-default --with-layout=gnustep --enable-objc-nonfragile-abi --enable-objc-arc
make -j8
sudo -E make install

. /usr/GNUstep/System/Library/Makefiles/GNUstep.sh

showPrompt

# Build GNUstep base
echo -e "\n\n"
echo -e "${GREEN}Building GNUstep-base...${NC}"
cd ../base/
./configure
make -j8
sudo -E make install

showPrompt

# Build GNUstep GUI
echo -e "\n\n"
echo -e "${GREEN} Building GNUstep-gui...${NC}"
cd ../gui
./configure
make -j8
sudo -E make install

showPrompt

# Build GNUstep back
echo -e "\n\n"
echo -e "${GREEN}Building GNUstep-back...${NC}"
cd ../back
./configure
make -j8
sudo -E make install

showPrompt

. /usr/GNUstep/System/Library/Makefiles/GNUstep.sh

echo -e "\n\n"
echo -e "${GREEN}Install is done. Open a new terminal to start using.${NC}"

Upvotes: 3

Related Questions