diff --git a/src/plugins/autotest/qtest/qttestparser.cpp b/src/plugins/autotest/qtest/qttestparser.cpp
index 93376cba7c99e0710a1852ad179d78d8e52d1c5f..476422295dc77b26fb398dd48b5c87a3bf23ef78 100644
--- a/src/plugins/autotest/qtest/qttestparser.cpp
+++ b/src/plugins/autotest/qtest/qttestparser.cpp
@@ -86,7 +86,7 @@ static bool qtTestLibDefined(const QString &fileName)
     const QList<CppTools::ProjectPart::Ptr> parts =
             CppTools::CppModelManager::instance()->projectPart(fileName);
     if (parts.size() > 0)
-        return parts.at(0)->projectDefines.contains("#define QT_TESTLIB_LIB");
+        return parts.at(0)->projectMacros.contains({"QT_TESTLIB_LIB"});
     return false;
 }
 
diff --git a/src/plugins/autotest/quick/quicktestparser.cpp b/src/plugins/autotest/quick/quicktestparser.cpp
index 0d897fe04e21c5a60dfc71b7a3c2cddba97d6919..46566952ff9c4eebc09db5f25e85d3ba8b7a2465 100644
--- a/src/plugins/autotest/quick/quicktestparser.cpp
+++ b/src/plugins/autotest/quick/quicktestparser.cpp
@@ -89,20 +89,21 @@ static bool includesQtQuickTest(const CPlusPlus::Document::Ptr &doc,
 static QString quickTestSrcDir(const CppTools::CppModelManager *cppMM,
                                const QString &fileName)
 {
-    static const QByteArray qtsd(" QUICK_TEST_SOURCE_DIR ");
     const QList<CppTools::ProjectPart::Ptr> parts = cppMM->projectPart(fileName);
     if (parts.size() > 0) {
-        QByteArray projDefines(parts.at(0)->projectDefines);
-        for (const QByteArray &line : projDefines.split('\n')) {
-            if (line.contains(qtsd)) {
-                QByteArray result = line.mid(line.indexOf(qtsd) + qtsd.length());
-                if (result.startsWith('"'))
-                    result.remove(result.length() - 1, 1).remove(0, 1);
-                if (result.startsWith("\\\""))
-                    result.remove(result.length() - 2, 2).remove(0, 2);
-                return QLatin1String(result);
-            }
-        }
+        const ProjectExplorer::Macros &macros = parts.at(0)->projectMacros;
+        auto found = std::find_if(
+                    macros.begin(),
+                    macros.end(),
+                    [] (const ProjectExplorer::Macro &macro) { return macro.key == "QUICK_TEST_SOURCE_DIR"; });
+       if (found != macros.end())  {
+           QByteArray result = found->value;
+           if (result.startsWith('"'))
+               result.remove(result.length() - 1, 1).remove(0, 1);
+           if (result.startsWith("\\\""))
+               result.remove(result.length() - 2, 2).remove(0, 2);
+           return QLatin1String(result);
+       }
     }
     return QString();
 }
diff --git a/src/plugins/autotoolsprojectmanager/autotoolsproject.cpp b/src/plugins/autotoolsprojectmanager/autotoolsproject.cpp
index a24993187eb6232cf63a0523df3d7ea300567505..01105d84979e2953fd3394a89e15591b64139961 100644
--- a/src/plugins/autotoolsprojectmanager/autotoolsproject.cpp
+++ b/src/plugins/autotoolsprojectmanager/autotoolsproject.cpp
@@ -298,7 +298,7 @@ void AutotoolsProject::updateCppCodeModel()
             ? target->activeBuildConfiguration()->buildDirectory().toString() : QString();
 
     rpp.setIncludePaths(filterIncludes(absSrc, absBuild, m_makefileParserThread->includePaths()));
-    rpp.setDefines(m_makefileParserThread->defines());
+    rpp.setMacros(m_makefileParserThread->macros());
     rpp.setFiles(m_files);
 
     m_cppCodeModelUpdater->update({this, cToolChain, cxxToolChain, k, {rpp}});
diff --git a/src/plugins/autotoolsprojectmanager/makefileparser.cpp b/src/plugins/autotoolsprojectmanager/makefileparser.cpp
index 74e29054ec6aeba3d53c3f7ca67d72a41c377072..9c3e32216b68e2189188dbd1275c463301403957 100644
--- a/src/plugins/autotoolsprojectmanager/makefileparser.cpp
+++ b/src/plugins/autotoolsprojectmanager/makefileparser.cpp
@@ -108,9 +108,9 @@ QStringList MakefileParser::includePaths() const
     return m_includePaths;
 }
 
-QByteArray MakefileParser::defines() const
+ProjectExplorer::Macros MakefileParser::macros() const
 {
-    return m_defines;
+    return m_macros;
 }
 
 QStringList MakefileParser::cflags() const
@@ -449,11 +449,7 @@ bool MakefileParser::maybeParseDefine(const QString &term)
 {
     if (term.startsWith(QLatin1String("-D"))) {
         QString def = term.mid(2); // remove the "-D"
-        QByteArray data = def.toUtf8();
-        int pos = data.indexOf('=');
-        if (pos >= 0)
-            data[pos] = ' ';
-        m_defines += (QByteArray("#define ") + data + '\n');
+        m_macros += ProjectExplorer::Macro::fromKeyValue(def);
         return true;
     }
     return false;
diff --git a/src/plugins/autotoolsprojectmanager/makefileparser.h b/src/plugins/autotoolsprojectmanager/makefileparser.h
index beaa1a57dbbb4b53d322a185ecc371074538d4bd..d6387bc29c74051a39fde8b4343a34baae21f084 100644
--- a/src/plugins/autotoolsprojectmanager/makefileparser.h
+++ b/src/plugins/autotoolsprojectmanager/makefileparser.h
@@ -27,10 +27,13 @@
 
 #pragma once
 
+#include <projectexplorer/projectmacro.h>
+
 #include <QMutex>
 #include <QStringList>
 #include <QTextStream>
 #include <QObject>
+#include <QVector>
 
 QT_FORWARD_DECLARE_CLASS(QDir)
 
@@ -49,6 +52,8 @@ class MakefileParser : public QObject
 {
     Q_OBJECT
 
+    using Macros = ProjectExplorer::Macros;
+
 public:
     /**
      * @param makefile  Filename including path of the autotools
@@ -98,7 +103,7 @@ public:
      * #define X12_HAS_DEPRECATED
      * @endcode
      */
-    QByteArray defines() const;
+     Macros macros() const;
 
     /**
      * @return List of compiler flags for C.
@@ -267,7 +272,7 @@ private:
     QStringList m_sources;      ///< Return value for MakefileParser::sources()
     QStringList m_makefiles;    ///< Return value for MakefileParser::makefiles()
     QStringList m_includePaths; ///< Return value for MakefileParser::includePaths()
-    QByteArray m_defines;       ///< Return value for MakefileParser::defines()
+    Macros m_macros;            ///< Return value for MakefileParser::macros()
     QStringList m_cflags;       ///< Return value for MakefileParser::cflags()
     QStringList m_cxxflags;     ///< Return value for MakefileParser::cxxflags()
     QStringList m_cppflags;     ///< The cpp flags, which will be part of both cflags and cxxflags
diff --git a/src/plugins/autotoolsprojectmanager/makefileparserthread.cpp b/src/plugins/autotoolsprojectmanager/makefileparserthread.cpp
index cfe7e9757fd5e101f4a9056735337e475b480cb1..a33d39599b0943ca15071dd6ed768eb9a5de8a36 100644
--- a/src/plugins/autotoolsprojectmanager/makefileparserthread.cpp
+++ b/src/plugins/autotoolsprojectmanager/makefileparserthread.cpp
@@ -61,10 +61,10 @@ QStringList MakefileParserThread::includePaths() const
     return m_includePaths;
 }
 
-QByteArray MakefileParserThread::defines() const
+ProjectExplorer::Macros MakefileParserThread::macros() const
 {
     QMutexLocker locker(&m_mutex);
-    return m_defines;
+    return m_macros;
 }
 
 QStringList MakefileParserThread::cflags() const
@@ -109,7 +109,7 @@ void MakefileParserThread::run()
     m_sources = m_parser.sources();
     m_makefiles = m_parser.makefiles();
     m_includePaths = m_parser.includePaths();
-    m_defines = m_parser.defines();
+    m_macros = m_parser.macros();
     m_cflags = m_parser.cflags();
     m_cxxflags = m_parser.cxxflags();
 }
diff --git a/src/plugins/autotoolsprojectmanager/makefileparserthread.h b/src/plugins/autotoolsprojectmanager/makefileparserthread.h
index 94b965d133b1a77b0551715ec0a3b29708913405..3e16bdccc44e34122347c7efb1f0f186437b4a6f 100644
--- a/src/plugins/autotoolsprojectmanager/makefileparserthread.h
+++ b/src/plugins/autotoolsprojectmanager/makefileparserthread.h
@@ -29,9 +29,12 @@
 
 #include "makefileparser.h"
 
+#include <projectexplorer/projectmacro.h>
+
 #include <QMutex>
 #include <QStringList>
 #include <QThread>
+#include <QVector>
 
 namespace AutotoolsProjectManager {
 namespace Internal {
@@ -47,6 +50,8 @@ class MakefileParserThread : public QThread
 {
     Q_OBJECT
 
+    using Macros = ProjectExplorer::Macros;
+
 public:
     MakefileParserThread(const QString &makefile);
 
@@ -82,10 +87,10 @@ public:
     QStringList includePaths() const;
 
     /**
-     * @return Concatenated defines. Should be invoked, after the signal
+     * @return Concatenated macros. Should be invoked, after the signal
      *         finished() has been emitted.
      */
-    QByteArray defines() const;
+    Macros macros() const;
 
     /**
      * @return List of compiler flags for C. Should be invoked, after the signal
@@ -134,7 +139,7 @@ private:
     QStringList m_sources;      ///< Return value for MakefileParserThread::sources()
     QStringList m_makefiles;    ///< Return value for MakefileParserThread::makefiles()
     QStringList m_includePaths; ///< Return value for MakefileParserThread::includePaths()
-    QByteArray m_defines;       ///< Return value for MakefileParserThread::defines()
+    Macros m_macros;            ///< Return value for MakefileParserThread::macros()
     QStringList m_cflags;       ///< Return value for MakefileParserThread::cflags()
     QStringList m_cxxflags;     ///< Return value for MakefileParserThread::cxxflags()
 };
diff --git a/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp b/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp
index db09aea6723be41bec1cacf810500c3009c1af23..8a9736a15aa23942f57689256a7dfc8ff0f7163f 100644
--- a/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp
+++ b/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp
@@ -512,7 +512,7 @@ bool OpenEditorAtCursorPosition::waitUntil(const std::function<bool ()> &conditi
 }
 
 CppTools::ProjectPart::Ptr createProjectPart(const QStringList &files,
-                                             const QString &defines)
+                                             const ProjectExplorer::Macros &macros)
 {
     using namespace CppTools;
 
@@ -521,19 +521,19 @@ CppTools::ProjectPart::Ptr createProjectPart(const QStringList &files,
     foreach (const QString &file, files)
         projectPart->files.append(ProjectFile(file, ProjectFile::classify(file)));
     projectPart->qtVersion = ProjectPart::NoQt;
-    projectPart->projectDefines = defines.toUtf8();
+    projectPart->projectMacros = macros;
 
     return projectPart;
 }
 
 CppTools::ProjectInfo createProjectInfo(ProjectExplorer::Project *project,
                                         const QStringList &files,
-                                        const QString &defines)
+                                        const ProjectExplorer::Macros &macros)
 {
     using namespace CppTools;
     QTC_ASSERT(project, return ProjectInfo());
 
-    const CppTools::ProjectPart::Ptr projectPart = createProjectPart(files, defines);
+    const CppTools::ProjectPart::Ptr projectPart = createProjectPart(files, macros);
     ProjectInfo projectInfo = ProjectInfo(project);
     projectInfo.appendProjectPart(projectPart);
     return projectInfo;
@@ -543,11 +543,11 @@ class ProjectLoader
 {
 public:
     ProjectLoader(const QStringList &projectFiles,
-                  const QString &projectDefines,
+                  const ProjectExplorer::Macros &projectMacros,
                   bool testOnlyForCleanedProjects = false)
         : m_project(0)
         , m_projectFiles(projectFiles)
-        , m_projectDefines(projectDefines)
+        , m_projectMacros(projectMacros)
         , m_helper(0, testOnlyForCleanedProjects)
     {
     }
@@ -557,17 +557,17 @@ public:
         m_project = m_helper.createProject(QLatin1String("testProject"));
         const CppTools::ProjectInfo projectInfo = createProjectInfo(m_project,
                                                                     m_projectFiles,
-                                                                    m_projectDefines);
+                                                                    m_projectMacros);
         const QSet<QString> filesIndexedAfterLoading = m_helper.updateProjectInfo(projectInfo);
         return m_projectFiles.size() == filesIndexedAfterLoading.size();
     }
 
-    bool updateProject(const QString &updatedProjectDefines)
+    bool updateProject(const ProjectExplorer::Macros &updatedProjectMacros)
     {
         QTC_ASSERT(m_project, return false);
         const CppTools::ProjectInfo updatedProjectInfo = createProjectInfo(m_project,
                                                                            m_projectFiles,
-                                                                           updatedProjectDefines);
+                                                                           updatedProjectMacros);
         return updateProjectInfo(updatedProjectInfo);
 
     }
@@ -581,7 +581,7 @@ private:
 
     ProjectExplorer::Project *m_project;
     QStringList m_projectFiles;
-    QString m_projectDefines;
+    ProjectExplorer::Macros m_projectMacros;
     CppTools::Tests::ModelManagerTestHelper m_helper;
 };
 
@@ -865,8 +865,7 @@ void ClangCodeCompletionTest::testCompleteProjectDependingCode()
     const TestDocument testDocument("completionWithProject.cpp");
     QVERIFY(testDocument.isCreatedAndHasValidCursorPosition());
 
-    ProjectLoader projectLoader(QStringList(testDocument.filePath),
-                                _("#define PROJECT_CONFIGURATION_1\n"));
+    ProjectLoader projectLoader(QStringList(testDocument.filePath), {{"PROJECT_CONFIGURATION_1"}});
     QVERIFY(projectLoader.load());
 
     OpenEditorAtCursorPosition openEditor(testDocument);
@@ -891,7 +890,7 @@ void ClangCodeCompletionTest::testCompleteProjectDependingCodeAfterChangingProje
     {
         // Check completion with project configuration 1
         ProjectLoader projectLoader(QStringList(testDocument.filePath),
-                                    _("#define PROJECT_CONFIGURATION_1\n"),
+                                    {{"PROJECT_CONFIGURATION_1"}},
                                     /* testOnlyForCleanedProjects= */ true);
         QVERIFY(projectLoader.load());
         openEditor.waitUntilProjectPartChanged(QLatin1String("myproject.project"));
@@ -902,7 +901,7 @@ void ClangCodeCompletionTest::testCompleteProjectDependingCodeAfterChangingProje
         QVERIFY(!hasItem(proposal, "projectConfiguration2"));
 
         // Check completion with project configuration 2
-        QVERIFY(projectLoader.updateProject(_("#define PROJECT_CONFIGURATION_2\n")));
+        QVERIFY(projectLoader.updateProject({{"PROJECT_CONFIGURATION_2"}}));
         proposal = completionResults(openEditor.editor());
 
         QVERIFY(!hasItem(proposal, "projectConfiguration1"));
diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp
index ad6363bc0e6be82109b68bec05096f20d4c41bfe..0cfafa08e118f7609d9a09e7df428d947ff60a84 100644
--- a/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp
+++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp
@@ -224,8 +224,8 @@ public:
         optionsBuilder.addDefineToAvoidIncludingGccOrMinGwIntrinsics();
         const Core::Id type = projectPart.toolchainType;
         if (type != ProjectExplorer::Constants::MSVC_TOOLCHAIN_TYPEID)
-            optionsBuilder.addDefines(projectPart.toolchainDefines);
-        optionsBuilder.addDefines(projectPart.projectDefines);
+            optionsBuilder.addMacros(projectPart.toolChainMacros);
+        optionsBuilder.addMacros(projectPart.projectMacros);
         optionsBuilder.undefineClangVersionMacrosForMsvc();
         optionsBuilder.undefineCppLanguageFeatureMacrosForMsvc2015();
         optionsBuilder.addHeaderPathOptions();
diff --git a/src/plugins/cmakeprojectmanager/cmakecbpparser.cpp b/src/plugins/cmakeprojectmanager/cmakecbpparser.cpp
index 591768f515e8c279c3c784b635faf29ed5582f93..96cf7d138df5bd308ec7e10ee17edf75b587fc57 100644
--- a/src/plugins/cmakeprojectmanager/cmakecbpparser.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakecbpparser.cpp
@@ -30,6 +30,7 @@
 #include <utils/fileutils.h>
 #include <utils/stringutils.h>
 #include <utils/algorithm.h>
+#include <projectexplorer/projectmacro.h>
 #include <projectexplorer/projectnodes.h>
 
 #include <QLoggingCategory>
@@ -71,8 +72,9 @@ void CMakeCbpParser::sortFiles()
     qCDebug(log) << "# Pre Dump    #";
     qCDebug(log) << "###############";
     foreach (const CMakeBuildTarget &target, m_buildTargets)
-        qCDebug(log) << target.title << target.sourceDirectory <<
-                 target.includeFiles << target.defines << target.files << "\n";
+        qCDebug(log) << target.title << target.sourceDirectory << target.includeFiles
+                     << ProjectExplorer::Macro::toByteArray(target.macros)
+                     << target.files << "\n";
 
     // find a good build target to fall back
     int fallbackIndex = 0;
@@ -153,7 +155,9 @@ void CMakeCbpParser::sortFiles()
     qCDebug(log) << "# After Dump  #";
     qCDebug(log) << "###############";
     foreach (const CMakeBuildTarget &target, m_buildTargets)
-        qCDebug(log) << target.title << target.sourceDirectory << target.includeFiles << target.defines << target.files << "\n";
+        qCDebug(log) << target.title << target.sourceDirectory << target.includeFiles
+                     << ProjectExplorer::Macro::toByteArray(target.macros)
+                     << target.files << "\n";
 }
 
 bool CMakeCbpParser::parseCbpFile(CMakeTool::PathMapper mapper, const FileName &fileName,
@@ -397,12 +401,8 @@ void CMakeCbpParser::parseAdd()
         m_buildTarget.compilerOptions.append(compilerOption);
         int macroNameIndex = compilerOption.indexOf("-D") + 2;
         if (macroNameIndex != 1) {
-            int assignIndex = compilerOption.indexOf('=', macroNameIndex);
-            if (assignIndex != -1)
-                compilerOption[assignIndex] = ' ';
-            m_buildTarget.defines.append("#define ");
-            m_buildTarget.defines.append(compilerOption.mid(macroNameIndex).toUtf8());
-            m_buildTarget.defines.append('\n');
+            const QString keyValue = compilerOption.mid(macroNameIndex);
+            m_buildTarget.macros.append(ProjectExplorer::Macro::fromKeyValue(keyValue));
         }
     }
 
diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp
index 42226ec6bdf4bcdc1177976bcb89c33f44f517f6..211f564cc83ba9d9927e6780d5572d4a6a7531a2 100644
--- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp
@@ -559,7 +559,7 @@ void CMakeBuildTarget::clear()
     targetType = UtilityType;
     includeFiles.clear();
     compilerOptions.clear();
-    defines.clear();
+    macros.clear();
     files.clear();
 }
 
diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.h b/src/plugins/cmakeprojectmanager/cmakeproject.h
index cb035dddf8cadc7ee3b25027c9341d00439d7e04..8083cf9dec128acdd5ba1af1f6137d79f6e53e70 100644
--- a/src/plugins/cmakeprojectmanager/cmakeproject.h
+++ b/src/plugins/cmakeprojectmanager/cmakeproject.h
@@ -30,6 +30,7 @@
 #include "treescanner.h"
 
 #include <projectexplorer/extracompiler.h>
+#include <projectexplorer/projectmacro.h>
 #include <projectexplorer/project.h>
 
 #include <utils/fileutils.h>
@@ -72,7 +73,7 @@ public:
     // code model
     QList<Utils::FileName> includeFiles;
     QStringList compilerOptions;
-    QByteArray defines;
+    ProjectExplorer::Macros macros;
     QList<Utils::FileName> files;
 
     void clear();
diff --git a/src/plugins/cmakeprojectmanager/servermodereader.cpp b/src/plugins/cmakeprojectmanager/servermodereader.cpp
index fce7288e076a40fec9887b1146d5c6717618aa7c..1b97b6b8381436bf582d2df135eb285277cb28ea 100644
--- a/src/plugins/cmakeprojectmanager/servermodereader.cpp
+++ b/src/plugins/cmakeprojectmanager/servermodereader.cpp
@@ -45,6 +45,8 @@
 #include <utils/qtcassert.h>
 #include <utils/qtcprocess.h>
 
+#include <QVector>
+
 using namespace ProjectExplorer;
 using namespace Utils;
 
