I have recently refreshed my working laptop to a 2016 MBP. My previous Dell is a great laptop except it is way too big and heavy to carry around with me and I need OS X environment for supporting sometimes, so I decided to make my Mac a daily working machine and my previous laptop became a supplemental machine for windows and linux platform.
The new MBP comes with OS X 10.12 Sierra. When I tried to start Xcode 6.1.1 first time, it gave me this dialog.
Although I could use Xcode 8.3 with OS X SDK 10.9, but I am not very familiar with Xcode's building configuration and I can't find a good way to switch between clang 8.0 and clang 6.0. It's not critical for just trial and debug for myself most of the time, but it is still not same as recommended environment. I tried xcode-select with specified platform SDK and it works well when building Maya debug builds on my Sierra Mac, so I've decided to try another way in the document - the makefile approach. It will create an output bundle directly at the sample's directory with make and clean the outputs with make clean. Since I've been using Visual Studio for almost two decades, I'd like to keep using it on the Mac.
Microsoft has release VSCode on Multiplatform, it is quite different from the regular Visual Studio on Windows. You'll have to install extensions and write configurations for launching, debugging and building in json format. I did some research and found that I'd be better to stick with the makefile comes with SDK and make some modifications. Here are the steps for setting up a simple Maya development environment on Mac in an alternative way.
VSCode shipped with json support only and you'll need to install C++ extension to make intellisense and other stuff working with C++.
The first comes with build. Unlike regular VS on windows using build configure, VSCode uses tasks.json for setting up build targets. To make it simple, I use it to set up environment variables for building multiple Maya version and call make to build with makefile. I'll do it later when the makefile has been done.
Maya's makefile comes with two parts, general building configuration inside devkit/plug-ins folder and makefile inside each plugin folder. The makefile refers to the buildrules and buildconfig in the parent folder, let's begin with buildrules first.
#-
# ==========================================================================
# Copyright (c) 2011 Autodesk, Inc.
# All rights reserved.
#
# These coded instructions, statements, and computer programs contain
# unpublished proprietary information written by Autodesk, Inc., and are
# protected by Federal copyright law. They may not be disclosed to third
# parties or copied or duplicated in any form, in whole or in part, without
# the prior written consent of Autodesk, Inc.
# ==========================================================================
#+
INCL_BUILDRULES := 1
#Add directories variables
OBJBASEDIR := ./objects
OBJDIR := ./objects/$(mayaVersion)
OUTPUTBASEDIR := ./output
OUTPUTDIR := ./output/$(mayaVersion)
include $(TOP)/buildconfig
##################
# Generic Rules #
##################
.PHONY : all plugins depend clean Clean
.DEFAULT : all
all : plugins
# Pre-built binaries of some plugins are shipped with Maya. We don't
# want to build them by default with all the others because then there
# would be two versions of the same plugin and unless you know exactly
# what you are doing the wrong one might get loaded.
#
# The top-level makefile of the devkit won't recurse by default into
# the sub-directory of prebuilt plugins. So if you want to build one
# of these you must do so explicitly by invoking make from the given
# sub-directory or invoke the prebuiltPlugins rules from the top-level
# makefile.
.PHONY : prebuiltPlugins
# The top-level makefile of the devkit won't recurse by default into
# the sub-directory of plugins depending on third-party components. So
# if you want to build one of these you must do so explicitly by
# invoking make from the given sub-directory or invoke the
# thirdPartyPlugins rules from the top-level makefile. For example:
.PHONY : thirdPartyPlugins
##################
# Generic Rules #
##################
#Creating directories
directories: $(OBJDIR) $(OUTPUTDIR)
$(OBJDIR):
mkdir -p $(OBJDIR)
$(OUTPUTDIR):
mkdir -p $(OUTPUTDIR)
#Change object output directory
$(OBJDIR)/%.o : %.c
$(CC) -c $(INCLUDES) $(CFLAGS) $< -o $@
$(OBJDIR)/%.o : %.cpp
$(CXX) -c $(INCLUDES) $(C++FLAGS) $< -o $@
%.o : %.c
$(CC) -c $(INCLUDES) $(CFLAGS) $< -o $@
%.o : %.cpp
$(CXX) -c $(INCLUDES) $(C++FLAGS) $< -o $@
%.i : %.cpp
$(CXX) -E $(INCLUDES) $(C++FLAGS) $*.cpp > $*.i
The original makefile will generate the output bundle at the same directory where makefile is, it is kind of messy in my opinion and not suitable for building for multiple platforms. Adding directories becomes my first priority.
#Add directories variables
OBJBASEDIR := ./objects
OBJDIR := ./objects/$(mayaVersion)
OUTPUTBASEDIR := ./output
OUTPUTDIR := ./output/$(mayaVersion)
I'll create two directories for object and output files. For different version of Maya I'll also create a subfolder inside of the each directory according to target version.
Then, I'll add a target for creating directories:
#Creating directories
directories: $(OBJDIR) $(OUTPUTDIR)
$(OBJDIR):
mkdir -p $(OBJDIR)
$(OUTPUTDIR):
mkdir -p $(OUTPUTDIR)
The target is called directories and it has to targets, object director and output directory. -p will let mkdir create directories recursively. Next is updating object targets.
#Change object output directory
$(OBJDIR)/%.o : %.c
$(CC) -c $(INCLUDES) $(CFLAGS) $< -o $@
$(OBJDIR)/%.o : %.cpp
$(CXX) -c $(INCLUDES) $(C++FLAGS) $< -o $@
Adding object target's directory in front of object output will put them in the directory I created earlier.
Buildrules is done now, the next will be buildconfig. Here is the full buildconfig
#-
# ==========================================================================
# Copyright 2012 Autodesk, Inc. All rights reserved.
#
# Use of this software is subject to the terms of the Autodesk
# license agreement provided at the time of installation or download,
# or which otherwise accompanies this software in either electronic
# or hard copy form.
# ==========================================================================
#+
# If you have Maya installed in a non-standard location, uncomment the
# following line and set the path to the 'Maya.app/Contents' directory of
# your Maya installation:
# MAYA_LOCATION = /my/path/to/Maya.app/Contents
# export MAYA_LOCATION
# Make sure these are in your path.
CC = clang
CXX = clang++
LD = clang++
# By default, we will try to build a universal binary to match the same
# architectures as the installed Maya binary. If you only want one specific
# architecture (e.g i386 or x86_64) then set it below.
PREFERRED_ARCHITECTURE =
ifneq ($(MAKECMDGOALS), clean)
#-------------------------------------------------------------
#
# Try to find Maya
#
#-------------------------------------------------------------
ifeq ($(MAYA_LOCATION),)
MAYA_LOCATION = /Applications/Autodesk/maya$(mayaVersion)/Maya.app/Contents
# downloaded devkit can be located outside of Maya install tree
ifeq ($(DEVKIT_LOCATION),)
DEVKIT = $(MAYA_LOCATION)/../../devkit
DEVKIT_LOCATION = $(MAYA_LOCATION)/../../include
else
DEVKIT = $(DEVKIT_LOCATION)
DEVKIT_INCLUDE = $(DEVKIT)/include
endif
ifeq ($(wildcard $(DEVKIT_INCLUDE)/maya/MTypes.h),)
$(error Cannot find Maya ${mayaVersion}. Please uncomment the \
MAYA_LOCATION setting near the top of 'buildconfig' and set \
it to point to the Maya.app/Contents directory of your \
Maya ${mayaVersion} installation)
endif
else
ifneq ($(DEVKIT_LOCATION),)
DEVKIT = $(DEVKIT_LOCATION)
DEVKIT_INCLUDE = $(DEVKIT)/include
else ifneq ($(wildcard $(MAYA_LOCATION)/../../include/maya/MTypes.h),)
DEVKIT = $(MAYA_LOCATION)/../../devkit
DEVKIT_INCLUDE = $(MAYA_LOCATION)/../../include
else
$(error The MAYA_LOCATION environment variable does not point to the \
directory where Maya is installed)
endif
# Make sure that MAYA_LOCATION is pointing at the correct version of
# Maya.
#
# Matching subversions (e.g. 2013.5) is not possible without a lookup
# table, so if this is a sub-version then just make sure that we've
# got the right base version. Not perfect, but better than nothing.
baseMayaVersion := $(shell echo $(mayaVersion) | sed 's/\..*//')
apiVersion := $(shell grep 'define.*MAYA_API_VERSION' $(DEVKIT_INCLUDE)/maya/MTypes.h | sed 's/^[^0-9]*\([0-9]...\).*$$/\1/')
endif
DEVKIT_ALEMBIC_INCDIR = $(DEVKIT)/Alembic/include
DEVKIT_ALEMBIC_LIBDIR = $(DEVKIT)/Alembic/lib
# Determine the architectures to build.
ARCH_FLAGS =
MAYABIN = ${MAYA_LOCATION}/bin/maya
MAYA_ARCHES = $(shell lipo -info $(MAYABIN) | sed 's/^.*://')
ifneq ($(PREFERRED_ARCHITECTURE),)
ifneq ($(filter $(PREFERRED_ARCHITECTURE),$(MAYA_ARCHES)),)
ARCH_FLAGS = -arch $(PREFERRED_ARCHITECTURE)
else
$(error $(MAYABIN) does not support the '$(PREFERRED_ARCHITECTURE)' architecture.)
endif
else
ARCH_FLAGS = $(patsubst %,-arch %,$(MAYA_ARCHES))
endif
# Settings, added several extra flags
CFLAGS = -DCC_GNU_ -DOSMac_ -DOSMacOSX_ $(EXTRA_ARCH_FLAG)\
-DOSMac_MachO_ -O3 $(ARCH_FLAGS) -D_LANGUAGE_C_PLUS_PLUS\
-mmacosx-version-min=10.8
C++FLAGS = $(CFLAGS) $(CXX_LIB_FLAG) $(WARNFLAGS) $(ERROR_FLAGS) -fno-gnu-keywords -fpascal-strings
INCLUDES = -I$(SRCDIR) -I"$(DEVKIT_INCLUDE)"
DYNLIB_LOCATION = $(MAYA_LOCATION)/MacOS
LFLAGS = $(CXX_LIB_FLAG) -fno-gnu-keywords -fpascal-strings \
-isysroot $(XCODE_PLATFORM_SDK_PATH) \
$(ARCH_FLAGS) -headerpad_max_install_names \
-framework System -framework SystemConfiguration \
-framework CoreServices -framework Carbon \
-framework Cocoa -framework ApplicationServices \
-framework IOKit \
-bundle
# When compiling plug-ins for Maya, we make sure to export only the
# symbols for the initializePlugin(MObject) and
# uninitializePlugin(MObject) functions. In particular, this
# prevents the plug-ins from exporting symbols coming from static
# libaries against which the plug-ins is linked.
#
# Here's why:
#
# Plug-ins are written by separate developers. Each of them might
# want to use various libraries to implement their plug-ins. At
# times, it occurs that plug-ins written by different developers are
# using different and "incompatible" versions of the same library. To
# support this, we recommend that plug-ins be linked against static
# versions of these libraries. And, by hidding the symbols of these
# libraries, we prevent the two incompatible versions of the library
# from interferring with each others.
LFLAGS += -Wl,-exported_symbol,$(INITIALIZE_SYMBOL) \
-Wl,-exported_symbol,$(UNINITIALIZE_SYMBOL) \
-Wl,-exported_symbol,_MApiVersion
LREMAP = -Wl,-executable_path,"$(DYNLIB_LOCATION)"
LFLAGS += -L"$(DYNLIB_LOCATION)" $(LREMAP)
LIBS =
EXT = bundle
#Detect current clang xdk and choose correct sdk
CLANG_VERSION := $(shell gcc --version | grep -o "version [0-9\.]*")
ifneq ("$(CLANG_VERSION)","$(TARGET_CLANG_VERSION)")
$(shell sudo xcode-select -s $(XCODE_PATH))
endif
# Makedepend flags
#
# Ignore dependencies on system header files.
MDFLAGS = -Y
# Find out if the Qt development tools are installed.
# we want to use the qmake file provided by Maya, not the system
ifneq ($(wildcard $(DEVKIT_LOCATION)/devkit/bin/qmake),)
#Maya 2017+
QMAKE = $(DEVKIT_LOCATION)/devkit/bin/qmake
ifneq ($(QMAKE),)
# We want to generate a Makefile, not an xcode project.
QMAKE += -spec $(DEVKIT)/mkspecs/macx-clang
endif
else
#Maya 2016, 2016.5
#qmake wasn't in 2016/2016.5 devkit
QMAKE = $(shell which qmake 2> /dev/null)
ifneq ($(QMAKE),)
# We want to generate a Makefile, not an xcode project.
# QMAKE for 2016/2016.5
QMAKE += -spec macx-g++
endif
endif
endif
To make it support Maya 2016~2018, I'll need to add several flags for specifying C++ library, Xcode, qmake and exported symbols for initialize/uninitialize plugin. I also removed mayaVersion to make it configurable in the tasks.json. Most of the changes are just replacing values with variables which should be same for the linux platform except the xcode-select part.
#Detect current clang xdk and choose correct sdk
CLANG_VERSION := $(shell gcc --version | grep -o "version [0-9\.]*")
ifneq ("$(CLANG_VERSION)","$(TARGET_CLANG_VERSION)")
$(shell sudo xcode-select -s $(XCODE_PATH))
endif
You could choose default clang you want to use with it. It requires root privilege so you might need to input password during building.
The last part is makefile itself. Unlike buildconfig and buildrules, it needs to be tweaked from projects to projects, so it is stored inside of project's directory.
#-
# ==========================================================================
# Copyright (c) 2011 Autodesk, Inc.
# All rights reserved.
#
# These coded instructions, statements, and computer programs contain
# unpublished proprietary information written by Autodesk, Inc., and are
# protected by Federal copyright law. They may not be disclosed to third
# parties or copied or duplicated in any form, in whole or in part, without
# the prior written consent of Autodesk, Inc.
# ==========================================================================
#+
ifndef INCL_BUILDRULES
#
# Include platform specific build settings
#
TOP := ..
include $(TOP)/buildrules
#
# Always build the local plug-in when make is invoked from the
# directory.
#
all : plugins
endif
#
# Variable definitions
#
SRCDIR := .
DSTDIR := $(OUTPUTDIR)
# Adding all cpp files as source file
SOURCES := $(wildcard *.cpp) $(wildcard **/*.cpp)
OBJECTS := $(addprefix $(OBJDIR)/,$(patsubst %.cpp, %.o, $(SOURCES)))
PLUGIN := $(DSTDIR)/$(PROJECT_NAME).$(EXT)
MAKEFILE := $(SRCDIR)/Makefile
#
# Include the optional per-plugin Makefile.inc
#
# The file can contain macro definitions such as:
# {pluginName}_EXTRA_CFLAGS
# {pluginName}_EXTRA_C++FLAGS
# {pluginName}_EXTRA_INCLUDES
# {pluginName}_EXTRA_LIBS
-include $(SRCDIR)/Makefile.inc
#
# Set target specific flags.
#
$(OBJECTS): CFLAGS := $(CFLAGS) $(EXTRA_CFLAGS)
$(OBJECTS): C++FLAGS := $(C++FLAGS) $(EXTRA_C++FLAGS)
$(OBJECTS): INCLUDES := $(INCLUDES) $(EXTRA_INCLUDES)
depend_mayaProject: INCLUDES := $(INCLUDES) $(EXTRA_INCLUDES)
$(PLUGIN): LFLAGS := $(LFLAGS) $(EXTRA_LFLAGS)
$(PLUGIN): LIBS := $(LIBS) -lOpenMaya -lOpenMayaUI -lOpenMayaRender -lFoundation -framework AGL -framework OpenGL $(EXTRA_LIBS)
#
# Rules definitions
#
.PHONY: depend_mayaProject clean_mayaProject Clean_mayaProject
$(PLUGIN): $(OBJECTS)
-rm -f $@
$(LD) -o $@ $(LFLAGS) $^ $(LIBS)
depend_mayaProject :
makedepend $(INCLUDES) $(MDFLAGS) -f$(DSTDIR)/Makefile $(SOURCES)
clean_mayaProject:
-rm -rf $(OBJBASEDIR)
-rm -rf $(OUTPUTBASEDIR)
Clean_mayaProject:
-rm -f $(MAKEFILE).bak $(OBJECTS) $(PLUGIN)
plugins: directories $(PLUGIN)
depend: depend_mayaProject
clean: clean_mayaProject
Clean: Clean_mayaProject
I took a little bit cheat here, I made it compile all cpp files inside my working directory.
SOURCES := $(wildcard *.cpp) $(wildcard **/*.cpp)
OBJECTS := $(addprefix $(OBJDIR)/,$(patsubst %.cpp, %.o, $(SOURCES)))
PLUGIN := $(DSTDIR)/$(PROJECT_NAME).$(EXT)
MAKEFILE := $(SRCDIR)/Makefile
I've heard of that globstar(**) is only good for OS X Sierra by default. For Linux, you'll need to enable globstar option with following command.
shopt -s globstar
It is only available after bash 4, so if you are running with earlier version of bash, you might want to update it. Otherwise, you'll need to use for loop for subdirectories instead.
$(PROJECT_NAME) is an environment variable comes from VSCode, I made it same as workspace's name, which usually is the folder's name. From debugging perspective, it is good enough for me.
After makefile is done, you could test them manually in terminal with environment variables set. You could also use other ide tools. Here is the VSCode way. I'll show two tasks here, clean and build for Maya 2016.
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"command": "make",
"type": "shell",
"echoCommand": true,
"suppressTaskName": true,
"tasks": [
{
"taskName": "clean",
"args": [
"clean"
],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": "$msCompile"
},
{
"taskName": "Build for maya 2016",
"options": {
"env": {
"mayaVersion": "2016",
"MAYA_LOCATION": "/Applications/Autodesk/maya2016/Maya.app/Contents",
"DEVKIT_LOCATION": "/Applications/Autodesk/maya2016",
"XCODE_PLATFORM_SDK_PATH": "/Applications/Xcode/6.1.1/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk",
"TARGET_CLANG_VERSION": "version 6.0",
"XCODE_PATH": "/Applications/Xcode/6.1.1/Xcode.app/Contents/Developer/",
"PROJECT_NAME": "${workspaceRootFolderName}",
"INITIALIZE_SYMBOL": "__Z16initializePlugin7MObject",
"UNINITIALIZE_SYMBOL" : "__Z18uninitializePlugin7MObject",
"CXX_LIB_FLAG" : "-stdlib=libstdc++",
"EXTRA_ARCH_FLAG": "-DREQUIRE_IOSTREAM"
}
},
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": {
"owner": "cpp",
"fileLocation": [
"relative",
"${workspaceRoot}"
],
"pattern": {
"regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
}
}]
}
I've decided to reuse makefiles from devkit, so the command here is “make” and it is running from “shell”(type). Then there is an array of tasks.
{
"taskName": "clean",
"args": [
"clean"
],
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": "$msCompile"
}
Clean is simple, the task's name is clean and it has an argument for make which is also clean. The group variable tells VSCode it is a build task so you could find it when hitting CMD+Shift+B. The problemMatcher is special for VSCode. Because it doesn't support C++ first, the builder can't grab any C++ warning and error messages. It requires user to write a matcher for it. For clean, we don't really need a matcher so I'll just use default msCompile one. In next part, I've found myself a C++ matcher on Stack overflow:)
{
"taskName": "Build for Maya 2016",
"options": {
"env": {
"mayaVersion": "2016",
"MAYA_LOCATION": "/Applications/Autodesk/maya2016/Maya.app/Contents",
"DEVKIT_LOCATION": "/Applications/Autodesk/maya2016",
"XCODE_PLATFORM_SDK_PATH": "/Applications/Xcode/6.1.1/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk",
"TARGET_CLANG_VERSION": "version 6.0",
"XCODE_PATH": "/Applications/Xcode/6.1.1/Xcode.app/Contents/Developer/",
"PROJECT_NAME": "${workspaceRootFolderName}",
"INITIALIZE_SYMBOL": "__Z16initializePlugin7MObject",
"UNINITIALIZE_SYMBOL" : "__Z18uninitializePlugin7MObject",
"CXX_LIB_FLAG" : "-stdlib=libstdc++",
"EXTRA_ARCH_FLAG": "-DREQUIRE_IOSTREAM"
}
},
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": {
"owner": "cpp",
"fileLocation": [
"relative",
"${workspaceRoot}"
],
"pattern": {
"regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
}
}
We have more environment settings for actual building task, there are small differences between most of Maya versions, you'll need to check with the makefile and buildconfig comes with each devkit. The problem matcher here is a regular expression. It grabs warning and error and show them in certain order.
Now it will build like below:
I've also added tasks for 2016.5 to 2018 so I could make it work with all these versions when I have to check if a plugin is working on different versions like regular Visual Studio build configurations.
Next step is intellisense and debug. Intellisense is an easy one, you'll just need to open .c_cpp_propertities.json inside .vscode folder and add Maya include path in it. Unlike Visual Studio, you could have extra include directories with each config to make intellisense working properly all the time, VSCode can't switch extra include directories on the fly. I make it base on Maya 2018 and take some cautions with previous versions. If you had a better solution for this, please let me know.
The last part is launch.json, it is quite simple. With cpp extension, it will generate a template item for you and you'll need to update path to it.
{ "version": "0.2.0",
"configurations": [
{
"name": "Attach to Maya 2016",
"type": "cppdbg",
"request": "attach",
"program": "/Applications/Autodesk/Maya2016/Maya.app/Contents/bin/maya",
"processId": "${command:pickProcess}",
"MIMode": "lldb"
}
]
}
Like tasks, you could have multiple debug configures. Choose a correct one before launching the debugger.
You could specify launch tasks for different workspaces or adding a default launch configuration to make it available for all workspaces in settings. Workspace's launch configure will always override the default config if it exists.
The setup is done now, When you are creating a new project, just create a subfolder in the one with buildconfig and buildrules then copy makefile and .vscode folder in previous workspace. I've also created a small sample on my GitHub, you could also use it as a bootstrap.
Comments
You can follow this conversation by subscribing to the comment feed for this post.