@@ -325,14 +327,6 @@ void ServerModeReader::updateCodeModel(CppTools::RawProjectParts &rpps)
     int counter = 0;
     for (const FileGroup *fg : Utils::asConst(m_fileGroups)) {
         ++counter;
-        const QString defineArg
-                = transform(fg->defines, [](const QString &s) -> QString {
-                    QString result = QString::fromLatin1("#define ") + s;
-                    int assignIndex = result.indexOf('=');
-                    if (assignIndex != -1)
-                        result[assignIndex] = ' ';
-                    return result;
-                }).join('\n');
         const QStringList flags = QtcProcess::splitArgs(fg->compileFlags);
         const QStringList includes = transform(fg->includePaths, [](const IncludePath *ip)  { return ip->path.toString(); });
 
@@ -340,7 +334,7 @@ void ServerModeReader::updateCodeModel(CppTools::RawProjectParts &rpps)
         rpp.setProjectFileLocation(fg->target->sourceDirectory.toString() + "/CMakeLists.txt");
         rpp.setBuildSystemTarget(fg->target->name);
         rpp.setDisplayName(fg->target->name + QString::number(counter));
-        rpp.setDefines(defineArg.toUtf8());
+        rpp.setMacros(fg->macros);
         rpp.setIncludePaths(includes);
 
         CppTools::RawProjectPartFlags cProjectFlags;
@@ -523,7 +517,9 @@ ServerModeReader::FileGroup *ServerModeReader::extractFileGroupData(const QVaria
     auto fileGroup = new FileGroup;
     fileGroup->target = t;
     fileGroup->compileFlags = data.value("compileFlags").toString();
-    fileGroup->defines = data.value("defines").toStringList();
+    fileGroup->macros = Utils::transform<QVector>(data.value("defines").toStringList(), [](const QString &s) {
+        return ProjectExplorer::Macro::fromKeyValue(s);
+    });
     fileGroup->includePaths = transform(data.value("includePath").toList(),
                                         [](const QVariant &i) -> IncludePath* {
         const QVariantMap iData = i.toMap();
@@ -662,7 +658,7 @@ void ServerModeReader::fixTarget(ServerModeReader::Target *target) const
 
     for (const FileGroup *group : Utils::asConst(target->fileGroups)) {
         if (group->includePaths.isEmpty() && group->compileFlags.isEmpty()
-                && group->defines.isEmpty())
+                && group->macros.isEmpty())
             continue;
 
         const FileGroup *fallback = languageFallbacks.value(group->language);
@@ -688,13 +684,13 @@ void ServerModeReader::fixTarget(ServerModeReader::Target *target) const
         (*it)->language = fallback->language.isEmpty() ? "CXX" : fallback->language;
 
         if (*it == fallback
-                || !(*it)->includePaths.isEmpty() || !(*it)->defines.isEmpty()
+                || !(*it)->includePaths.isEmpty() || !(*it)->macros.isEmpty()
                 || !(*it)->compileFlags.isEmpty())
             continue;
 
         for (const IncludePath *ip : fallback->includePaths)
             (*it)->includePaths.append(new IncludePath(*ip));
-        (*it)->defines = fallback->defines;
+        (*it)->macros = fallback->macros;
         (*it)->compileFlags = fallback->compileFlags;
     }
 }
diff --git a/src/plugins/cmakeprojectmanager/servermodereader.h b/src/plugins/cmakeprojectmanager/servermodereader.h
index 8dc4f5e35392549322311aacff09e1a6a886c84d..f861483227a566dc5f892ce1c4cb849798a3e0a9 100644
--- a/src/plugins/cmakeprojectmanager/servermodereader.h
+++ b/src/plugins/cmakeprojectmanager/servermodereader.h
@@ -86,7 +86,7 @@ private:
 
         Target *target = nullptr;
         QString compileFlags;
-        QStringList defines;
+        ProjectExplorer::Macros macros;
         QList<IncludePath *> includePaths;
         QString language;
         QList<Utils::FileName> sources;
diff --git a/src/plugins/cmakeprojectmanager/tealeafreader.cpp b/src/plugins/cmakeprojectmanager/tealeafreader.cpp
index ca3794c89d5f201a1f21b832cecd515c334c189a..69b170d38d639a091408c983a260d054068dae44 100644
--- a/src/plugins/cmakeprojectmanager/tealeafreader.cpp
+++ b/src/plugins/cmakeprojectmanager/tealeafreader.cpp
@@ -384,7 +384,7 @@ void TeaLeafReader::updateCodeModel(CppTools::RawProjectParts &rpps)
         cxxProjectFlags.commandLineFlags = cxxflags;
         rpp.setFlagsForCxx(cxxProjectFlags);
 
-        rpp.setDefines(cbt.defines);
+        rpp.setMacros(cbt.macros);
         rpp.setDisplayName(cbt.title);
         rpp.setFiles(transform(cbt.files, [](const FileName &fn) { return fn.toString(); }));
 
diff --git a/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp b/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp
index 6283d43d497a878cd08782a089cda75bcd088f22..51ff07584527c51178e8c69967c2beaffa1d6b54 100644
--- a/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp
+++ b/src/plugins/cppeditor/cppcodemodelinspectordialog.cpp
@@ -35,6 +35,7 @@
 #include <cpptools/cppmodelmanager.h>
 #include <cpptools/cpptoolsbridge.h>
 #include <cpptools/cppworkingcopy.h>
+#include <projectexplorer/projectmacro.h>
 #include <projectexplorer/project.h>
 
 #include <cplusplus/CppDocument.h>
@@ -49,6 +50,7 @@
 #include <QSortFilterProxyModel>
 
 #include <algorithm>
+#include <numeric>
 
 using namespace CPlusPlus;
 using namespace CppTools;
@@ -756,7 +758,7 @@ class MacrosModel : public QAbstractListModel
     Q_OBJECT
 public:
     MacrosModel(QObject *parent);
-    void configure(const QList<Macro> &macros);
+    void configure(const QList<CPlusPlus::Macro> &macros);
     void clear();
 
     enum Columns { LineNumberColumn, MacroColumn, ColumnCount };
@@ -767,14 +769,14 @@ public:
     QVariant headerData(int section, Qt::Orientation orientation, int role) const;
 
 private:
-    QList<Macro> m_macros;
+    QList<CPlusPlus::Macro> m_macros;
 };
 
 MacrosModel::MacrosModel(QObject *parent) : QAbstractListModel(parent)
 {
 }
 
-void MacrosModel::configure(const QList<Macro> &macros)
+void MacrosModel::configure(const QList<CPlusPlus::Macro> &macros)
 {
     emit layoutAboutToBeChanged();
     m_macros = macros;
@@ -802,7 +804,7 @@ QVariant MacrosModel::data(const QModelIndex &index, int role) const
 {
     const int column = index.column();
     if (role == Qt::DisplayRole || (role == Qt::ToolTipRole && column == MacroColumn)) {
-        const Macro macro = m_macros.at(index.row());
+        const CPlusPlus::Macro macro = m_macros.at(index.row());
         if (column == LineNumberColumn)
             return macro.line();
         else if (column == MacroColumn)
@@ -1614,7 +1616,8 @@ void CppCodeModelInspectorDialog::refresh()
     }
 
     // Merged entities
-    dumper.dumpMergedEntities(cmmi->headerPaths(), cmmi->definedMacros());
+    dumper.dumpMergedEntities(cmmi->headerPaths(),
+                              ProjectExplorer::Macro::toByteArray(cmmi->definedMacros()));
 }
 
 enum DocumentTabs {
@@ -1758,6 +1761,15 @@ void CppCodeModelInspectorDialog::clearProjectPartData()
                                      partTabName(ProjectPartPrecompiledHeadersTab));
 }
 
+static int defineCount(const ProjectExplorer::Macros &macros)
+{
+    using ProjectExplorer::Macro;
+    return int(std::count_if(
+                   macros.begin(),
+                   macros.end(),
+                   [](const Macro &macro) { return macro.type == ProjectExplorer::MacroType::Define; }));
+}
+
 void CppCodeModelInspectorDialog::updateProjectPartData(const ProjectPart::Ptr &part)
 {
     QTC_ASSERT(part, return);
@@ -1802,16 +1814,10 @@ void CppCodeModelInspectorDialog::updateProjectPartData(const ProjectPart::Ptr &
     m_ui->projectPartTab->setTabText(ProjectPartFilesTab,
         partTabName(ProjectPartFilesTab, part->files.size()));
 
-    // Defines
-    const QList<QByteArray> defineLines = part->toolchainDefines.split('\n')
-        + part->projectDefines.split('\n');
-    int numberOfDefines = 0;
-    foreach (const QByteArray &line, defineLines) {
-        if (line.startsWith("#define "))
-            ++numberOfDefines;
-    }
-    m_ui->partToolchainDefinesEdit->setPlainText(QString::fromUtf8(part->toolchainDefines));
-    m_ui->partProjectDefinesEdit->setPlainText(QString::fromUtf8(part->projectDefines));
+    int numberOfDefines = defineCount(part->toolChainMacros) + defineCount(part->projectMacros);
+
+    m_ui->partToolchainDefinesEdit->setPlainText(QString::fromUtf8(ProjectExplorer::Macro::toByteArray(part->toolChainMacros)));
+    m_ui->partProjectDefinesEdit->setPlainText(QString::fromUtf8(ProjectExplorer::Macro::toByteArray(part->projectMacros)));
     m_ui->projectPartTab->setTabText(ProjectPartDefinesTab,
         partTabName(ProjectPartDefinesTab, numberOfDefines));
 
diff --git a/src/plugins/cpptools/builtineditordocumentparser.cpp b/src/plugins/cpptools/builtineditordocumentparser.cpp
index ac3cf3ee2a3e4257188acbbd946cfdfcafe9319c..3fb407cb2da43cd4af5364f90f14583c34d5468a 100644
--- a/src/plugins/cpptools/builtineditordocumentparser.cpp
+++ b/src/plugins/cpptools/builtineditordocumentparser.cpp
@@ -26,6 +26,7 @@
 #include "builtineditordocumentparser.h"
 #include "cppsourceprocessor.h"
 
+#include <projectexplorer/projectmacro.h>
 #include <projectexplorer/projectexplorerconstants.h>
 
 #include <utils/qtcassert.h>
@@ -91,9 +92,9 @@ void BuiltinEditorDocumentParser::updateImpl(const QFutureInterface<void> &futur
     }
 
     if (const ProjectPart::Ptr part = baseState.projectPartInfo.projectPart) {
-        configFile += part->toolchainDefines;
+        configFile += ProjectExplorer::Macro::toByteArray(part->toolChainMacros);
         configFile += overwrittenToolchainDefines(*part.data());
-        configFile += part->projectDefines;
+        configFile += ProjectExplorer::Macro::toByteArray(part->projectMacros);
         if (!part->projectConfigFile.isEmpty())
             configFile += ProjectPart::readProjectConfigFile(part);
         headerPaths = part->headerPaths;
diff --git a/src/plugins/cpptools/compileroptionsbuilder.cpp b/src/plugins/cpptools/compileroptionsbuilder.cpp
index 170279c9b8fc5c313b98fe3b75ee42d61ac33b55..079f76fc6aa66f80339bd618da69215adbee5547 100644
--- a/src/plugins/cpptools/compileroptionsbuilder.cpp
+++ b/src/plugins/cpptools/compileroptionsbuilder.cpp
@@ -48,44 +48,9 @@ void CompilerOptionsBuilder::add(const QString &option)
     m_options.append(option);
 }
 
-struct Macro {
-    static Macro fromDefineDirective(const QByteArray &defineDirective);
-    QByteArray toDefineOption(const QByteArray &option) const;
-
-    QByteArray name;
-    QByteArray value;
-};
-
-Macro Macro::fromDefineDirective(const QByteArray &defineDirective)
+void CompilerOptionsBuilder::addDefine(const ProjectExplorer::Macro &macro)
 {
-    const QByteArray str = defineDirective.mid(8);
-    const int spaceIdx = str.indexOf(' ');
-    const bool hasValue = spaceIdx != -1;
-
-    Macro macro;
-    macro.name = str.left(hasValue ? spaceIdx : str.size());
-    if (hasValue)
-        macro.value = str.mid(spaceIdx + 1);
-
-    return macro;
-}
-
-QByteArray Macro::toDefineOption(const QByteArray &option) const
-{
-    QByteArray result;
-
-    result.append(option);
-    result.append(name);
-    result.append('=');
-    if (!value.isEmpty())
-        result.append(value);
-
-    return result;
-}
-
-void CompilerOptionsBuilder::addDefine(const QByteArray &defineDirective)
-{
-    m_options.append(defineDirectiveToDefineOption(defineDirective));
+    m_options.append(defineDirectiveToDefineOption(macro));
 }
 
 void CompilerOptionsBuilder::addWordWidth()
@@ -162,19 +127,19 @@ void CompilerOptionsBuilder::addPrecompiledHeaderOptions(PchUsage pchUsage)
 
 void CompilerOptionsBuilder::addToolchainAndProjectDefines()
 {
-    addDefines(m_projectPart.toolchainDefines);
-    addDefines(m_projectPart.projectDefines);
+    addMacros(m_projectPart.toolChainMacros);
+    addMacros(m_projectPart.projectMacros);
 }
 
-void CompilerOptionsBuilder::addDefines(const QByteArray &defineDirectives)
+void CompilerOptionsBuilder::addMacros(const ProjectExplorer::Macros &macros)
 {
     QStringList result;
 
-    foreach (QByteArray def, defineDirectives.split('\n')) {
-        if (def.isEmpty() || excludeDefineDirective(def))
+    for (const ProjectExplorer::Macro &macro : macros) {
+        if (excludeDefineDirective(macro))
             continue;
 
-        const QString defineOption = defineDirectiveToDefineOption(def);
+        const QString defineOption = defineDirectiveToDefineOption(macro);
         if (!result.contains(defineOption))
             result.append(defineOption);
     }
@@ -303,8 +268,8 @@ void CompilerOptionsBuilder::addDefineToAvoidIncludingGccOrMinGwIntrinsics()
     const Core::Id type = m_projectPart.toolchainType;
     if (type == ProjectExplorer::Constants::MINGW_TOOLCHAIN_TYPEID
             || type == ProjectExplorer::Constants::GCC_TOOLCHAIN_TYPEID) {
-        addDefine("#define _X86INTRIN_H_INCLUDED");
-        addDefine("#define BOOST_UUID_NO_SIMD");
+        addDefine({"_X86INTRIN_H_INCLUDED"});
+        addDefine({"BOOST_UUID_NO_SIMD"});
     }
 }
 
@@ -315,14 +280,10 @@ static QByteArray toMsCompatibilityVersionFormat(const QByteArray &mscFullVer)
          + mscFullVer.mid(2, 2);
 }
 
-static QByteArray msCompatibilityVersionFromDefines(const QByteArray &defineDirectives)
+static QByteArray msCompatibilityVersionFromDefines(const ProjectExplorer::Macros &macros)
 {
-    foreach (QByteArray defineDirective, defineDirectives.split('\n')) {
-        if (defineDirective.isEmpty())
-            continue;
-
-        const Macro macro = Macro::fromDefineDirective(defineDirective);
-        if (macro.name == "_MSC_FULL_VER")
+    for (const ProjectExplorer::Macro &macro : macros) {
+        if (macro.key == "_MSC_FULL_VER")
             return toMsCompatibilityVersionFormat(macro.value);
     }
 
@@ -332,8 +293,8 @@ static QByteArray msCompatibilityVersionFromDefines(const QByteArray &defineDire
 void CompilerOptionsBuilder::addMsvcCompatibilityVersion()
 {
     if (m_projectPart.toolchainType == ProjectExplorer::Constants::MSVC_TOOLCHAIN_TYPEID) {
-        const QByteArray defines = m_projectPart.toolchainDefines + m_projectPart.projectDefines;
-        const QByteArray msvcVersion = msCompatibilityVersionFromDefines(defines);
+        const ProjectExplorer::Macros macros = m_projectPart.toolChainMacros + m_projectPart.projectMacros;
+        const QByteArray msvcVersion = msCompatibilityVersionFromDefines(macros);
 
         if (!msvcVersion.isEmpty()) {
             const QString option = QLatin1String("-fms-compatibility-version=")
@@ -398,7 +359,7 @@ void CompilerOptionsBuilder::addDefineFloat128ForMingw()
     // CLANG-UPGRADE-CHECK: Workaround still needed?
     // https://llvm.org/bugs/show_bug.cgi?id=30685
     if (m_projectPart.toolchainType == ProjectExplorer::Constants::MINGW_TOOLCHAIN_TYPEID)
-        addDefine("#define __float128 short");
+        addDefine({"__float128", "short", ProjectExplorer::MacroType::Define});
 }
 
 QString CompilerOptionsBuilder::includeDirOption() const
@@ -406,12 +367,25 @@ QString CompilerOptionsBuilder::includeDirOption() const
     return QLatin1String("-I");
 }
 
-QString CompilerOptionsBuilder::defineDirectiveToDefineOption(const QByteArray &defineDirective)
+QByteArray CompilerOptionsBuilder::macroOption(const ProjectExplorer::Macro &macro) const
+{
+    switch (macro.type) {
+        case ProjectExplorer::MacroType::Define:     return defineOption().toUtf8();
+        case ProjectExplorer::MacroType::Undefine:   return undefineOption().toUtf8();
+        default: return QByteArray();
+    }
+}
+
+QByteArray CompilerOptionsBuilder::toDefineOption(const ProjectExplorer::Macro &macro) const
+{
+    return macro.toKeyValue(macroOption(macro));
+}
+
+QString CompilerOptionsBuilder::defineDirectiveToDefineOption(const ProjectExplorer::Macro &macro) const
 {
-    const Macro macro = Macro::fromDefineDirective(defineDirective);
-    const QByteArray option = macro.toDefineOption(defineOption().toLatin1());
+    const QByteArray option = toDefineOption(macro);
 
-    return QString::fromLatin1(option);
+    return QString::fromUtf8(option);
 }
 
 QString CompilerOptionsBuilder::defineOption() const
@@ -435,11 +409,11 @@ static bool isGccOrMinGwToolchain(const Core::Id &toolchainType)
         || toolchainType == ProjectExplorer::Constants::MINGW_TOOLCHAIN_TYPEID;
 }
 
-bool CompilerOptionsBuilder::excludeDefineDirective(const QByteArray &defineDirective) const
+bool CompilerOptionsBuilder::excludeDefineDirective(const ProjectExplorer::Macro &macro) const
 {
     // This is a quick fix for QTCREATORBUG-11501.
     // TODO: do a proper fix, see QTCREATORBUG-11709.
-    if (defineDirective.startsWith("#define __cplusplus"))
+    if (macro.key == "__cplusplus")
         return true;
 
     // gcc 4.9 has:
@@ -449,7 +423,7 @@ bool CompilerOptionsBuilder::excludeDefineDirective(const QByteArray &defineDire
     // override clang's own (non-macro, it seems) definitions of the symbols on the left-hand
     // side.
     if (isGccOrMinGwToolchain(m_projectPart.toolchainType)
-            && defineDirective.contains("has_include")) {
+            && macro.key.contains("has_include")) {
         return true;
     }
 
@@ -459,14 +433,14 @@ bool CompilerOptionsBuilder::excludeDefineDirective(const QByteArray &defineDire
     // __builtin_va_arg_pack, which clang does not support (yet), so avoid
     // including those.
     if (m_projectPart.toolchainType == ProjectExplorer::Constants::GCC_TOOLCHAIN_TYPEID
-            && defineDirective.startsWith("#define _FORTIFY_SOURCE")) {
+            && macro.key == "_FORTIFY_SOURCE") {
         return true;
     }
 
     // MinGW 6 supports some fancy asm output flags and uses them in an
     // intrinsics header pulled in by windows.h. Clang does not know them.
     if (m_projectPart.toolchainType == ProjectExplorer::Constants::MINGW_TOOLCHAIN_TYPEID
-            && defineDirective.startsWith("#define __GCC_ASM_FLAG_OUTPUTS__")) {
+            && macro.key == "__GCC_ASM_FLAG_OUTPUTS__") {
         return true;
     }
 
diff --git a/src/plugins/cpptools/compileroptionsbuilder.h b/src/plugins/cpptools/compileroptionsbuilder.h
index 75b0c4f530debc0ca0e681e4156ab5862242c885..48c23b3e9127c1f36ebd7cd26c5e3ae0eb096848 100644
--- a/src/plugins/cpptools/compileroptionsbuilder.h
+++ b/src/plugins/cpptools/compileroptionsbuilder.h
@@ -46,7 +46,7 @@ public:
 
     // Add custom options
     void add(const QString &option);
-    void addDefine(const QByteArray &defineDirective);
+    void addDefine(const ProjectExplorer::Macro &marco);
 
     // Add options based on project part
     void addWordWidth();
@@ -55,7 +55,7 @@ public:
     void addHeaderPathOptions();
     void addPrecompiledHeaderOptions(PchUsage pchUsage);
     void addToolchainAndProjectDefines();
-    void addDefines(const QByteArray &defineDirectives);
+    void addMacros(const ProjectExplorer::Macros &macros);
     virtual void addLanguageOption(ProjectFile::Kind fileKind);
     virtual void addOptionsForLanguage(bool checkForBorlandExtensions = true);
 
@@ -67,7 +67,7 @@ public:
     void addDefineFloat128ForMingw();
 
 protected:
-    virtual bool excludeDefineDirective(const QByteArray &defineDirective) const;
+    virtual bool excludeDefineDirective(const ProjectExplorer::Macro &macro) const;
     virtual bool excludeHeaderPath(const QString &headerPath) const;
 
     virtual QString defineOption() const;
@@ -78,7 +78,9 @@ protected:
     const ProjectPart m_projectPart;
 
 private:
-    QString defineDirectiveToDefineOption(const QByteArray &defineDirective);
+    QByteArray macroOption(const ProjectExplorer::Macro &macro) const;
+    QByteArray toDefineOption(const ProjectExplorer::Macro &macro) const;
+    QString defineDirectiveToDefineOption(const ProjectExplorer::Macro &marco) const;
 
     QStringList m_options;
 };
diff --git a/src/plugins/cpptools/cppcodemodelinspectordumper.cpp b/src/plugins/cpptools/cppcodemodelinspectordumper.cpp
index 199f185d639f37f7901541d132bacc8b986a8a3b..8797a2ba6360be4add87be60fe52cc95d0a8c8f4 100644
--- a/src/plugins/cpptools/cppcodemodelinspectordumper.cpp
+++ b/src/plugins/cpptools/cppcodemodelinspectordumper.cpp
@@ -31,6 +31,7 @@
 #include <app/app_version.h>
 #include <coreplugin/icore.h>
 #include <cpptools/cppprojectfile.h>
+#include <projectexplorer/projectmacro.h>
 #include <projectexplorer/project.h>
 #include <utils/algorithm.h>
 #include <utils/temporarydirectory.h>
@@ -495,15 +496,17 @@ void Dumper::dumpProjectInfos( const QList<ProjectInfo> &projectInfos)
                 }
             }
 
-            if (!part->toolchainDefines.isEmpty()) {
+            if (!part->toolChainMacros.isEmpty()) {
                 m_out << i3 << "Toolchain Defines:{{{4\n";
-                const QList<QByteArray> defineLines = part->toolchainDefines.split('\n');
+                const QList<QByteArray> defineLines =
+                        ProjectExplorer::Macro::toByteArray(part->toolChainMacros).split('\n');
                 foreach (const QByteArray &defineLine, defineLines)
                     m_out << i4 << defineLine << "\n";
             }
-            if (!part->projectDefines.isEmpty()) {
+            if (!part->projectMacros.isEmpty()) {
                 m_out << i3 << "Project Defines:{{{4\n";
-                const QList<QByteArray> defineLines = part->projectDefines.split('\n');
+                const QList<QByteArray> defineLines =
+                        ProjectExplorer::Macro::toByteArray(part->projectMacros).split('\n');
                 foreach (const QByteArray &defineLine, defineLines)
                     m_out << i4 << defineLine << "\n";
             }
diff --git a/src/plugins/cpptools/cppcompletionassist.cpp b/src/plugins/cpptools/cppcompletionassist.cpp
index 26b139e6478130c73284b47f97b85d1105560e0b..3e1c19399e633277ceb4894bef12a559b6a5e2dc 100644
--- a/src/plugins/cpptools/cppcompletionassist.cpp
+++ b/src/plugins/cpptools/cppcompletionassist.cpp
@@ -1902,7 +1902,7 @@ void InternalCppCompletionAssistProcessor::addMacros_helper(const Snapshot &snap
     foreach (const Document::Include &i, doc->resolvedIncludes())
         addMacros_helper(snapshot, i.resolvedFileName(), processed, definedMacros);
 
-    foreach (const Macro &macro, doc->definedMacros()) {
+    foreach (const CPlusPlus::Macro &macro, doc->definedMacros()) {
         const QString macroName = macro.nameToQString();
         if (!macro.isHidden())
             definedMacros->insert(macroName);
diff --git a/src/plugins/cpptools/cppfindreferences.cpp b/src/plugins/cpptools/cppfindreferences.cpp
index a27233f1c1d258e279fcbbd8c9db2e8a75a38d99..9e521ac46ffc2ee41ac34fbc8ce7d11839d85b36 100644
--- a/src/plugins/cpptools/cppfindreferences.cpp
+++ b/src/plugins/cpptools/cppfindreferences.cpp
@@ -607,13 +607,13 @@ class FindMacroUsesInFile: public std::unary_function<QString, QList<Usage> >
 {
     const WorkingCopy workingCopy;
     const Snapshot snapshot;
-    const Macro &macro;
+    const CPlusPlus::Macro &macro;
     QFutureInterface<Usage> *future;
 
 public:
     FindMacroUsesInFile(const WorkingCopy &workingCopy,
                         const Snapshot snapshot,
-                        const Macro &macro,
+                        const CPlusPlus::Macro &macro,
                         QFutureInterface<Usage> *future)
         : workingCopy(workingCopy), snapshot(snapshot), macro(macro), future(future)
     { }
@@ -632,7 +632,7 @@ restart_search:
 
         usages.clear();
         foreach (const Document::MacroUse &use, doc->macroUses()) {
-            const Macro &useMacro = use.macro();
+            const CPlusPlus::Macro &useMacro = use.macro();
 
             if (useMacro.fileName() == macro.fileName()) { // Check if this is a match, but possibly against an outdated document.
                 if (source.isEmpty())
@@ -687,7 +687,7 @@ restart_search:
 static void findMacroUses_helper(QFutureInterface<Usage> &future,
                                  const WorkingCopy workingCopy,
                                  const Snapshot snapshot,
-                                 const Macro macro)
+                                 const CPlusPlus::Macro macro)
 {
     const Utils::FileName sourceFile = Utils::FileName::fromString(macro.fileName());
     Utils::FileNameList files{sourceFile};
@@ -704,12 +704,13 @@ static void findMacroUses_helper(QFutureInterface<Usage> &future,
     future.setProgressValue(files.size());
 }
 
-void CppFindReferences::findMacroUses(const Macro &macro)
+void CppFindReferences::findMacroUses(const CPlusPlus::Macro &macro)
 {
     findMacroUses(macro, QString(), false);
 }
 
-void CppFindReferences::findMacroUses(const Macro &macro, const QString &replacement, bool replace)
+void CppFindReferences::findMacroUses(const CPlusPlus::Macro &macro, const QString &replacement,
+                                      bool replace)
 {
     SearchResult *search = SearchResultWindow::instance()->startNewSearch(
                 tr("C++ Macro Usages:"),
@@ -753,7 +754,7 @@ void CppFindReferences::findMacroUses(const Macro &macro, const QString &replace
     connect(progress, &FutureProgress::clicked, search, &SearchResult::popup);
 }
 
-void CppFindReferences::renameMacroUses(const Macro &macro, const QString &replacement)
+void CppFindReferences::renameMacroUses(const CPlusPlus::Macro &macro, const QString &replacement)
 {
     const QString textToReplace = replacement.isEmpty() ? macro.nameToQString() : replacement;
     findMacroUses(macro, textToReplace, true);
diff --git a/src/plugins/cpptools/cppmodelmanager.cpp b/src/plugins/cpptools/cppmodelmanager.cpp
index d2493e2e4573fa9f9164871da913c24624791030..08799709b1634b93b8ed7c8b8acdd743ea711826 100644
--- a/src/plugins/cpptools/cppmodelmanager.cpp
+++ b/src/plugins/cpptools/cppmodelmanager.cpp
@@ -47,6 +47,7 @@
 #include <texteditor/textdocument.h>
 #include <projectexplorer/project.h>
 #include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/projectmacro.h>
 #include <projectexplorer/session.h>
 #include <extensionsystem/pluginmanager.h>
 #include <utils/fileutils.h>
@@ -138,7 +139,7 @@ public:
     bool m_dirty;
     QStringList m_projectFiles;
     ProjectPartHeaderPaths m_headerPaths;
-    QByteArray m_definedMacros;
+    ProjectExplorer::Macros m_definedMacros;
 
     // Editor integration
     mutable QMutex m_cppEditorDocumentsMutex;
@@ -446,35 +447,31 @@ ProjectPartHeaderPaths CppModelManager::internalHeaderPaths() const
     return headerPaths;
 }
 
-static void addUnique(const QList<QByteArray> &defs, QByteArray *macros, QSet<QByteArray> *alreadyIn)
+static void addUnique(const ProjectExplorer::Macros &newMacros,
+                      ProjectExplorer::Macros &macros,
+                      QSet<ProjectExplorer::Macro> &alreadyIn)
 {
-    Q_ASSERT(macros);
-    Q_ASSERT(alreadyIn);
-
-    foreach (const QByteArray &def, defs) {
-        if (def.trimmed().isEmpty())
-            continue;
-        if (!alreadyIn->contains(def)) {
-            macros->append(def);
-            macros->append('\n');
-            alreadyIn->insert(def);
+    for (const ProjectExplorer::Macro &macro : newMacros) {
+        if (!alreadyIn.contains(macro)) {
+            macros += macro;
+            alreadyIn.insert(macro);
         }
     }
 }
 
-QByteArray CppModelManager::internalDefinedMacros() const
+ProjectExplorer::Macros CppModelManager::internalDefinedMacros() const
 {
-    QByteArray macros;
-    QSet<QByteArray> alreadyIn;
+    ProjectExplorer::Macros macros;
+    QSet<ProjectExplorer::Macro> alreadyIn;
     QMapIterator<ProjectExplorer::Project *, ProjectInfo> it(d->m_projectToProjectsInfo);
     while (it.hasNext()) {
         it.next();
         const ProjectInfo pinfo = it.value();
-        foreach (const ProjectPart::Ptr &part, pinfo.projectParts()) {
-            addUnique(part->toolchainDefines.split('\n'), &macros, &alreadyIn);
-            addUnique(part->projectDefines.split('\n'), &macros, &alreadyIn);
+        for (const ProjectPart::Ptr &part : pinfo.projectParts()) {
+            addUnique(part->toolChainMacros, macros, alreadyIn);
+            addUnique(part->projectMacros, macros, alreadyIn);
             if (!part->projectConfigFile.isEmpty())
-                macros += ProjectPart::readProjectConfigFile(part);
+                macros += ProjectExplorer::Macro::toMacros(ProjectPart::readProjectConfigFile(part));
         }
     }
     return macros;
@@ -491,7 +488,8 @@ void CppModelManager::dumpModelManagerConfiguration(const QString &logFileId)
     dumper.dumpProjectInfos(projectInfos());
     dumper.dumpSnapshot(globalSnapshot, globalSnapshotTitle, /*isGlobalSnapshot=*/ true);
     dumper.dumpWorkingCopy(workingCopy());
-    dumper.dumpMergedEntities(headerPaths(), definedMacros());
+    dumper.dumpMergedEntities(headerPaths(),
+                              ProjectExplorer:: Macro::toByteArray(definedMacros()));
 }
 
 QSet<AbstractEditorSupport *> CppModelManager::abstractEditorSupports() const
@@ -569,12 +567,12 @@ void CppModelManager::renameUsages(Symbol *symbol,
         d->m_findReferences->renameUsages(symbol, context, replacement);
 }
 
-void CppModelManager::findMacroUsages(const Macro &macro)
+void CppModelManager::findMacroUsages(const CPlusPlus::Macro &macro)
 {
     d->m_findReferences->findMacroUses(macro);
 }
 
-void CppModelManager::renameMacroUsages(const Macro &macro, const QString &replacement)
+void CppModelManager::renameMacroUsages(const CPlusPlus::Macro &macro, const QString &replacement)
 {
     d->m_findReferences->renameMacroUses(macro, replacement);
 }
@@ -603,7 +601,7 @@ WorkingCopy CppModelManager::buildWorkingCopyList()
 
     // Add the project configuration file
     QByteArray conf = codeModelConfiguration();
-    conf += definedMacros();
+    conf += ProjectExplorer::Macro::toByteArray(definedMacros());
     workingCopy.insert(configurationFileName(), conf);
 
     return workingCopy;
@@ -991,7 +989,7 @@ ProjectPart::Ptr CppModelManager::fallbackProjectPart()
 {
     ProjectPart::Ptr part(new ProjectPart);
 
-    part->projectDefines = definedMacros();
+    part->projectMacros = definedMacros();
     part->headerPaths = headerPaths();
 
     // Do not activate ObjectiveCExtensions since this will lead to the
@@ -1270,7 +1268,7 @@ void CppModelManager::setHeaderPaths(const ProjectPartHeaderPaths &headerPaths)
     d->m_headerPaths = headerPaths;
 }
 
-QByteArray CppModelManager::definedMacros()
+ProjectExplorer::Macros CppModelManager::definedMacros()
 {
     QMutexLocker locker(&d->m_projectMutex);
     ensureUpdated();
diff --git a/src/plugins/cpptools/cppmodelmanager.h b/src/plugins/cpptools/cppmodelmanager.h
index 97a93fce60b97b5ee55182f135f08e46453b0953..1cabaf50efb4dcd4ed6925cc7b1908d0e89a9679 100644
--- a/src/plugins/cpptools/cppmodelmanager.h
+++ b/src/plugins/cpptools/cppmodelmanager.h
@@ -163,7 +163,7 @@ public:
     // Use this *only* for auto tests
     void setHeaderPaths(const ProjectPartHeaderPaths &headerPaths);
 
-    QByteArray definedMacros();
+    ProjectExplorer::Macros definedMacros();
 
     void enableGarbageCollector(bool enable);
 
@@ -229,7 +229,7 @@ private:
     void ensureUpdated();
     QStringList internalProjectFiles() const;
     ProjectPartHeaderPaths internalHeaderPaths() const;
-    QByteArray internalDefinedMacros() const;
+    ProjectExplorer::Macros internalDefinedMacros() const;
 
     void dumpModelManagerConfiguration(const QString &logFileId);
 
diff --git a/src/plugins/cpptools/cppmodelmanager_test.cpp b/src/plugins/cpptools/cppmodelmanager_test.cpp
index 35f9931f2190174608722303cecb9c6aa04cfb63..c50c12402cc7859cabf100516414362282ac4c67 100644
--- a/src/plugins/cpptools/cppmodelmanager_test.cpp
+++ b/src/plugins/cpptools/cppmodelmanager_test.cpp
@@ -187,7 +187,7 @@ void CppToolsPlugin::test_modelmanager_paths_are_clean()
 
     ProjectPart::Ptr part(new ProjectPart);
     part->qtVersion = ProjectPart::Qt5;
-    part->projectDefines = QByteArray("#define OH_BEHAVE -1\n");
+    part->projectMacros = {ProjectExplorer::Macro("OH_BEHAVE", "-1")};
     part->headerPaths = {HeaderPath(testDataDir.includeDir(false), HeaderPath::IncludePath),
                          HeaderPath(testDataDir.frameworksDir(false), HeaderPath::FrameworkPath)};
     pi.appendProjectPart(part);
@@ -219,7 +219,7 @@ void CppToolsPlugin::test_modelmanager_framework_headers()
 
     ProjectPart::Ptr part(new ProjectPart);
     part->qtVersion = ProjectPart::Qt5;
-    part->projectDefines = QByteArray("#define OH_BEHAVE -1\n");
+    part->projectMacros = {{"OH_BEHAVE", "-1"}};
     part->headerPaths = {HeaderPath(testDataDir.includeDir(false), HeaderPath::IncludePath),
                          HeaderPath(testDataDir.frameworksDir(false), HeaderPath::FrameworkPath)};
     const QString &source = testDataDir.fileFromSourcesDir(
@@ -268,7 +268,7 @@ void CppToolsPlugin::test_modelmanager_refresh_also_includes_of_project_files()
 
     ProjectPart::Ptr part(new ProjectPart);
     part->qtVersion = ProjectPart::Qt5;
-    part->projectDefines = QByteArray("#define OH_BEHAVE -1\n");
+    part->projectMacros = {{"OH_BEHAVE", "-1"}};
     part->headerPaths = {HeaderPath(testDataDir.includeDir(false), HeaderPath::IncludePath)};
     part->files.append(ProjectFile(testCpp, ProjectFile::CXXSource));
     pi.appendProjectPart(part);
@@ -286,7 +286,7 @@ void CppToolsPlugin::test_modelmanager_refresh_also_includes_of_project_files()
     QVERIFY(macrosInHeaderBefore.first().name() == "test_modelmanager_refresh_h");
 
     // Introduce a define that will enable another define once the document is reparsed.
-    part->projectDefines = QByteArray("#define TEST_DEFINE 1\n");
+    part->projectMacros = {{"TEST_DEFINE", "1"}};
     pi = ProjectInfo(project);
     pi.appendProjectPart(part);
 
@@ -334,13 +334,13 @@ void CppToolsPlugin::test_modelmanager_refresh_several_times()
     QSet<QString> refreshedFiles;
     CPlusPlus::Document::Ptr document;
 
-    QByteArray defines = "#define FIRST_DEFINE";
+    ProjectExplorer::Macros macros = {{"FIRST_DEFINE"}};
     for (int i = 0; i < 2; ++i) {
         pi = ProjectInfo(project);
         ProjectPart::Ptr part(new ProjectPart);
         // Simulate project configuration change by having different defines each time.
-        defines += "\n#define ANOTHER_DEFINE";
-        part->projectDefines = defines;
+        macros += {"ANOTHER_DEFINE"};
+        part->projectMacros = macros;
         part->qtVersion = ProjectPart::Qt5;
         part->files.append(ProjectFile(testHeader1, ProjectFile::CXXHeader));
         part->files.append(ProjectFile(testHeader2, ProjectFile::CXXHeader));
@@ -762,7 +762,7 @@ void CppToolsPlugin::test_modelmanager_defines_per_project()
     part1->files.append(ProjectFile(main1File, ProjectFile::CXXSource));
     part1->files.append(ProjectFile(header, ProjectFile::CXXHeader));
     part1->qtVersion = ProjectPart::NoQt;
-    part1->projectDefines = QByteArray("#define SUB1\n");
+    part1->projectMacros = {{"SUB1"}};
     part1->headerPaths = {HeaderPath(testDataDirectory.includeDir(false), HeaderPath::IncludePath)};
 
     ProjectPart::Ptr part2(new ProjectPart);
@@ -770,7 +770,7 @@ void CppToolsPlugin::test_modelmanager_defines_per_project()
     part2->files.append(ProjectFile(main2File, ProjectFile::CXXSource));
     part2->files.append(ProjectFile(header, ProjectFile::CXXHeader));
     part2->qtVersion = ProjectPart::NoQt;
-    part2->projectDefines = QByteArray("#define SUB2\n");
+    part2->projectMacros = {{"SUB2"}};
     part2->headerPaths = {HeaderPath(testDataDirectory.includeDir(false), HeaderPath::IncludePath)};
 
     ProjectInfo pi = ProjectInfo(project);
diff --git a/src/plugins/cpptools/cppprojectinfogenerator.cpp b/src/plugins/cpptools/cppprojectinfogenerator.cpp
index bff5d36ec48a4a26dc553c2101fdebbcede6a953..3c8ac3765489d63bae13ada7833955207944970e 100644
--- a/src/plugins/cpptools/cppprojectinfogenerator.cpp
+++ b/src/plugins/cpptools/cppprojectinfogenerator.cpp
@@ -143,7 +143,7 @@ private:
         if (!m_tcInfo.predefinedMacrosRunner)
             return; // No compiler set in kit.
 
-        m_projectPart.toolchainDefines = m_tcInfo.predefinedMacrosRunner(m_flags.commandLineFlags);
+        m_projectPart.toolChainMacros = m_tcInfo.predefinedMacrosRunner(m_flags.commandLineFlags);
     }
 
 private:
@@ -187,7 +187,7 @@ static ProjectPart::Ptr projectPartFromRawProjectPart(const RawProjectPart &rawP
     part->callGroupId = rawProjectPart.callGroupId;
     part->buildSystemTarget = rawProjectPart.buildSystemTarget;
     part->qtVersion = rawProjectPart.qtVersion;
-    part->projectDefines = rawProjectPart.projectDefines;
+    part->projectMacros = rawProjectPart.projectMacros;
     part->headerPaths = rawProjectPart.headerPaths;
     part->precompiledHeaders = rawProjectPart.precompiledHeaders;
     part->selectedForBuilding = rawProjectPart.selectedForBuilding;
diff --git a/src/plugins/cpptools/cpprawprojectpart.cpp b/src/plugins/cpptools/cpprawprojectpart.cpp
index 3668f8caec0fe5a97cc14a4b03cbfeb4c4b228de..001bf01af3057c3e5b943ef25e12159d357a8b51 100644
--- a/src/plugins/cpptools/cpprawprojectpart.cpp
+++ b/src/plugins/cpptools/cpprawprojectpart.cpp
@@ -81,9 +81,9 @@ void RawProjectPart::setQtVersion(ProjectPart::QtVersion qtVersion)
     this->qtVersion = qtVersion;
 }
 
-void RawProjectPart::setDefines(const QByteArray &defines)
+void RawProjectPart::setMacros(const ProjectExplorer::Macros &macros)
 {
-    this->projectDefines = defines;
+    this->projectMacros = macros;
 }
 
 void RawProjectPart::setHeaderPaths(const ProjectPartHeaderPaths &headerPaths)
diff --git a/src/plugins/cpptools/cpprawprojectpart.h b/src/plugins/cpptools/cpprawprojectpart.h
index 18f0591ec266d5caf6c0ef6b49d444a860a9fbfc..aafe74281dc4251a588fa799f613fb87f835b1ea 100644
--- a/src/plugins/cpptools/cpprawprojectpart.h
+++ b/src/plugins/cpptools/cpprawprojectpart.h
@@ -67,7 +67,7 @@ public:
 
     void setQtVersion(ProjectPart::QtVersion qtVersion);
 
-    void setDefines(const QByteArray &defines);
+    void setMacros(const ProjectExplorer::Macros &macros);
     void setHeaderPaths(const ProjectPartHeaderPaths &headerPaths);
     void setIncludePaths(const QStringList &includePaths);
 
@@ -88,7 +88,7 @@ public:
     QString buildSystemTarget;
     QStringList precompiledHeaders;
     ProjectPartHeaderPaths headerPaths;
-    QByteArray projectDefines;
+    ProjectExplorer::Macros projectMacros;
     ProjectPart::QtVersion qtVersion = ProjectPart::UnknownQt;
     bool selectedForBuilding = true;
 
diff --git a/src/plugins/cpptools/cppsourceprocessor.cpp b/src/plugins/cpptools/cppsourceprocessor.cpp
index fc28a6135e7258395974c2d1407ab973ed630df9..507e33c90ba1203b1bc4df6abd31d38d3abf14a5 100644
--- a/src/plugins/cpptools/cppsourceprocessor.cpp
+++ b/src/plugins/cpptools/cppsourceprocessor.cpp
@@ -62,11 +62,12 @@ static Q_LOGGING_CATEGORY(log, "qtc.cpptools.sourceprocessor")
 
 namespace {
 
-inline QByteArray generateFingerPrint(const QList<Macro> &definedMacros, const QByteArray &code)
+inline QByteArray generateFingerPrint(const QList<CPlusPlus::Macro> &definedMacros,
+                                      const QByteArray &code)
 {
     QCryptographicHash hash(QCryptographicHash::Sha1);
     hash.addData(code);
-    foreach (const Macro &macro, definedMacros) {
+    foreach (const CPlusPlus::Macro &macro, definedMacros) {
         if (macro.isHidden()) {
             static const QByteArray undef("#undef ");
             hash.addData(undef);
@@ -98,10 +99,10 @@ inline Message messageNoFileContents(Document::Ptr &document, const QString &fil
     return Message(Message::Warning, document->fileName(), line, /*column =*/ 0, text);
 }
 
-inline const Macro revision(const WorkingCopy &workingCopy,
-                            const Macro &macro)
+inline const CPlusPlus::Macro revision(const WorkingCopy &workingCopy,
+                                       const CPlusPlus::Macro &macro)
 {
-    Macro newMacro(macro);
+    CPlusPlus::Macro newMacro(macro);
     newMacro.setFileRevision(workingCopy.get(macro.fileName()).second);
     return newMacro;
 }
@@ -316,7 +317,7 @@ QString CppSourceProcessor::resolveFile_helper(const QString &fileName,
     return QString();
 }
 
-void CppSourceProcessor::macroAdded(const Macro &macro)
+void CppSourceProcessor::macroAdded(const CPlusPlus::Macro &macro)
 {
     if (!m_currentDoc)
         return;
@@ -325,7 +326,7 @@ void CppSourceProcessor::macroAdded(const Macro &macro)
 }
 
 void CppSourceProcessor::passedMacroDefinitionCheck(unsigned bytesOffset, unsigned utf16charsOffset,
-                                                    unsigned line, const Macro &macro)
+                                                    unsigned line, const CPlusPlus::Macro &macro)
 {
     if (!m_currentDoc)
         return;
@@ -347,7 +348,7 @@ void CppSourceProcessor::failedMacroDefinitionCheck(unsigned bytesOffset, unsign
 }
 
 void CppSourceProcessor::notifyMacroReference(unsigned bytesOffset, unsigned utf16charOffset,
-                                              unsigned line, const Macro &macro)
+                                              unsigned line, const CPlusPlus::Macro &macro)
 {
     if (!m_currentDoc)
         return;
@@ -359,7 +360,7 @@ void CppSourceProcessor::notifyMacroReference(unsigned bytesOffset, unsigned utf
 }
 
 void CppSourceProcessor::startExpandingMacro(unsigned bytesOffset, unsigned utf16charOffset,
-                                             unsigned line, const Macro &macro,
+                                             unsigned line, const CPlusPlus::Macro &macro,
                                              const QVector<MacroArgumentReference> &actuals)
 {
     if (!m_currentDoc)
@@ -371,7 +372,7 @@ void CppSourceProcessor::startExpandingMacro(unsigned bytesOffset, unsigned utf1
                               line, actuals);
 }
 
-void CppSourceProcessor::stopExpandingMacro(unsigned, const Macro &)
+void CppSourceProcessor::stopExpandingMacro(unsigned, const CPlusPlus::Macro &)
 {
     if (!m_currentDoc)
         return;
diff --git a/src/plugins/cpptools/cpptoolsunittestfiles.pri b/src/plugins/cpptools/cpptoolsunittestfiles.pri
index 6960ccbde943ba011c8d6156c7ab856ffd673cd6..48c70fce6e2ffb6a2304fad444a91fb799dcb453 100644
--- a/src/plugins/cpptools/cpptoolsunittestfiles.pri
+++ b/src/plugins/cpptools/cpptoolsunittestfiles.pri
@@ -1,11 +1,7 @@
-# Currently there are no tests for the project explorer plugin, but we include
-# headers from it that needs to have the export/import adapted for Windows.
 shared {
     DEFINES += CPPTOOLS_LIBRARY
-    DEFINES += PROJECTEXPLORER_LIBRARY
 } else {
     DEFINES += CPPTOOLS_STATIC_LIBRARY
-    DEFINES += PROJECTEXPLORER_STATIC_LIBRARY
 }
 
 HEADERS += \
diff --git a/src/plugins/cpptools/projectinfo.cpp b/src/plugins/cpptools/projectinfo.cpp
index 1ca95e3e9c0783f937c4d5bcd12c97dc4fc7a7cf..e0dbee363fcda25b98c87aa2c23442709194c29a 100644
--- a/src/plugins/cpptools/projectinfo.cpp
+++ b/src/plugins/cpptools/projectinfo.cpp
@@ -160,13 +160,10 @@ void ProjectInfo::finish()
             m_sourceFiles.insert(file.path);
 
         // Update defines
-        m_defines.append(part->toolchainDefines);
-        m_defines.append(part->projectDefines);
-        if (!part->projectConfigFile.isEmpty()) {
-            m_defines.append('\n');
-            m_defines += ProjectPart::readProjectConfigFile(part);
-            m_defines.append('\n');
-        }
+        m_defines.append(part->toolChainMacros);
+        m_defines.append(part->projectMacros);
+        if (!part->projectConfigFile.isEmpty())
+            m_defines += ProjectExplorer::Macro::toMacros(ProjectPart::readProjectConfigFile(part));
     }
 }
 
diff --git a/src/plugins/cpptools/projectinfo.h b/src/plugins/cpptools/projectinfo.h
index 9096322989de3b4decd269c9f184dc3d2fe7987e..ee016f5b3ea825f0842bace18f51f79a220a8398 100644
--- a/src/plugins/cpptools/projectinfo.h
+++ b/src/plugins/cpptools/projectinfo.h
@@ -123,7 +123,7 @@ private:
     // The members below are (re)calculated from the project parts with finish()
     ProjectPartHeaderPaths m_headerPaths;
     QSet<QString> m_sourceFiles;
-    QByteArray m_defines;
+    ProjectExplorer::Macros m_defines;
 };
 
 } // namespace CppTools
diff --git a/src/plugins/cpptools/projectpart.cpp b/src/plugins/cpptools/projectpart.cpp
index 6a87adf93f74f816c9bdb041bb2adb02293bd4d3..e77e079c808f7f1dd1bd612abff6f3582058a8fb 100644
--- a/src/plugins/cpptools/projectpart.cpp
+++ b/src/plugins/cpptools/projectpart.cpp
@@ -25,6 +25,8 @@
 
 #include "projectpart.h"
 
+#include <utils/algorithm.h>
+
 #include <QFile>
 #include <QDir>
 #include <QTextStream>
@@ -43,16 +45,9 @@ void ProjectPart::updateLanguageFeatures()
     if (!hasQt) {
         languageFeatures.qtKeywordsEnabled = false;
     } else {
-        const QByteArray noKeywordsMacro = "#define QT_NO_KEYWORDS";
-        const int noKeywordsIndex = projectDefines.indexOf(noKeywordsMacro);
-        if (noKeywordsIndex == -1) {
-            languageFeatures.qtKeywordsEnabled = true;
-        } else {
-            const char nextChar = projectDefines.at(noKeywordsIndex + noKeywordsMacro.length());
-            // Detect "#define QT_NO_KEYWORDS" and "#define QT_NO_KEYWORDS 1", but exclude
-            // "#define QT_NO_KEYWORDS_FOO"
-            languageFeatures.qtKeywordsEnabled = nextChar != '\n' && nextChar != ' ';
-        }
+        languageFeatures.qtKeywordsEnabled = !Utils::contains(
+                    projectMacros,
+                    [] (const ProjectExplorer::Macro &macro) { return macro.key == "QT_NO_KEYWORDS"; });
     }
 }
 
diff --git a/src/plugins/cpptools/projectpart.h b/src/plugins/cpptools/projectpart.h
index c04a18c61f52fd23ba91e31bd6bf01c9990c6edd..a6851e001dd4eccebb4f6b10feb8a920ec48502b 100644
--- a/src/plugins/cpptools/projectpart.h
+++ b/src/plugins/cpptools/projectpart.h
@@ -31,6 +31,7 @@
 #include "projectpartheaderpath.h"
 
 #include <projectexplorer/projectexplorer_global.h>
+#include <projectexplorer/projectmacro.h>
 
 #include <coreplugin/id.h>
 
@@ -118,7 +119,7 @@ public:
     QStringList precompiledHeaders;
     ProjectPartHeaderPaths headerPaths;
 
-    QByteArray projectDefines;
+    ProjectExplorer::Macros projectMacros;
 
     LanguageVersion languageVersion = LatestCxxVersion;
     LanguageExtensions languageExtensions = NoExtensions;
@@ -130,7 +131,7 @@ public:
 
     Core::Id toolchainType;
     bool isMsvc2015Toolchain = false;
-    QByteArray toolchainDefines;
+    ProjectExplorer::Macros toolChainMacros;
     ToolChainWordWidth toolChainWordWidth = WordWidth32Bit;
     QString toolChainTargetTriple;
 };
diff --git a/src/plugins/nim/project/nimtoolchain.cpp b/src/plugins/nim/project/nimtoolchain.cpp
index 6c8fd0f32b8be0227e5832a5b65b9f62b3f7b969..3605b5bcb6b65b23e6c913dd13d0425ecf63c74b 100644
--- a/src/plugins/nim/project/nimtoolchain.cpp
+++ b/src/plugins/nim/project/nimtoolchain.cpp
@@ -82,9 +82,9 @@ ToolChain::PredefinedMacrosRunner NimToolChain::createPredefinedMacrosRunner() c
     return ToolChain::PredefinedMacrosRunner();
 }
 
-QByteArray NimToolChain::predefinedMacros(const QStringList &) const
+Macros NimToolChain::predefinedMacros(const QStringList &) const
 {
-    return QByteArray();
+    return Macros();
 }
 
 ToolChain::CompilerFlags NimToolChain::compilerFlags(const QStringList &) const
diff --git a/src/plugins/nim/project/nimtoolchain.h b/src/plugins/nim/project/nimtoolchain.h
index 7061a9751bcc2892edea6170a516d5dcaab20aa1..2da07dd2e2346dcab958e86bec516410cac9972f 100644
--- a/src/plugins/nim/project/nimtoolchain.h
+++ b/src/plugins/nim/project/nimtoolchain.h
@@ -41,7 +41,7 @@ public:
     bool isValid() const override;
 
     PredefinedMacrosRunner createPredefinedMacrosRunner() const override;
-    QByteArray predefinedMacros(const QStringList &flags) const final;
+    ProjectExplorer::Macros predefinedMacros(const QStringList &flags) const final;
     CompilerFlags compilerFlags(const QStringList &flags) const final;
     ProjectExplorer::WarningFlags warningFlags(const QStringList &flags) const final;
 
diff --git a/src/plugins/projectexplorer/abstractmsvctoolchain.cpp b/src/plugins/projectexplorer/abstractmsvctoolchain.cpp
index 121e2b43b4f683d9cee5937bc2e791d7220bd2a5..44d961ba08736ec225880d6084f17758bf2e146d 100644
--- a/src/plugins/projectexplorer/abstractmsvctoolchain.cpp
+++ b/src/plugins/projectexplorer/abstractmsvctoolchain.cpp
@@ -118,7 +118,7 @@ ToolChain::PredefinedMacrosRunner AbstractMsvcToolChain::createPredefinedMacrosR
     };
 }
 
-QByteArray AbstractMsvcToolChain::predefinedMacros(const QStringList &cxxflags) const
+ProjectExplorer::Macros AbstractMsvcToolChain::predefinedMacros(const QStringList &cxxflags) const
 {
     return createPredefinedMacrosRunner()(cxxflags);
 }
@@ -277,13 +277,6 @@ bool AbstractMsvcToolChain::canClone() const
     return true;
 }
 
-// Function must be thread-safe!
-QByteArray AbstractMsvcToolChain::msvcPredefinedMacros(const QStringList,
-                                                       const Utils::Environment&) const
-{
-    return QByteArray();
-}
-
 bool AbstractMsvcToolChain::generateEnvironmentSettings(const Utils::Environment &env,
                                                         const QString &batchFile,
                                                         const QString &batchArgs,
diff --git a/src/plugins/projectexplorer/abstractmsvctoolchain.h b/src/plugins/projectexplorer/abstractmsvctoolchain.h
index 9a96faea0ebbf4a3575e6c58b7be131bae296230..0851e09cc0904116e3c7d0e113b17342222ecddc 100644
--- a/src/plugins/projectexplorer/abstractmsvctoolchain.h
+++ b/src/plugins/projectexplorer/abstractmsvctoolchain.h
@@ -53,7 +53,7 @@ public:
     QString originalTargetTriple() const override;
 
     PredefinedMacrosRunner createPredefinedMacrosRunner() const override;
-    QByteArray predefinedMacros(const QStringList &cxxflags) const override;
+    Macros predefinedMacros(const QStringList &cxxflags) const override;
     CompilerFlags compilerFlags(const QStringList &cxxflags) const override;
     WarningFlags warningFlags(const QStringList &cflags) const override;
     SystemHeaderPathsRunner createSystemHeaderPathsRunner() const override;
@@ -92,13 +92,14 @@ protected:
 
     static void inferWarningsForLevel(int warningLevel, WarningFlags &flags);
     virtual Utils::Environment readEnvironmentSetting(const Utils::Environment& env) const = 0;
-    virtual QByteArray msvcPredefinedMacros(const QStringList cxxflags,
-                                            const Utils::Environment& env) const;
+    // Function must be thread-safe!
+    virtual Macros msvcPredefinedMacros(const QStringList cxxflags,
+                                        const Utils::Environment& env) const = 0;
 
 
     Utils::FileName m_debuggerCommand;
     mutable QMutex *m_predefinedMacrosMutex = nullptr;
-    mutable QByteArray m_predefinedMacros;
+    mutable Macros m_predefinedMacros;
     mutable Utils::Environment m_lastEnvironment;   // Last checked 'incoming' environment.
     mutable Utils::Environment m_resultEnvironment; // Resulting environment for VC
     mutable QMutex *m_headerPathsMutex = nullptr;
diff --git a/src/plugins/projectexplorer/customtoolchain.cpp b/src/plugins/projectexplorer/customtoolchain.cpp
index 06a3b041e89ef7167899e8eb81ac7d6d630c6877..dc0989eed1942ce56ec9d725e54ca0fafd3a13e3 100644
--- a/src/plugins/projectexplorer/customtoolchain.cpp
+++ b/src/plugins/projectexplorer/customtoolchain.cpp
@@ -32,6 +32,7 @@
 #include "customparser.h"
 #include "customparserconfigdialog.h"
 #include "projectexplorerconstants.h"
+#include "projectmacro.h"
 #include "toolchainmanager.h"
 
 #include <utils/algorithm.h>
@@ -118,39 +119,24 @@ bool CustomToolChain::isValid() const
 
 ToolChain::PredefinedMacrosRunner CustomToolChain::createPredefinedMacrosRunner() const
 {
-    const QStringList theMacros = m_predefinedMacros;
+    const Macros theMacros = m_predefinedMacros;
 
     // This runner must be thread-safe!
     return [theMacros](const QStringList &cxxflags){
         QByteArray result;
-        QStringList macros = theMacros;
+        Macros macros = theMacros;
         for (const QString &cxxFlag : cxxflags) {
-            if (cxxFlag.startsWith(QLatin1String("-D"))) {
-                macros << cxxFlag.mid(2).trimmed();
-            } else if (cxxFlag.startsWith(QLatin1String("-U"))) {
-                const QString &removedName = cxxFlag.mid(2).trimmed();
-                for (int i = macros.size() - 1; i >= 0; --i) {
-                    const QString &m = macros.at(i);
-                    if (m.left(m.indexOf(QLatin1Char('='))) == removedName)
-                        macros.removeAt(i);
-                }
-            }
-        }
-        for (const QString &str : Utils::asConst(macros)) {
-            QByteArray ba = str.toUtf8();
-            int equals = ba.indexOf('=');
-            if (equals == -1) {
-                result += "#define " + ba.trimmed() + '\n';
-            } else {
-                result += "#define " + ba.left(equals).trimmed() + ' '
-                        + ba.mid(equals + 1).trimmed() + '\n';
-            }
+            if (cxxFlag.startsWith(QLatin1String("-D")))
+                macros.append(Macro::fromKeyValue(cxxFlag.mid(2).trimmed()));
+             else if (cxxFlag.startsWith(QLatin1String("-U")) && !cxxFlag.contains('='))
+                macros.append({cxxFlag.mid(2).trimmed().toUtf8(), MacroType::Undefine});
+
         }
-        return result;
+        return macros;
     };
 }
 
-QByteArray CustomToolChain::predefinedMacros(const QStringList &cxxflags) const
+Macros CustomToolChain::predefinedMacros(const QStringList &cxxflags) const
 {
     return createPredefinedMacrosRunner()(cxxflags);
 }
@@ -169,16 +155,16 @@ WarningFlags CustomToolChain::warningFlags(const QStringList &cxxflags) const
     return WarningFlags::Default;
 }
 
-const QStringList &CustomToolChain::rawPredefinedMacros() const
+const Macros &CustomToolChain::rawPredefinedMacros() const
 {
     return m_predefinedMacros;
 }
 
-void CustomToolChain::setPredefinedMacros(const QStringList &list)
+void CustomToolChain::setPredefinedMacros(const Macros &macros)
 {
-    if (m_predefinedMacros == list)
+    if (m_predefinedMacros == macros)
         return;
-    m_predefinedMacros = list;
+    m_predefinedMacros = macros;
     toolChainUpdated();
 }
 
@@ -323,7 +309,8 @@ QVariantMap CustomToolChain::toMap() const
     data.insert(QLatin1String(compilerCommandKeyC), m_compilerCommand.toString());
     data.insert(QLatin1String(makeCommandKeyC), m_makeCommand.toString());
     data.insert(QLatin1String(targetAbiKeyC), m_targetAbi.toString());
-    data.insert(QLatin1String(predefinedMacrosKeyC), m_predefinedMacros);
+    QStringList macros = Utils::transform<QList>(m_predefinedMacros, [](const Macro &m) { return QString::fromUtf8(m.toByteArray()); });
+    data.insert(QLatin1String(predefinedMacrosKeyC), macros);
     data.insert(QLatin1String(headerPathsKeyC), headerPathsList());
     data.insert(QLatin1String(cxx11FlagsKeyC), m_cxx11Flags);
     data.insert(QLatin1String(mkspecsKeyC), mkspecs());
@@ -352,7 +339,8 @@ bool CustomToolChain::fromMap(const QVariantMap &data)
     m_compilerCommand = FileName::fromString(data.value(QLatin1String(compilerCommandKeyC)).toString());
     m_makeCommand = FileName::fromString(data.value(QLatin1String(makeCommandKeyC)).toString());
     m_targetAbi = Abi(data.value(QLatin1String(targetAbiKeyC)).toString());
-    m_predefinedMacros = data.value(QLatin1String(predefinedMacrosKeyC)).toStringList();
+    const QStringList macros = data.value(QLatin1String(predefinedMacrosKeyC)).toStringList();
+    m_predefinedMacros = Macro::toMacros(macros.join('\n').toUtf8());
     setHeaderPaths(data.value(QLatin1String(headerPathsKeyC)).toStringList());
     m_cxx11Flags = data.value(QLatin1String(cxx11FlagsKeyC)).toStringList();
     setMkspecs(data.value(QLatin1String(mkspecsKeyC)).toString());
@@ -526,11 +514,16 @@ public:
         return static_cast<QPlainTextEdit *>(widget());
     }
 
-    inline QStringList entries() const
+    QStringList entries() const
     {
         return textEditWidget()->toPlainText().split(QLatin1Char('\n'), QString::SkipEmptyParts);
     }
 
+    QString text() const
+    {
+        return textEditWidget()->toPlainText();
+    }
+
     // not accurate, counts empty lines (except last)
     int entryCount() const
     {
@@ -656,7 +649,7 @@ void CustomToolChainConfigWidget::applyImpl()
     tc->setCompilerCommand(m_compilerCommand->fileName());
     tc->setMakeCommand(m_makeCommand->fileName());
     tc->setTargetAbi(m_abiWidget->currentAbi());
-    tc->setPredefinedMacros(m_predefinedDetails->entries());
+    tc->setPredefinedMacros(Macro::toMacros(m_predefinedDetails->text().toUtf8()));
     tc->setHeaderPaths(m_headerDetails->entries());
     tc->setCxx11Flags(m_cxx11Flags->text().split(QLatin1Char(',')));
     tc->setMkspecs(m_mkspecs->text());
@@ -673,8 +666,8 @@ void CustomToolChainConfigWidget::setFromToolchain()
     m_compilerCommand->setFileName(tc->compilerCommand());
     m_makeCommand->setFileName(FileName::fromString(tc->makeCommand(Environment())));
     m_abiWidget->setAbis(QList<Abi>(), tc->targetAbi());
-    m_predefinedMacros->setPlainText(tc->rawPredefinedMacros().join(QLatin1Char('\n')));
-    m_headerPaths->setPlainText(tc->headerPathsList().join(QLatin1Char('\n')));
+    m_predefinedMacros->setPlainText(QString::fromUtf8(Macro::toByteArray(tc->rawPredefinedMacros())));
+    m_headerPaths->setPlainText(tc->headerPathsList().join('\n'));
     m_cxx11Flags->setText(tc->cxx11Flags().join(QLatin1Char(',')));
     m_mkspecs->setText(tc->mkspecs());
     int index = m_errorParserComboBox->findData(tc->outputParserId().toSetting());
@@ -690,7 +683,7 @@ bool CustomToolChainConfigWidget::isDirtyImpl() const
     return m_compilerCommand->fileName() != tc->compilerCommand()
             || m_makeCommand->path() != tc->makeCommand(Environment())
             || m_abiWidget->currentAbi() != tc->targetAbi()
-            || m_predefinedDetails->entries() != tc->rawPredefinedMacros()
+            || Macro::toMacros(m_predefinedDetails->text().toUtf8()) != tc->rawPredefinedMacros()
             || m_headerDetails->entries() != tc->headerPathsList()
             || m_cxx11Flags->text().split(QLatin1Char(',')) != tc->cxx11Flags()
             || m_mkspecs->text() != tc->mkspecs()
diff --git a/src/plugins/projectexplorer/customtoolchain.h b/src/plugins/projectexplorer/customtoolchain.h
index fa5ec68ed01a27f86e69ed45fac0057fbf9d5cd1..75ba190c66f6aa5315895d0aa92faa9f5f01193f 100644
--- a/src/plugins/projectexplorer/customtoolchain.h
+++ b/src/plugins/projectexplorer/customtoolchain.h
@@ -72,11 +72,11 @@ public:
     bool isValid() const override;
 
     PredefinedMacrosRunner createPredefinedMacrosRunner() const override;
-    QByteArray predefinedMacros(const QStringList &cxxflags) const override;
+    Macros predefinedMacros(const QStringList &cxxflags) const override;
     CompilerFlags compilerFlags(const QStringList &cxxflags) const override;
     WarningFlags warningFlags(const QStringList &cxxflags) const override;
-    const QStringList &rawPredefinedMacros() const;
-    void setPredefinedMacros(const QStringList &list);
+    const Macros &rawPredefinedMacros() const;
+    void setPredefinedMacros(const Macros &macros);
 
     SystemHeaderPathsRunner createSystemHeaderPathsRunner() const override;
     QList<HeaderPath> systemHeaderPaths(const QStringList &cxxFlags,
@@ -124,7 +124,7 @@ private:
     Utils::FileName m_makeCommand;
 
     Abi m_targetAbi;
-    QStringList m_predefinedMacros;
+    Macros m_predefinedMacros;
     QList<HeaderPath> m_systemHeaderPaths;
     QStringList m_cxx11Flags;
     Utils::FileNameList m_mkspecs;
diff --git a/src/plugins/projectexplorer/gcctoolchain.cpp b/src/plugins/projectexplorer/gcctoolchain.cpp
index 102441bdf1a74138cd62b6d2cb76b6156172cc83..9800908a53dfb6123f79e6ab994695d12b4f0778 100644
--- a/src/plugins/projectexplorer/gcctoolchain.cpp
+++ b/src/plugins/projectexplorer/gcctoolchain.cpp
@@ -28,6 +28,7 @@
 #include "gcctoolchainfactories.h"
 #include "gccparser.h"
 #include "linuxiccparser.h"
+#include "projectmacro.h"
 #include "projectexplorerconstants.h"
 #include "toolchainmanager.h"
 
@@ -48,6 +49,7 @@
 #include <QLineEdit>
 #include <QRegularExpression>
 
+#include <algorithm>
 #include <memory>
 
 using namespace Utils;
@@ -119,6 +121,11 @@ HeaderPathsCache::Cache HeaderPathsCache::cache() const
     return m_cache;
 }
 
+MacroCache::MacroCache() : m_mutex(QMutex::Recursive)
+{
+    m_cache.reserve(CACHE_SIZE + 1);
+}
+
 MacroCache::MacroCache(const MacroCache &other)
     : MacroCache()
 {
@@ -126,40 +133,21 @@ MacroCache::MacroCache(const MacroCache &other)
     m_cache = other.cache();
 }
 
-void MacroCache::insert(const QStringList &compilerCommand, const QByteArray &macros)
+void MacroCache::insert(const QStringList &compilerCommand, const Macros &macros)
 {
-    if (macros.isNull())
+    QMutexLocker locker(&m_mutex);
+    if (macros.isEmpty() || unlockedCheck(compilerCommand).isEmpty())
         return;
 
-    CacheItem runResults;
-    QByteArray data = macros;
-    runResults.first = compilerCommand;
-    if (macros.isNull())
-        data = QByteArray("");
-    runResults.second = data;
-
-    QMutexLocker locker(&m_mutex);
-    if (check(compilerCommand).isNull()) {
-        m_cache.push_back(runResults);
-        if (m_cache.size() > CACHE_SIZE)
-            m_cache.pop_front();
-    }
+    m_cache.push_back(qMakePair(compilerCommand, macros));
+    if (m_cache.size() > CACHE_SIZE)
+        m_cache.pop_front();
 }
 
-QByteArray MacroCache::check(const QStringList &compilerCommand) const
+Macros MacroCache::check(const QStringList &compilerCommand) const
 {
     QMutexLocker locker(&m_mutex);
-    for (Cache::iterator it = m_cache.begin(); it != m_cache.end(); ++it) {
-        if (it->first == compilerCommand) {
-            // Increase cached item priority
-            CacheItem pair = *it;
-            m_cache.erase(it);
-            m_cache.push_back(pair);
-
-            return pair.second;
-        }
-    }
-    return QByteArray();
+    return unlockedCheck(compilerCommand);
 }
 
 MacroCache::Cache MacroCache::cache() const
@@ -168,6 +156,16 @@ MacroCache::Cache MacroCache::cache() const
     return m_cache;
 }
 
+Macros MacroCache::unlockedCheck(const QStringList &compilerCommand) const
+{
+    auto it = std::stable_partition(m_cache.begin(), m_cache.end(), [&](const CacheItem &ci) {
+        return ci.first == compilerCommand;
+    });
+    if (it != m_cache.end())
+        return it->second;
+    return {};
+}
+
 static QByteArray runGcc(const FileName &gcc, const QStringList &arguments, const QStringList &env)
 {
     if (gcc.isEmpty() || !gcc.toFileInfo().isExecutable())
@@ -196,25 +194,28 @@ static const QStringList gccPredefinedMacrosOptions(Core::Id languageId)
     return QStringList({langOption, "-E", "-dM"});
 }
 
-static QByteArray gccPredefinedMacros(const FileName &gcc, const QStringList &args, const QStringList &env)
+static ProjectExplorer::Macros gccPredefinedMacros(const FileName &gcc,
+                                                   const QStringList &args,
+                                                   const QStringList &env)
 {
     QStringList arguments = args;
     arguments << "-";
 
-    QByteArray predefinedMacros = runGcc(gcc, arguments, env);
+    ProjectExplorer::Macros  predefinedMacros = Macro::toMacros(runGcc(gcc, arguments, env));
     // Sanity check in case we get an error message instead of real output:
-    QTC_CHECK(predefinedMacros.isNull() || predefinedMacros.startsWith("#define "));
+    QTC_CHECK(predefinedMacros.isEmpty()
+              || predefinedMacros.front().type == ProjectExplorer::MacroType::Define);
     if (HostOsInfo::isMacHost()) {
         // Turn off flag indicating Apple's blocks support
-        const QByteArray blocksDefine("#define __BLOCKS__ 1");
-        const QByteArray blocksUndefine("#undef __BLOCKS__");
+        const ProjectExplorer::Macro blocksDefine("__BLOCKS__", "1");
+        const ProjectExplorer::Macro blocksUndefine("__BLOCKS__", ProjectExplorer::MacroType::Undefine);
         const int idx = predefinedMacros.indexOf(blocksDefine);
         if (idx != -1)
-            predefinedMacros.replace(idx, blocksDefine.length(), blocksUndefine);
+            predefinedMacros[idx] = blocksUndefine;
 
         // Define __strong and __weak (used for Apple's GC extension of C) to be empty
-        predefinedMacros.append("#define __strong\n");
-        predefinedMacros.append("#define __weak\n");
+        predefinedMacros.append({"__strong"});
+        predefinedMacros.append({"__weak"});
     }
     return predefinedMacros;
 }
@@ -261,7 +262,7 @@ QList<HeaderPath> GccToolChain::gccHeaderPaths(const FileName &gcc, const QStrin
     return systemHeaderPaths;
 }
 
-static QList<Abi> guessGccAbi(const QString &m, const QByteArray &macros)
+static QList<Abi> guessGccAbi(const QString &m, const ProjectExplorer::Macros &macros)
 {
     QList<Abi> abiList;
 
@@ -274,19 +275,13 @@ static QList<Abi> guessGccAbi(const QString &m, const QByteArray &macros)
     Abi::OSFlavor flavor = guessed.osFlavor();
     Abi::BinaryFormat format = guessed.binaryFormat();
     int width = guessed.wordWidth();
-    const QByteArray mscVer = "#define _MSC_VER ";
-
-    if (macros.contains("#define __SIZEOF_SIZE_T__ 8"))
-        width = 64;
-    else if (macros.contains("#define __SIZEOF_SIZE_T__ 4"))
-        width = 32;
-    else if (macros.contains("#define __SIZEOF_SIZE_T__ 2"))
-        width = 16;
-    int mscVerIndex = macros.indexOf(mscVer);
-    if (mscVerIndex != -1) {
-        mscVerIndex += mscVer.length();
-        const int eol = macros.indexOf('\n', mscVerIndex);
-        const int msvcVersion = macros.mid(mscVerIndex, eol - mscVerIndex).toInt();
+
+    const Macro sizeOfMacro = Utils::findOrDefault(macros, [](const Macro &m) { return m.key == "__SIZEOF_SIZE_T__"; });
+    if (sizeOfMacro.isValid() && sizeOfMacro.type == MacroType::Define)
+        width = sizeOfMacro.value.toInt() * 8;
+    const Macro &mscVerMacro = Utils::findOrDefault(macros, [](const Macro &m) { return m.key == "_MSC_VER"; });
+    if (mscVerMacro.type == MacroType::Define) {
+        const int msvcVersion = mscVerMacro.value.toInt();
         flavor = Abi::flavorForMsvcVersion(msvcVersion);
     }
 
@@ -305,7 +300,7 @@ static QList<Abi> guessGccAbi(const QString &m, const QByteArray &macros)
 
 
 static GccToolChain::DetectedAbisResult guessGccAbi(const FileName &path, const QStringList &env,
-                                                   const QByteArray &macros,
+                                                   const ProjectExplorer::Macros &macros,
                                                    const QStringList &extraArgs = QStringList())
 {
     if (path.isEmpty())
@@ -495,8 +490,8 @@ ToolChain::PredefinedMacrosRunner GccToolChain::createPredefinedMacrosRunner() c
         }
 
         arguments = reinterpretOptions(arguments);
-        QByteArray macros = macroCache->check(arguments);
-        if (!macros.isNull())
+        Macros macros = macroCache->check(arguments);
+        if (!macros.isEmpty())
             return macros;
 
         macros = gccPredefinedMacros(findLocalCompiler(compilerCommand, env),
@@ -517,7 +512,7 @@ ToolChain::PredefinedMacrosRunner GccToolChain::createPredefinedMacrosRunner() c
  * adds _OPENMP macro, for full list of macro search by word "when" on this page:
  * http://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
  */
-QByteArray GccToolChain::predefinedMacros(const QStringList &cxxflags) const
+ProjectExplorer::Macros GccToolChain::predefinedMacros(const QStringList &cxxflags) const
 {
     return createPredefinedMacrosRunner()(cxxflags);
 }
@@ -701,6 +696,8 @@ void GccToolChain::addCommandPathToEnvironment(const FileName &command, Environm
         env.prependOrSetPath(command.parentDir().toString());
 }
 
+GccToolChain::GccToolChain(const GccToolChain &) = default;
+
 void GccToolChain::addToEnvironment(Environment &env) const
 {
     addCommandPathToEnvironment(m_compilerCommand, env);
@@ -898,7 +895,7 @@ GccToolChain::DetectedAbisResult GccToolChain::detectSupportedAbis() const
 {
     Environment env = Environment::systemEnvironment();
     addToEnvironment(env);
-    QByteArray macros = predefinedMacros(QStringList());
+    ProjectExplorer::Macros macros = predefinedMacros(QStringList());
     return guessGccAbi(findLocalCompiler(m_compilerCommand, env),
                        env.toStringList(),
                        macros,
@@ -1060,7 +1057,7 @@ QList<ToolChain *> GccToolChainFactory::autoDetectToolChain(const FileName &comp
     Environment systemEnvironment = Environment::systemEnvironment();
     GccToolChain::addCommandPathToEnvironment(compilerPath, systemEnvironment);
     const FileName localCompilerPath = findLocalCompiler(compilerPath, systemEnvironment);
-    QByteArray macros
+    Macros macros
             = gccPredefinedMacros(localCompilerPath, gccPredefinedMacrosOptions(language),
                                   systemEnvironment.toStringList());
     const GccToolChain::DetectedAbisResult detectedAbis = guessGccAbi(localCompilerPath,
@@ -1765,7 +1762,7 @@ void ProjectExplorerPlugin::testGccAbiGuessing()
     QFETCH(QByteArray, macros);
     QFETCH(QStringList, abiList);
 
-    QList<Abi> al = guessGccAbi(input, macros);
+    QList<Abi> al = guessGccAbi(input, ProjectExplorer::Macro::toMacros(macros));
     QCOMPARE(al.count(), abiList.count());
     for (int i = 0; i < al.count(); ++i)
         QCOMPARE(al.at(i).toString(), abiList.at(i));
diff --git a/src/plugins/projectexplorer/gcctoolchain.h b/src/plugins/projectexplorer/gcctoolchain.h
index 0399b1ec145c1e8101b2e42e9d35224f00bcb338..a3c4b4eacae94cde905dda7a8cea670541aaa060 100644
--- a/src/plugins/projectexplorer/gcctoolchain.h
+++ b/src/plugins/projectexplorer/gcctoolchain.h
@@ -51,7 +51,7 @@ class LinuxIccToolChainFactory;
 // GccToolChain
 // --------------------------------------------------------------------------
 
-class PROJECTEXPLORER_EXPORT HeaderPathsCache
+class HeaderPathsCache
 {
 public:
     HeaderPathsCache() : m_mutex(QMutex::Recursive) {}
@@ -69,20 +69,22 @@ private:
     mutable Cache m_cache;
 };
 
-class PROJECTEXPLORER_EXPORT MacroCache
+class MacroCache
 {
 public:
-    MacroCache() : m_mutex(QMutex::Recursive) {}
+    MacroCache();
     MacroCache(const MacroCache &other);
-    void insert(const QStringList &compilerCommand, const QByteArray &macros);
-    QByteArray check(const QStringList &compilerCommand) const;
+    void insert(const QStringList &compilerCommand, const Macros &macros);
+    Macros check(const QStringList &compilerCommand) const;
 
 protected:
-    using CacheItem = QPair<QStringList, QByteArray>;
-    using Cache = QList<CacheItem>;
+    using CacheItem = QPair<QStringList, Macros>;
+    using Cache = QVector<CacheItem>;
     Cache cache() const;
 
 private:
+    // Does not lock!
+    Macros unlockedCheck(const QStringList &compilerCommand) const;
     mutable QMutex m_mutex;
     mutable Cache m_cache;
 };
@@ -104,7 +106,7 @@ public:
     WarningFlags warningFlags(const QStringList &cflags) const override;
 
     PredefinedMacrosRunner createPredefinedMacrosRunner() const override;
-    QByteArray predefinedMacros(const QStringList &cxxflags) const override;
+    Macros predefinedMacros(const QStringList &cxxflags) const override;
 
     SystemHeaderPathsRunner createSystemHeaderPathsRunner() const override;
     QList<HeaderPath> systemHeaderPaths(const QStringList &cxxflags,
@@ -147,14 +149,16 @@ public:
     };
 
 protected:
-    GccToolChain(const GccToolChain &) = default;
+    using CacheItem = QPair<QStringList, Macros>;
+    using GccCache = QVector<CacheItem>;
+
+    GccToolChain(const GccToolChain &);
 
     void setCompilerCommand(const Utils::FileName &path);
     void setSupportedAbis(const QList<Abi> &m_abis);
     void setOriginalTargetTriple(const QString &targetTriple);
-
-    void setMacroCache(const QStringList &allCxxflags, const QByteArray &macros) const;
-    QByteArray macroCache(const QStringList &allCxxflags) const;
+    void setMacroCache(const QStringList &allCxxflags, const Macros &macroCache) const;
+    Macros macroCache(const QStringList &allCxxflags) const;
 
     virtual QString defaultDisplayName() const;
     virtual CompilerFlags defaultCompilerFlags() const;
diff --git a/src/plugins/projectexplorer/gcctoolchainfactories.h b/src/plugins/projectexplorer/gcctoolchainfactories.h
index 4417fc712fd7733a506f58622fb01754331f61bb..e4c0a583e9e876841e26880be9ce5c23b46cbd09 100644
--- a/src/plugins/projectexplorer/gcctoolchainfactories.h
+++ b/src/plugins/projectexplorer/gcctoolchainfactories.h
@@ -99,7 +99,7 @@ private:
     AbiWidget *m_abiWidget;
 
     bool m_isReadOnly = false;
-    QByteArray m_macros;
+    ProjectExplorer::Macros m_macros;
 };
 
 // --------------------------------------------------------------------------
diff --git a/src/plugins/projectexplorer/msvctoolchain.cpp b/src/plugins/projectexplorer/msvctoolchain.cpp
index 2562b7fcc4b8ba3b0484a282be9296421e08c260..2ff34dbcb879a4376ee1360349f3fd9a314ce678 100644
--- a/src/plugins/projectexplorer/msvctoolchain.cpp
+++ b/src/plugins/projectexplorer/msvctoolchain.cpp
@@ -410,31 +410,18 @@ static QByteArray msvcCompilationFile()
 //
 // [1] https://msdn.microsoft.com/en-us/library/b0084kay.aspx
 // [2] http://stackoverflow.com/questions/3665537/how-to-find-out-cl-exes-built-in-macros
-QByteArray MsvcToolChain::msvcPredefinedMacros(const QStringList cxxflags,
-                                               const Utils::Environment &env) const
+Macros MsvcToolChain::msvcPredefinedMacros(const QStringList cxxflags,
+                                           const Utils::Environment &env) const
 {
-    QByteArray predefinedMacros;
+    Macros predefinedMacros;
 
     QStringList toProcess;
-    foreach (const QString &arg, cxxflags) {
+    for (const QString &arg : cxxflags) {
         if (arg.startsWith(QLatin1String("/D"))) {
-            QString define = arg.mid(2);
-            int pos = define.indexOf(QLatin1Char('='));
-            if (pos < 0) {
-                predefinedMacros += "#define ";
-                predefinedMacros += define.toLocal8Bit();
-                predefinedMacros += '\n';
-            } else {
-                predefinedMacros += "#define ";
-                predefinedMacros += define.left(pos).toLocal8Bit();
-                predefinedMacros += ' ';
-                predefinedMacros += define.mid(pos + 1).toLocal8Bit();
-                predefinedMacros += '\n';
-            }
+            const QString define = arg.mid(2);
+            predefinedMacros.append(Macro::fromKeyValue(define));
         } else if (arg.startsWith(QLatin1String("/U"))) {
-            predefinedMacros += "#undef ";
-            predefinedMacros += arg.mid(2).toLocal8Bit();
-            predefinedMacros += '\n';
+            predefinedMacros.append({arg.mid(2).toLocal8Bit(), ProjectExplorer::MacroType::Undefine});
         } else if (arg.startsWith(QLatin1String("-I"))) {
             // Include paths should not have any effect on defines
         } else {
@@ -468,18 +455,8 @@ QByteArray MsvcToolChain::msvcPredefinedMacros(const QStringList cxxflags,
 
     const QStringList output = Utils::filtered(response.stdOut().split('\n'),
                                                [](const QString &s) { return s.startsWith('V'); });
-    foreach (const QString& line, output) {
-        QStringList split = line.split('=');
-        const QString key = split.at(0).mid(1);
-        QString value = split.at(1);
-        predefinedMacros += "#define ";
-        predefinedMacros += key.toUtf8();
-        predefinedMacros += ' ';
-        predefinedMacros += value.toUtf8();
-        predefinedMacros += '\n';
-    }
-    if (debug)
-        qDebug() << "msvcPredefinedMacros" << predefinedMacros;
+    for (const QString &line : output)
+        predefinedMacros.append(Macro::fromKeyValue(line.mid(1)));
     return predefinedMacros;
 }
 
diff --git a/src/plugins/projectexplorer/msvctoolchain.h b/src/plugins/projectexplorer/msvctoolchain.h
index fd341177eb331c446ffd32ea1a8b0b8f4ededbbc..3dcb715286a3de7a47c61d41cbb34e93bc23c905 100644
--- a/src/plugins/projectexplorer/msvctoolchain.h
+++ b/src/plugins/projectexplorer/msvctoolchain.h
@@ -82,8 +82,8 @@ protected:
 
     Utils::Environment readEnvironmentSetting(const Utils::Environment& env) const final;
     // Function must be thread-safe!
-    QByteArray msvcPredefinedMacros(const QStringList cxxflags,
-                                    const Utils::Environment &env) const override;
+    Macros msvcPredefinedMacros(const QStringList cxxflags,
+                                const Utils::Environment &env) const override;
 
 private:
     QList<Utils::EnvironmentItem> environmentModifications() const;
diff --git a/src/plugins/projectexplorer/projectexplorer.pro b/src/plugins/projectexplorer/projectexplorer.pro
index d702d3a1205e339140ecc1420331fd505bd7bec5..979a3e865d8e0200f26aa73f0d35257bc48303c8 100644
--- a/src/plugins/projectexplorer/projectexplorer.pro
+++ b/src/plugins/projectexplorer/projectexplorer.pro
@@ -149,7 +149,8 @@ HEADERS += projectexplorer.h \
     projectexplorer_global.h \
     extracompiler.h \
     customexecutableconfigurationwidget.h \
-    customexecutablerunconfiguration.h
+    customexecutablerunconfiguration.h \
+    projectmacro.h
 
 SOURCES += projectexplorer.cpp \
     abi.cpp \
@@ -284,7 +285,8 @@ SOURCES += projectexplorer.cpp \
     projectexplorericons.cpp \
     extracompiler.cpp \
     customexecutableconfigurationwidget.cpp \
-    customexecutablerunconfiguration.cpp
+    customexecutablerunconfiguration.cpp \
+    projectmacro.cpp
 
 FORMS += processstep.ui \
     editorsettingspropertiespage.ui \
diff --git a/src/plugins/projectexplorer/projectexplorerunittestfiles.pri b/src/plugins/projectexplorer/projectexplorerunittestfiles.pri
new file mode 100644
index 0000000000000000000000000000000000000000..6b005059e6abd2f4d3df1fcfd5ed7a2e1f4c4a95
--- /dev/null
+++ b/src/plugins/projectexplorer/projectexplorerunittestfiles.pri
@@ -0,0 +1,12 @@
+shared {
+    DEFINES += PROJECTEXPLORER_LIBRARY
+} else {
+    DEFINES += PROJECTEXPLORER_STATIC_LIBRARY
+}
+
+HEADERS += \
+    $$PWD/projectmacro.h
+
+SOURCES += \
+    $$PWD/projectmacro.cpp
+
diff --git a/src/plugins/projectexplorer/projectmacro.cpp b/src/plugins/projectexplorer/projectmacro.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e69e60421df740b610777900b83a9e879082d6d0
--- /dev/null
+++ b/src/plugins/projectexplorer/projectmacro.cpp
@@ -0,0 +1,219 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "projectmacro.h"
+
+#include <utils/algorithm.h>
+
+namespace ProjectExplorer {
+
+bool Macro::isValid() const
+{
+    return !key.isEmpty() && type != MacroType::Invalid;
+}
+
+QByteArray Macro::toByteArray() const
+{
+    switch (type) {
+        case MacroType::Define: return QByteArray("#define ") + key + ' ' + value;
+        case MacroType::Undefine: return QByteArray("#undef ") + key;
+        case MacroType::Invalid: break;
+    }
+
+    return QByteArray();
+}
+
+QByteArray Macro::toByteArray(const Macros &macros)
+{
+    QByteArray text;
+
+    for (const Macro &macro : macros) {
+        const QByteArray macroText = macro.toByteArray();
+        if (!macroText.isEmpty())
+            text += macroText + '\n';
+    }
+
+    return  text;
+}
+
+QByteArray Macro::toByteArray(const QVector<Macros> &macrosVector)
+{
+    QByteArray text;
+
+    for (const Macros &macros : macrosVector)
+            text += toByteArray(macros);
+
+    return  text;
+}
+
+Macros Macro::toMacros(const QByteArray &text)
+{
+    return tokensLinesToMacros(tokenizeLines(splitLines(text)));
+}
+
+Macro Macro::fromKeyValue(const QString &utf16text)
+{
+    return fromKeyValue(utf16text.toUtf8());
+}
+
+Macro Macro::fromKeyValue(const QByteArray &text)
+{
+    QByteArray key;
+    QByteArray value;
+    MacroType type = MacroType::Invalid;
+
+    if (!text.isEmpty()) {
+        type = MacroType::Define;
+
+        int index = text.indexOf('=');
+
+        if (index != -1) {
+            key = text.left(index).trimmed();
+            value = text.mid(index + 1).trimmed();
+        } else {
+            key = text.trimmed();
+            value = "1";
+        }
+    }
+
+    return Macro(key, value, type);
+}
+
+QByteArray Macro::toKeyValue(const QByteArray &prefix) const
+{
+    QByteArray keyValue;
+    if (type != MacroType::Invalid)
+        keyValue = prefix;
+
+    if (value.isEmpty())
+        keyValue += key;
+    else
+        keyValue += key + '=' + value;
+
+    return keyValue;
+}
+
+static void removeCarriageReturn(QByteArray &line)
+{
+    if (line.endsWith('\r'))
+        line.truncate(line.size() - 1);
+}
+
+static void removeCarriageReturns(QList<QByteArray> &lines)
+{
+    for (QByteArray &line : lines)
+        removeCarriageReturn(line);
+}
+
+QList<QByteArray> Macro::splitLines(const QByteArray &text)
+{
+    QList<QByteArray> splitLines = text.split('\n');
+
+    splitLines.removeAll("");
+    removeCarriageReturns(splitLines);
+
+    return splitLines;
+}
+
+QByteArray Macro::removeNonsemanticSpaces(QByteArray line)
+{
+    auto begin = line.begin();
+    auto end = line.end();
+    bool notInString = true;
+
+    auto newEnd = std::unique(begin, end, [&] (char first, char second) {
+        notInString = notInString && first != '\"';
+        return notInString && (first == '#' || std::isspace(first)) && std::isspace(second);
+    });
+
+    line.truncate(line.size() - int(std::distance(newEnd, end)));
+
+    return line.trimmed();
+}
+
+QList<QByteArray> Macro::tokenizeLine(const QByteArray &line)
+{
+    const QByteArray normalizedLine = removeNonsemanticSpaces(line);
+
+    const auto begin = normalizedLine.begin();
+    auto first = std::find(normalizedLine.begin(), normalizedLine.end(), ' ');
+    auto second = std::find(std::next(first), normalizedLine.end(), ' ');
+    const auto end = normalizedLine.end();
+
+    QList<QByteArray> tokens;
+
+    if (first != end) {
+        tokens.append(QByteArray(begin, int(std::distance(begin, first))));
+
+        std::advance(first, 1);
+        tokens.append(QByteArray(first, int(std::distance(first, second))));
+
+        if (second != end) {
+            std::advance(second, 1);
+            tokens.append(QByteArray(second, int(std::distance(second, end))));
+        }
+    }
+
+   return tokens;
+}
+
+QList<QList<QByteArray>> Macro::tokenizeLines(const QList<QByteArray> &lines)
+{
+    QList<QList<QByteArray>> tokensLines = Utils::transform(lines, &Macro::tokenizeLine);
+
+    return tokensLines;
+}
+
+Macro Macro::tokensToMacro(const QList<QByteArray> &tokens)
+{
+    Macro macro;
+
+    if (tokens.size() >= 2 && tokens[0] == "#define") {
+        macro.type = MacroType::Define;
+        macro.key = tokens[1];
+
+        if (tokens.size() >= 3)
+            macro.value = tokens[2];
+    }
+
+    return macro;
+}
+
+Macros Macro::tokensLinesToMacros(const QList<QList<QByteArray>> &tokensLines)
+{
+    Macros macros;
+    macros.reserve(tokensLines.size());
+
+    for (const QList<QByteArray> &tokens : tokensLines) {
+        Macro macro = tokensToMacro(tokens);
+
+        if (macro.type != MacroType::Invalid)
+            macros.push_back(std::move(macro));
+    }
+
+    return macros;
+}
+
+} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/projectmacro.h b/src/plugins/projectexplorer/projectmacro.h
new file mode 100644
index 0000000000000000000000000000000000000000..9489b54d809c26a8d98d72e10f520cdaac884086
--- /dev/null
+++ b/src/plugins/projectexplorer/projectmacro.h
@@ -0,0 +1,102 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "projectexplorer_export.h"
+
+#include <QByteArray>
+#include <QHash>
+#include <QVector>
+
+namespace ProjectExplorer {
+
+enum class MacroType
+{
+    Invalid,
+    Define,
+    Undefine
+};
+
+class Macro;
+
+using Macros = QVector<Macro>;
+
+class PROJECTEXPLORER_EXPORT Macro
+{
+public:
+    Macro() = default;
+
+    Macro(QByteArray key, QByteArray value, MacroType type = MacroType::Define)
+        : key(key), value(value), type(type)
+    {}
+
+    Macro(QByteArray key, MacroType type = MacroType::Define)
+        : key(key), type(type)
+    {}
+
+    bool isValid() const;
+
+    QByteArray toByteArray() const;
+    static QByteArray toByteArray(const Macros &macros);
+    static QByteArray toByteArray(const QVector<Macros> &macross);
+
+    static Macros toMacros(const QByteArray &text);
+
+    // define Foo will be converted to Foo=1
+    static Macro fromKeyValue(const QString &utf16text);
+    static Macro fromKeyValue(const QByteArray &text);
+    QByteArray toKeyValue(const QByteArray &prefix) const;
+
+public:
+    QByteArray key;
+    QByteArray value;
+    MacroType type = MacroType::Invalid;
+
+private:
+    static QList<QByteArray> splitLines(const QByteArray &text);
+    static QByteArray removeNonsemanticSpaces(QByteArray line);
+    static QList<QByteArray> tokenizeLine(const QByteArray &line);
+    static QList<QList<QByteArray>> tokenizeLines(const QList<QByteArray> &lines);
+    static Macro tokensToMacro(const QList<QByteArray> &tokens);
+    static Macros tokensLinesToMacros(const QList<QList<QByteArray>> &tokensLines);
+};
+
+inline
+uint qHash(const Macro &macro)
+{
+    using ::qHash;
+    return qHash(macro.key) ^ qHash(macro.value) ^ qHash(int(macro.type));
+}
+
+inline
+bool operator==(const Macro &first, const Macro &second)
+{
+    return first.type == second.type
+        && first.key == second.key
+        && first.value == second.value;
+}
+
+} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/toolchain.h b/src/plugins/projectexplorer/toolchain.h
index 8c461bd398738b3d2651fe2f2169660e61082744..4ee56f05a8f7692ac3c8ebcae70a615f3cc12d62 100644
--- a/src/plugins/projectexplorer/toolchain.h
+++ b/src/plugins/projectexplorer/toolchain.h
@@ -27,6 +27,7 @@
 
 #include "projectexplorer_export.h"
 #include "projectexplorer_global.h"
+#include "projectmacro.h"
 
 #include <coreplugin/id.h>
 
@@ -122,9 +123,9 @@ public:
     virtual WarningFlags warningFlags(const QStringList &cflags) const = 0;
 
     // A PredefinedMacrosRunner is created in the ui thread and runs in another thread.
-    using PredefinedMacrosRunner = std::function<QByteArray(const QStringList &cxxflags)>;
+    using PredefinedMacrosRunner = std::function<Macros(const QStringList &cxxflags)>;
     virtual PredefinedMacrosRunner createPredefinedMacrosRunner() const = 0;
-    virtual QByteArray predefinedMacros(const QStringList &cxxflags) const = 0;
+    virtual Macros predefinedMacros(const QStringList &cxxflags) const = 0;
 
     // A SystemHeaderPathsRunner is created in the ui thread and runs in another thread.
     using SystemHeaderPathsRunner = std::function<QList<HeaderPath>(const QStringList &cxxflags, const QString &sysRoot)>;
diff --git a/src/plugins/projectexplorer/toolchainmanager.cpp b/src/plugins/projectexplorer/toolchainmanager.cpp
index df7d6888ec164b2f4780784039857c061f819d5f..b6c79b8773c48177de4b8dccbd606396d477e2c6 100644
--- a/src/plugins/projectexplorer/toolchainmanager.cpp
+++ b/src/plugins/projectexplorer/toolchainmanager.cpp
@@ -492,7 +492,7 @@ public:
     Abi targetAbi() const override { return Abi::hostAbi(); }
     bool isValid() const override { return m_valid; }
     PredefinedMacrosRunner createPredefinedMacrosRunner() const override { return PredefinedMacrosRunner(); }
-    QByteArray predefinedMacros(const QStringList &cxxflags) const override { Q_UNUSED(cxxflags); return QByteArray(); }
+    Macros predefinedMacros(const QStringList &cxxflags) const override { Q_UNUSED(cxxflags); return Macros(); }
     CompilerFlags compilerFlags(const QStringList &cxxflags) const override { Q_UNUSED(cxxflags); return NoFlags; }
     WarningFlags warningFlags(const QStringList &cflags) const override { Q_UNUSED(cflags); return WarningFlags::NoWarnings; }
     SystemHeaderPathsRunner createSystemHeaderPathsRunner() const override { return SystemHeaderPathsRunner(); }
diff --git a/src/plugins/qbsprojectmanager/qbsproject.cpp b/src/plugins/qbsprojectmanager/qbsproject.cpp
index 11e035a0e73b20235dde951c5e4e5eb5a85da7e6..902bed93f022f9d46356d7920427326cb619ca96 100644
--- a/src/plugins/qbsprojectmanager/qbsproject.cpp
+++ b/src/plugins/qbsprojectmanager/qbsproject.cpp
@@ -975,17 +975,7 @@ void QbsProject::updateCppCodeModel()
             QStringList list = props.getModulePropertiesAsStringList(
                         QLatin1String(CONFIG_CPP_MODULE),
                         QLatin1String(CONFIG_DEFINES));
-            QByteArray grpDefines;
-            foreach (const QString &def, list) {
-                QByteArray data = def.toUtf8();
-                int pos = data.indexOf('=');
-                if (pos >= 0)
-                    data[pos] = ' ';
-                else
-                    data.append(" 1"); // cpp.defines: [ "FOO" ] is considered to be "FOO=1"
-                grpDefines += (QByteArray("#define ") + data + '\n');
-            }
-            rpp.setDefines(grpDefines);
+            rpp.setMacros(Utils::transform<QVector>(list, [](const QString &s) { return ProjectExplorer::Macro::fromKeyValue(s); }));
 
             list = props.getModulePropertiesAsStringList(QLatin1String(CONFIG_CPP_MODULE),
                                                          QLatin1String(CONFIG_INCLUDEPATHS));
diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.cpp b/src/plugins/qmakeprojectmanager/qmakeproject.cpp
index 0d4703b15d03c3506b725631239c38ce400ae493..f53192e016f9da5494e0630dc165b390a065fb4f 100644
--- a/src/plugins/qmakeprojectmanager/qmakeproject.cpp
+++ b/src/plugins/qmakeprojectmanager/qmakeproject.cpp
@@ -292,7 +292,7 @@ void QmakeProject::updateCppCodeModel()
         rpp.setBuildSystemTarget(pro->targetInformation().target);
         // TODO: Handle QMAKE_CFLAGS
         rpp.setFlagsForCxx({cxxToolChain, pro->variableValue(Variable::CppFlags)});
-        rpp.setDefines(pro->cxxDefines());
+        rpp.setMacros(ProjectExplorer::Macro::toMacros(pro->cxxDefines()));
         rpp.setPreCompiledHeaders(pro->variableValue(Variable::PrecompiledHeader));
         rpp.setSelectedForBuilding(pro->includedInExactParse());
 
diff --git a/tests/unit/mockup/projectexplorer/toolchain.h b/tests/unit/mockup/projectexplorer/toolchain.h
index 33bfa836dff71b44b37025e1c5cee470ea029966..9bcf43a59be1b219aa2c7837c1d975a0ce1e58f5 100644
--- a/tests/unit/mockup/projectexplorer/toolchain.h
+++ b/tests/unit/mockup/projectexplorer/toolchain.h
@@ -27,6 +27,7 @@
 
 #include <projectexplorer/headerpath.h>
 #include <projectexplorer/abi.h>
+#include <projectexplorer/projectmacro.h>
 #include <coreplugin/id.h>
 
 #include <functional>
@@ -59,7 +60,7 @@ public:
     using SystemHeaderPathsRunner = std::function<QList<HeaderPath>(const QStringList &cxxflags, const QString &sysRoot)>;
     virtual SystemHeaderPathsRunner createSystemHeaderPathsRunner() const { return SystemHeaderPathsRunner(); }
 
-    using PredefinedMacrosRunner = std::function<QByteArray(const QStringList &cxxflags)>;
+    using PredefinedMacrosRunner = std::function<Macros(const QStringList &cxxflags)>;
     virtual PredefinedMacrosRunner createPredefinedMacrosRunner() const { return PredefinedMacrosRunner(); }
 
     virtual QString originalTargetTriple() const { return QString(); }
diff --git a/tests/unit/unittest/creator_dependency.pri b/tests/unit/unittest/creator_dependency.pri
index edf7417029be0dbb2593b5dc13dc0d40a6e97d40..b9a5db097c802574b83da1290886b50d9a58308d 100644
--- a/tests/unit/unittest/creator_dependency.pri
+++ b/tests/unit/unittest/creator_dependency.pri
@@ -7,13 +7,13 @@ include($$PWD/../../../src/libs/utils/utils-lib.pri)
 include($$PWD/../../../src/libs/sqlite/sqlite-lib.pri)
 include($$PWD/../../../src/libs/clangsupport/clangsupport-lib.pri)
 include($$PWD/../../../src/plugins/coreplugin/corepluginunittestfiles.pri)
+include($$PWD/../../../src/plugins/projectexplorer/projectexplorerunittestfiles.pri)
 include($$PWD/../../../src/tools/clangrefactoringbackend/source/clangrefactoringbackend-source.pri)
 include($$PWD/../../../src/tools/clangpchmanagerbackend/source/clangpchmanagerbackend-source.pri)
 include($$PWD/../../../src/plugins/clangrefactoring/clangrefactoring-source.pri)
 include($$PWD/../../../src/plugins/clangpchmanager/clangpchmanager-source.pri)
 include($$PWD/../../../src/plugins/cpptools/cpptoolsunittestfiles.pri)
 include(cplusplus.pri)
-
 !isEmpty(LLVM_INSTALL_DIR) {
 include($$PWD/../../../src/shared/clang/clang_defines.pri)
 include($$PWD/../../../src/tools/clangbackend/ipcsource/clangbackendclangipc-source.pri)
diff --git a/tests/unit/unittest/google-using-declarations.h b/tests/unit/unittest/google-using-declarations.h
index 4a04cec3944752d5b2732a5bb16c04226450244c..7a1fe3b30fe02a468e28c22fdfe88f347ac708d5 100644
--- a/tests/unit/unittest/google-using-declarations.h
+++ b/tests/unit/unittest/google-using-declarations.h
@@ -43,4 +43,5 @@ using testing::Property;
 using testing::Return;
 using testing::ReturnRef;
 using testing::Sequence;
+using testing::StrEq;
 using testing::UnorderedElementsAre;
diff --git a/tests/unit/unittest/gtest-creator-printing.cpp b/tests/unit/unittest/gtest-creator-printing.cpp
index 5980ec6c63ee6bb125078b6a25d5ffb9685154f7..d7cb22b7ce4c3c32cf18d4cd6f710c91d658004c 100644
--- a/tests/unit/unittest/gtest-creator-printing.cpp
+++ b/tests/unit/unittest/gtest-creator-printing.cpp
@@ -29,6 +29,8 @@
 
 #include <coreplugin/find/searchresultitem.h>
 
+#include <projectexplorer/projectmacro.h>
+
 namespace Core {
 namespace Search {
 
@@ -54,3 +56,35 @@ void PrintTo(const TextRange &range, ::std::ostream *os)
 
 }
 }
+
+namespace ProjectExplorer {
+
+static const char *typeToString(const MacroType &type)
+{
+    switch (type) {
+        case MacroType::Invalid: return "MacroType::Invalid";
+        case MacroType::Define: return "MacroType::Define";
+        case MacroType::Undefine: return  "MacroType::Undefine";
+    }
+
+    return "";
+}
+
+std::ostream &operator<<(std::ostream &out, const MacroType &type)
+{
+    out << typeToString(type);
+
+    return out;
+}
+
+std::ostream &operator<<(std::ostream &out, const Macro &macro)
+{
+    out << "("
+        << macro.key.data() << ", "
+        << macro.value.data() << ", "
+        << macro.type << ")";
+
+  return out;
+}
+
+}
diff --git a/tests/unit/unittest/gtest-creator-printing.h b/tests/unit/unittest/gtest-creator-printing.h
index 814130f2272a531327b7d2848ab7fdf95640d85d..92db70f6ed17ac7189c7fabea10d2f2a2708aee0 100644
--- a/tests/unit/unittest/gtest-creator-printing.h
+++ b/tests/unit/unittest/gtest-creator-printing.h
@@ -42,3 +42,13 @@ void PrintTo(const TextRange &range, ::std::ostream *os);
 
 }
 }
+
+namespace ProjectExplorer {
+
+enum class MacroType;
+class Macro;
+
+std::ostream &operator<<(std::ostream &out, const MacroType &type);
+std::ostream &operator<<(std::ostream &out, const Macro &macro);
+
+}
diff --git a/tests/unit/unittest/projectmacro-test.cpp b/tests/unit/unittest/projectmacro-test.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2c9258e635446c25d739eb4d328181d1ab9c43d3
--- /dev/null
+++ b/tests/unit/unittest/projectmacro-test.cpp
@@ -0,0 +1,408 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "googletest.h"
+
+#define private public
+#include <projectexplorer/projectmacro.h>
+#undef private
+
+namespace {
+
+using ProjectExplorer::MacroType;
+using ProjectExplorer::Macro;
+using ProjectExplorer::Macros;
+
+MATCHER_P3(IsMacro, key, value, type,
+           std::string(negation ? "isn't" : "is")
+           + " key "+ PrintToString(key)
+           + ", value " + PrintToString(value)
+           + " and type " + PrintToString(type))
+{
+    return arg.key == key && arg.value == value && arg.type == type;
+}
+
+TEST(Macro, SplitLines)
+{
+    QByteArray text = "#define Foo 42\n\n#define Bar\n#define HoHoHo Bar\n// foo";
+
+    auto textLines = Macro::splitLines(text);
+
+    ASSERT_THAT(textLines, ElementsAre("#define Foo 42", "#define Bar", "#define HoHoHo Bar", "// foo"));
+}
+
+TEST(Macro, RemoveCarriageReturn)
+{
+    QByteArray text = "#define Foo 42\r\n#define Bar\n";
+
+    auto textLines = Macro::splitLines(text);
+
+    ASSERT_THAT(textLines, ElementsAre("#define Foo 42", "#define Bar"));
+}
+
+TEST(Macro, TokenizeNullLine)
+{
+    QByteArray line;
+
+    auto tokens = Macro::tokenizeLine(line);
+
+    ASSERT_THAT(tokens, IsEmpty());
+}
+
+TEST(Macro, TokenizeEmptyLine)
+{
+    QByteArray line = "";
+
+    auto tokens = Macro::tokenizeLine(line);
+
+    ASSERT_THAT(tokens, IsEmpty());
+}
+
+TEST(Macro, TokenizeSpaces)
+{
+    QByteArray line = "   ";
+
+    auto tokens = Macro::tokenizeLine(line);
+
+    ASSERT_THAT(tokens, IsEmpty());
+}
+
+TEST(Macro, TokenizeOneEntry)
+{
+    QByteArray line = "//blah";
+
+    auto tokens = Macro::tokenizeLine(line);
+
+    ASSERT_THAT(tokens, IsEmpty());
+}
+
+TEST(Macro, TokenizeThreeEntries)
+{
+    QByteArray line = "#define Foo 42";
+
+    auto tokens = Macro::tokenizeLine(line);
+
+    ASSERT_THAT(tokens, ElementsAre("#define", "Foo", "42"));
+}
+
+TEST(Macro, TokenizeManyEntries)
+{
+    QByteArray line = "#define Foo unsigned long long int";
+
+    auto tokens = Macro::tokenizeLine(line);
+
+    ASSERT_THAT(tokens, ElementsAre("#define", "Foo", "unsigned long long int"));
+}
+
+TEST(Macro, TokenizeWithMutipleSpaces)
+{
+    QByteArray line = "#define  Foo 42";
+
+    auto tokens = Macro::tokenizeLine(line);
+
+    ASSERT_THAT(tokens, ElementsAre("#define", "Foo", "42"));
+}
+
+TEST(Macro, TokenizeLines)
+{
+    QList<QByteArray> lines = {"#define Foo 42",
+                              "#define Bar Ho",
+                              "// this is a comment",
+                              " "};
+
+    auto tokensLines = Macro::tokenizeLines(lines);
+
+    ASSERT_THAT(tokensLines,
+                ElementsAre(ElementsAre("#define", "Foo", "42"),
+                            ElementsAre("#define", "Bar", "Ho"),
+                            ElementsAre("//", "this", "is a comment"),
+                            IsEmpty()));
+}
+
+TEST(Macro, SpacesBeforEntries)
+{
+    QByteArray line = "    #define Foo";
+
+    auto strippedLine = Macro::removeNonsemanticSpaces(line);
+
+    ASSERT_THAT(strippedLine, "#define Foo");
+}
+
+TEST(Macro, SpacesAfterEntries)
+{
+    QByteArray line = "#define Foo    ";
+
+    auto strippedLine = Macro::removeNonsemanticSpaces(line);
+
+    ASSERT_THAT(strippedLine, "#define Foo");
+}
+
+TEST(Macro, ManySpacesInbetweenEntries)
+{
+    QByteArray line = "#define \t  Foo  42";
+
+    auto strippedLine = Macro::removeNonsemanticSpaces(line);
+
+    ASSERT_THAT(strippedLine, "#define Foo 42");
+}
+
+TEST(Macro, EmptyString)
+{
+    QByteArray line = "#define \t  Foo \"\"";
+
+    auto strippedLine = Macro::removeNonsemanticSpaces(line);
+
+    ASSERT_THAT(strippedLine, "#define Foo \"\"");
+}
+
+TEST(Macro, StringWithQuotes)
+{
+    QByteArray line = "#define \t  Foo \"\\\"string\\\"\"";
+
+    auto strippedLine = Macro::removeNonsemanticSpaces(line);
+
+    ASSERT_THAT(strippedLine, "#define Foo \"\\\"string\\\"\"");
+}
+
+TEST(Macro, DISABLED_StringConcatenation)
+{
+    QByteArray line = "#define \t  Foo \"a\"     \"b\"";
+
+    auto strippedLine = Macro::removeNonsemanticSpaces(line);
+
+    ASSERT_THAT(strippedLine, "#define Foo \"a\" \"b\"");
+}
+
+TEST(Macro, DISABLED_TokenConcatenation)
+{
+    QByteArray line = "#define \t  Foo \"a\"  ##   c";
+
+    auto strippedLine = Macro::removeNonsemanticSpaces(line);
+
+    ASSERT_THAT(strippedLine, "#define Foo \"a\" ## c");
+}
+
+TEST(Macro, StringWithQuotesAndSpaces)
+{
+    QByteArray line = "#define \t  Foo \"\\\"string   \\\"\"";
+
+    auto strippedLine = Macro::removeNonsemanticSpaces(line);
+
+    ASSERT_THAT(strippedLine.toStdString(), "#define Foo \"\\\"string   \\\"\"");
+}
+
+TEST(Macro, SpacesAferHashEntries)
+{
+    QByteArray line = "#    define Foo 42";
+
+    auto strippedLine = Macro::removeNonsemanticSpaces(line);
+
+    ASSERT_THAT(strippedLine, "#define Foo 42");
+}
+
+TEST(Macro, SpacesInStringEntries)
+{
+    QByteArray line = "#define   Foo  \"some  text   with spaces\"";
+
+    auto strippedLine = Macro::removeNonsemanticSpaces(line);
+
+    ASSERT_THAT(strippedLine, "#define Foo \"some  text   with spaces\"");
+}
+
+TEST(Macro, EmptyTokensToMacro)
+{
+    QList<QByteArray> tokens;
+
+    auto macro = Macro::tokensToMacro(tokens);
+
+    ASSERT_THAT(macro, IsMacro("", "", MacroType::Invalid));
+}
+
+TEST(Macro, DefineTwoTokensToMacro)
+{
+    QList<QByteArray> tokens = {"#define", "Foo"};
+
+    auto macro = Macro::tokensToMacro(tokens);
+
+    ASSERT_THAT(macro, IsMacro("Foo", "", MacroType::Define));
+}
+
+TEST(Macro, DefineThreeTokensToMacro)
+{
+    QList<QByteArray> tokens = {"#define", "Foo", "42"};
+
+    auto macro = Macro::tokensToMacro(tokens);
+
+    ASSERT_THAT(macro, IsMacro("Foo", "42", MacroType::Define));
+}
+
+TEST(Macro, DoNotParseNonDefines)
+{
+    QList<QByteArray> tokens = {"//", "this", "is a comment"};
+
+    auto macro = Macro::tokensToMacro(tokens);
+
+    ASSERT_THAT(macro, IsMacro("", "", MacroType::Invalid));
+}
+
+TEST(Macro, TokensLinesToMacros)
+{
+    QList<QList<QByteArray>> tokensLines = {{"#define", "Foo", "42"},
+                                            {"#define", "Bar", "Ho"},
+                                            {"//", "this", "is", "a", "comment"},
+                                            {}};
+
+    auto macros = Macro::tokensLinesToMacros(tokensLines);
+
+    ASSERT_THAT(macros, ElementsAre(IsMacro("Foo", "42", MacroType::Define),
+                                    IsMacro("Bar", "Ho", MacroType::Define)));
+}
+
+
+TEST(Macro, TextToMacros)
+{
+    QByteArray text = {"#define Foo 42\n"
+                       "#define Bar Ho\n"
+                       "// this is a comment\n"
+                       " "};
+
+    auto macros = Macro::toMacros(text);
+
+    ASSERT_THAT(macros, ElementsAre(IsMacro("Foo", "42", MacroType::Define),
+                                    IsMacro("Bar", "Ho", MacroType::Define)));
+}
+
+TEST(Macro, InvalidToText)
+{
+    Macro macro;
+
+    auto text = macro.toByteArray();
+
+    ASSERT_THAT(text, QByteArray());
+}
+
+TEST(Macro, DefineToText)
+{
+    Macro macro{"Foo", "Bar", MacroType::Define};
+
+    auto text = macro.toByteArray();
+
+    ASSERT_THAT(text, "#define Foo Bar");
+}
+
+TEST(Macro, DefineWithSpacesToText)
+{
+    Macro macro{"LongInt", "long long int", MacroType::Define};
+
+    auto text = macro.toByteArray();
+
+    ASSERT_THAT(text, "#define LongInt long long int");
+}
+
+TEST(Macro, UndefineToText)
+{
+    Macro macro{"Foo", "Bar", MacroType::Undefine};
+
+    auto text = macro.toByteArray();
+
+    ASSERT_THAT(text, "#undef Foo");
+}
+
+TEST(Macro, MacrosToText)
+{
+    Macros macros{{"LongInt", "long long int"}, {"Foo", "Bar"}, {}};
+
+    auto text = Macro::toByteArray(macros);
+
+    ASSERT_THAT(text, "#define LongInt long long int\n#define Foo Bar\n");
+}
+
+TEST(Macro, MacrosVectorToText)
+{
+    Macros macros{{"LongInt", "long long int"}, {"Foo", "Bar"}, {}, {"Foo", MacroType::Undefine}};
+
+    auto text = Macro::toByteArray(macros);
+
+    ASSERT_THAT(text, "#define LongInt long long int\n#define Foo Bar\n#undef Foo\n");
+}
+
+TEST(Macro, EmptyKeyValueToMacro)
+{
+    QString text;
+
+    auto macro = Macro::fromKeyValue(text);
+
+    ASSERT_THAT(macro, IsMacro("", "", MacroType::Invalid));
+}
+
+TEST(Macro, KeyToMacro)
+{
+    QString text = "Foo";
+
+    auto macro = Macro::fromKeyValue(text);
+
+    ASSERT_THAT(macro, IsMacro("Foo", "1", MacroType::Define));
+}
+
+TEST(Macro, KeyValueToMacro)
+{
+    QString text = "Foo=\"Some=Text\"";
+
+    auto macro = Macro::fromKeyValue(text);
+
+    ASSERT_THAT(macro, IsMacro("Foo", "\"Some=Text\"", MacroType::Define));
+}
+
+TEST(Macro, InvalidMacroToKeyValue)
+{
+    Macro macro;
+    QByteArray prefix = "-D";
+
+    auto keyValue = macro.toKeyValue(prefix);
+
+    ASSERT_THAT(keyValue, "");
+}
+
+TEST(Macro, DefineKeyOnlyMacroToKeyValue)
+{
+    Macro macro{"Foo"};
+    QByteArray prefix = "-D";
+
+    auto keyValue = macro.toKeyValue(prefix);
+
+    ASSERT_THAT(keyValue, "-DFoo");
+}
+
+TEST(Macro, DefineMacroToKeyValue)
+{
+    Macro macro{"Foo", "Bar"};
+    QByteArray prefix = "-D";
+
+    auto keyValue = macro.toKeyValue(prefix);
+
+    ASSERT_THAT(keyValue, "-DFoo=Bar");
+}
+}
+
diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro
index 1f1b490536a03e3943ceafa16831faac9119328d..e4f2c363c0a951e256c14ad3ddcc94fc06165c7a 100644
--- a/tests/unit/unittest/unittest.pro
+++ b/tests/unit/unittest/unittest.pro
@@ -37,7 +37,6 @@ DEFINES += CPPTOOLS_JSON=\"R\\\"xxx($${cpptoolsjson.output})xxx\\\"\"
 
 SOURCES += \
     changedfilepathcompressor-test.cpp \
-    clangfollowsymbol-test.cpp \
     clangpathwatcher-test.cpp \
     clangqueryexamplehighlightmarker-test.cpp \
     clangqueryhighlightmarker-test.cpp \
@@ -59,6 +58,7 @@ SOURCES += \
     pchmanagerclient-test.cpp \
     pchmanagerserver-test.cpp \
     processevents-utilities.cpp \
+    projectmacro-test.cpp \
     projectparts-test.cpp \
     projectupdater-test.cpp \
     readandwritemessageblock-test.cpp \
@@ -96,6 +96,7 @@ SOURCES += \
     clangdocumentsuspenderresumer-test.cpp \
     clangdocument-test.cpp \
     clangfixitoperation-test.cpp \
+    clangfollowsymbol-test.cpp \
     clangisdiagnosticrelatedtolocation-test.cpp \
     clangjobqueue-test.cpp \
     clangjobs-test.cpp \