Browse Source

Merge topic 'cmake-open'

96d642c7 cmake-gui: Use cmake::Open to open generated project
5de37a4a cmake: Add --open option for IDE generators

Acked-by: Kitware Robot <[email protected]>
Acked-by: Ruslan Baratov <[email protected]>
Merge-request: !1337
Brad King 8 years ago
parent
commit
b0b94cdec5

+ 1 - 1
Auxiliary/bash-completion/cmake

@@ -96,7 +96,7 @@ _cmake()
             _filedir
             return
             ;;
-        --build)
+        --build|--open)
             _filedir -d
             return
             ;;

+ 5 - 0
Help/manual/cmake.1.rst

@@ -11,6 +11,7 @@ Synopsis
  cmake [<options>] (<path-to-source> | <path-to-existing-build>)
  cmake [(-D <var>=<value>)...] -P <cmake-script-file>
  cmake --build <dir> [<options>...] [-- <build-tool-options>...]
+ cmake --open <dir>
  cmake -E <command> [<options>...]
  cmake --find-package <options>...
 
@@ -51,6 +52,10 @@ Options
 ``--build <dir>``
  See `Build Tool Mode`_.
 
+``--open <dir>``
+ Open the generated project in the associated application.  This is
+ only supported by some generators.
+
 ``-N``
  View mode only.
 

+ 6 - 0
Help/release/dev/cmake-open.rst

@@ -0,0 +1,6 @@
+cmake-open
+----------
+
+* The :manual:`cmake(1)` ``--open <dir>`` command line option can now
+  be used to open generated IDE projects like Visual Studio solutions
+  or Xcode projects.

+ 23 - 0
Modules/CMakeFindSublimeText2.cmake

@@ -0,0 +1,23 @@
+# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+# file Copyright.txt or https://cmake.org/licensing for details.
+
+
+# This file is included in CMakeSystemSpecificInformation.cmake if
+# the Sublime Text 2 extra generator has been selected.
+
+find_program(CMAKE_SUBLIMETEXT_EXECUTABLE
+    NAMES subl3 subl sublime_text
+    PATHS
+        "/Applications/Sublime Text.app/Contents/SharedSupport/bin"
+        "/Applications/Sublime Text 3.app/Contents/SharedSupport/bin"
+        "/Applications/Sublime Text 2.app/Contents/SharedSupport/bin"
+        "$ENV{HOME}/Applications/Sublime Text.app/Contents/SharedSupport/bin"
+        "$ENV{HOME}/Applications/Sublime Text 3.app/Contents/SharedSupport/bin"
+        "$ENV{HOME}/Applications/Sublime Text 2.app/Contents/SharedSupport/bin"
+        "/opt/sublime_text"
+        "/opt/sublime_text_3"
+    DOC "The Sublime Text executable")
+
+if(CMAKE_SUBLIMETEXT_EXECUTABLE)
+   set(CMAKE_OPEN_PROJECT_COMMAND "${CMAKE_SUBLIMETEXT_EXECUTABLE} --project <PROJECT_FILE>" )
+endif()

+ 9 - 27
Source/QtDialog/CMakeSetupDialog.cxx

@@ -188,6 +188,9 @@ CMakeSetupDialog::CMakeSetupDialog()
   connect(this->Output, SIGNAL(customContextMenuRequested(const QPoint&)),
           this, SLOT(doOutputContextMenu(const QPoint&)));
 
+  // disable open project button
+  this->OpenProjectButton->setDisabled(true);
+
   // start the cmake worker thread
   this->CMakeThread = new QCMakeThread(this);
   QObject::connect(this->CMakeThread, SIGNAL(cmakeInitialized()), this,
@@ -249,6 +252,10 @@ void CMakeSetupDialog::initialize()
                    SIGNAL(outputMessage(QString)), this,
                    SLOT(message(QString)));
 
+  QObject::connect(this->CMakeThread->cmakeInstance(),
+                   SIGNAL(openPossible(bool)), this->OpenProjectButton,
+                   SLOT(setEnabled(bool)));
+
   QObject::connect(this->groupedCheck, SIGNAL(toggled(bool)), this,
                    SLOT(setGroupedView(bool)));
   QObject::connect(this->advancedCheck, SIGNAL(toggled(bool)), this,
@@ -492,24 +499,10 @@ void CMakeSetupDialog::doGenerate()
   this->ConfigureNeeded = true;
 }
 
-QString CMakeSetupDialog::getProjectFilename()
-{
-  QStringList nameFilter;
-  nameFilter << "*.sln"
-             << "*.xcodeproj";
-  QDir directory(this->BinaryDirectory->currentText());
-  QStringList nlnFile = directory.entryList(nameFilter);
-
-  if (nlnFile.count() == 1) {
-    return this->BinaryDirectory->currentText() + "/" + nlnFile.at(0);
-  }
-
-  return QString();
-}
-
 void CMakeSetupDialog::doOpenProject()
 {
-  QDesktopServices::openUrl(QUrl::fromLocalFile(this->getProjectFilename()));
+  QMetaObject::invokeMethod(this->CMakeThread->cmakeInstance(), "open",
+                            Qt::QueuedConnection);
 }
 
 void CMakeSetupDialog::closeEvent(QCloseEvent* e)
@@ -630,11 +623,6 @@ void CMakeSetupDialog::updateBinaryDirectory(const QString& dir)
     this->BinaryDirectory->setEditText(dir);
     this->BinaryDirectory->blockSignals(false);
   }
-  if (!this->getProjectFilename().isEmpty()) {
-    this->OpenProjectButton->setEnabled(true);
-  } else {
-    this->OpenProjectButton->setEnabled(false);
-  }
 }
 
 void CMakeSetupDialog::doBinaryBrowse()
@@ -1039,9 +1027,6 @@ void CMakeSetupDialog::enterState(CMakeSetupDialog::State s)
     this->GenerateButton->setEnabled(true);
     this->GenerateAction->setEnabled(true);
     this->ConfigureButton->setEnabled(true);
-    if (!this->getProjectFilename().isEmpty()) {
-      this->OpenProjectButton->setEnabled(true);
-    }
     this->ConfigureButton->setText(tr("&Configure"));
     this->GenerateButton->setText(tr("&Generate"));
   } else if (s == ReadyGenerate) {
@@ -1049,9 +1034,6 @@ void CMakeSetupDialog::enterState(CMakeSetupDialog::State s)
     this->GenerateButton->setEnabled(true);
     this->GenerateAction->setEnabled(true);
     this->ConfigureButton->setEnabled(true);
-    if (!this->getProjectFilename().isEmpty()) {
-      this->OpenProjectButton->setEnabled(true);
-    }
     this->ConfigureButton->setText(tr("&Configure"));
     this->GenerateButton->setText(tr("&Generate"));
   }

+ 0 - 1
Source/QtDialog/CMakeSetupDialog.h

@@ -31,7 +31,6 @@ protected slots:
   void initialize();
   void doConfigure();
   void doGenerate();
-  QString getProjectFilename();
   void doOpenProject();
   void doInstallForCommandLine();
   void doHelp();

+ 29 - 0
Source/QtDialog/QCMake.cxx

@@ -115,6 +115,8 @@ void QCMake::setBinaryDirectory(const QString& _dir)
     if (toolset) {
       this->setToolset(QString::fromLocal8Bit(toolset));
     }
+
+    checkOpenPossible();
   }
 }
 
@@ -183,6 +185,26 @@ void QCMake::generate()
 #endif
 
   emit this->generateDone(err);
+  checkOpenPossible();
+}
+
+void QCMake::open()
+{
+#ifdef Q_OS_WIN
+  UINT lastErrorMode = SetErrorMode(0);
+#endif
+
+  InterruptFlag = 0;
+  cmSystemTools::ResetErrorOccuredFlag();
+
+  auto successful = this->CMakeInstance->Open(
+    this->BinaryDirectory.toLocal8Bit().data(), false);
+
+#ifdef Q_OS_WIN
+  SetErrorMode(lastErrorMode);
+#endif
+
+  emit this->openDone(successful);
 }
 
 void QCMake::setProperties(const QCMakePropertyList& newProps)
@@ -450,3 +472,10 @@ void QCMake::setWarnUnusedMode(bool value)
 {
   this->WarnUnusedMode = value;
 }
+
+void QCMake::checkOpenPossible()
+{
+  auto data = this->BinaryDirectory.toLocal8Bit().data();
+  auto possible = this->CMakeInstance->Open(data, true);
+  emit openPossible(possible);
+}

+ 8 - 0
Source/QtDialog/QCMake.h

@@ -80,6 +80,8 @@ public slots:
   void configure();
   /// generate the files
   void generate();
+  /// open the project
+  void open();
   /// set the property values
   void setProperties(const QCMakePropertyList&);
   /// interrupt the configure or generate process (if connecting, make a direct
@@ -111,6 +113,8 @@ public slots:
   void setWarnUninitializedMode(bool value);
   /// set whether to run cmake with warnings about unused variables
   void setWarnUnusedMode(bool value);
+  /// check if project IDE open is possible and emit openPossible signal
+  void checkOpenPossible();
 
 public:
   /// get the list of cache properties
@@ -151,6 +155,10 @@ signals:
   void debugOutputChanged(bool);
   /// signal when the toolset changes
   void toolsetChanged(const QString& toolset);
+  /// signal when open is done
+  void openDone(bool successful);
+  /// signal when open is done
+  void openPossible(bool possible);
 
 protected:
   cmake* CMakeInstance;

+ 7 - 0
Source/cmExternalMakefileProjectGenerator.cxx

@@ -24,6 +24,13 @@ std::string cmExternalMakefileProjectGenerator::CreateFullGeneratorName(
   return fullName;
 }
 
+bool cmExternalMakefileProjectGenerator::Open(
+  const std::string& /*bindir*/, const std::string& /*projectName*/,
+  bool /*dryRun*/)
+{
+  return false;
+}
+
 cmExternalMakefileProjectGeneratorFactory::
   cmExternalMakefileProjectGeneratorFactory(const std::string& n,
                                             const std::string& doc)

+ 3 - 0
Source/cmExternalMakefileProjectGenerator.h

@@ -55,6 +55,9 @@ public:
   void SetName(const std::string& n) { Name = n; }
   std::string GetName() const { return Name; }
 
+  virtual bool Open(const std::string& bindir, const std::string& projectName,
+                    bool dryRun);
+
 protected:
   ///! Contains the names of the global generators support by this generator.
   std::vector<std::string> SupportedGlobalGenerators;

+ 23 - 0
Source/cmExtraSublimeTextGenerator.cxx

@@ -400,3 +400,26 @@ std::string cmExtraSublimeTextGenerator::ComputeDefines(
 
   return definesString;
 }
+
+bool cmExtraSublimeTextGenerator::Open(const std::string& bindir,
+                                       const std::string& projectName,
+                                       bool dryRun)
+{
+  const char* sublExecutable =
+    this->GlobalGenerator->GetCMakeInstance()->GetCacheDefinition(
+      "CMAKE_SUBLIMETEXT_EXECUTABLE");
+  if (!sublExecutable) {
+    return false;
+  }
+  if (cmSystemTools::IsNOTFOUND(sublExecutable)) {
+    return false;
+  }
+
+  std::string filename = bindir + "/" + projectName + ".sublime-project";
+  if (dryRun) {
+    return cmSystemTools::FileExists(filename, true);
+  }
+
+  return cmSystemTools::RunSingleCommand(
+    { sublExecutable, "--project", filename });
+}

+ 3 - 0
Source/cmExtraSublimeTextGenerator.h

@@ -65,6 +65,9 @@ private:
   std::string ComputeDefines(cmSourceFile* source, cmLocalGenerator* lg,
                              cmGeneratorTarget* gtgt);
 
+  bool Open(const std::string& bindir, const std::string& projectName,
+            bool dryRun) override;
+
   bool ExcludeBuildFolder;
   std::string EnvSettings;
 };

+ 10 - 0
Source/cmGlobalGenerator.cxx

@@ -1824,6 +1824,16 @@ int cmGlobalGenerator::Build(const std::string& /*unused*/,
   return retVal;
 }
 
+bool cmGlobalGenerator::Open(const std::string& bindir,
+                             const std::string& projectName, bool dryRun)
+{
+  if (this->ExtraGenerator) {
+    return this->ExtraGenerator->Open(bindir, projectName, dryRun);
+  }
+
+  return false;
+}
+
 std::string cmGlobalGenerator::GenerateCMakeBuildCommand(
   const std::string& target, const std::string& config,
   const std::string& native, bool ignoreErrors)

+ 6 - 0
Source/cmGlobalGenerator.h

@@ -162,6 +162,12 @@ public:
             std::vector<std::string> const& nativeOptions =
               std::vector<std::string>());
 
+  /**
+   * Open a generated IDE project given the following information.
+   */
+  virtual bool Open(const std::string& bindir, const std::string& projectName,
+                    bool dryRun);
+
   virtual void GenerateBuildCommand(
     std::vector<std::string>& makeCommand, const std::string& makeProgram,
     const std::string& projectName, const std::string& projectDir,

+ 33 - 0
Source/cmGlobalVisualStudioGenerator.cxx

@@ -4,7 +4,10 @@
 #include "cmGlobalVisualStudioGenerator.h"
 
 #include "cmsys/Encoding.hxx"
+#include <future>
 #include <iostream>
+#include <objbase.h>
+#include <shellapi.h>
 #include <windows.h>
 
 #include "cmAlgorithms.h"
@@ -928,3 +931,33 @@ void cmGlobalVisualStudioGenerator::AddSymbolExportCommand(
                           commandLines, "Auto build dll exports", ".");
   commands.push_back(command);
 }
+
+static bool OpenSolution(std::string sln)
+{
+  HRESULT comInitialized =
+    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
+  if (FAILED(comInitialized)) {
+    return false;
+  }
+
+  HINSTANCE hi =
+    ShellExecuteA(NULL, "open", sln.c_str(), NULL, NULL, SW_SHOWNORMAL);
+
+  CoUninitialize();
+
+  return reinterpret_cast<intptr_t>(hi) > 32;
+}
+
+bool cmGlobalVisualStudioGenerator::Open(const std::string& bindir,
+                                         const std::string& projectName,
+                                         bool dryRun)
+{
+  std::string buildDir = cmSystemTools::ConvertToOutputPath(bindir.c_str());
+  std::string sln = buildDir + "\\" + projectName + ".sln";
+
+  if (dryRun) {
+    return cmSystemTools::FileExists(sln, true);
+  }
+
+  return std::async(std::launch::async, OpenSolution, sln).get();
+}

+ 3 - 0
Source/cmGlobalVisualStudioGenerator.h

@@ -131,6 +131,9 @@ public:
                               std::vector<cmCustomCommand>& commands,
                               std::string const& configName);
 
+  bool Open(const std::string& bindir, const std::string& projectName,
+            bool dryRun) override;
+
 protected:
   virtual void AddExtraIDETargets();
 

+ 34 - 0
Source/cmGlobalXCodeGenerator.cxx

@@ -35,6 +35,11 @@
 
 struct cmLinkImplementation;
 
+#if defined(CMAKE_BUILD_WITH_CMAKE) && defined(__APPLE__)
+#define HAVE_APPLICATION_SERVICES
+#include <ApplicationServices/ApplicationServices.h>
+#endif
+
 #if defined(CMAKE_BUILD_WITH_CMAKE)
 #include "cmXMLParser.h"
 
@@ -287,6 +292,35 @@ void cmGlobalXCodeGenerator::EnableLanguage(
   this->ComputeArchitectures(mf);
 }
 
+bool cmGlobalXCodeGenerator::Open(const std::string& bindir,
+                                  const std::string& projectName, bool dryRun)
+{
+  bool ret = false;
+
+#ifdef HAVE_APPLICATION_SERVICES
+  std::string url = bindir + "/" + projectName + ".xcodeproj";
+
+  if (dryRun) {
+    return cmSystemTools::FileExists(url, false);
+  }
+
+  CFStringRef cfStr = CFStringCreateWithCString(
+    kCFAllocatorDefault, url.c_str(), kCFStringEncodingUTF8);
+  if (cfStr) {
+    CFURLRef cfUrl = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, cfStr,
+                                                   kCFURLPOSIXPathStyle, true);
+    if (cfUrl) {
+      OSStatus err = LSOpenCFURLRef(cfUrl, nullptr);
+      ret = err == noErr;
+      CFRelease(cfUrl);
+    }
+    CFRelease(cfStr);
+  }
+#endif
+
+  return ret;
+}
+
 void cmGlobalXCodeGenerator::GenerateBuildCommand(
   std::vector<std::string>& makeCommand, const std::string& makeProgram,
   const std::string& projectName, const std::string& /*projectDir*/,

+ 7 - 0
Source/cmGlobalXCodeGenerator.h

@@ -55,6 +55,13 @@ public:
    */
   void EnableLanguage(std::vector<std::string> const& languages, cmMakefile*,
                       bool optional) override;
+
+  /**
+   * Open a generated IDE project given the following information.
+   */
+  bool Open(const std::string& bindir, const std::string& projectName,
+            bool dryRun) override;
+
   /**
    * Try running cmake and building a file. This is used for dynalically
    * loaded commands, not as part of the usual build process.

+ 43 - 0
Source/cmake.cxx

@@ -2425,6 +2425,49 @@ int cmake::Build(const std::string& dir, const std::string& target,
                     nativeOptions);
 }
 
+bool cmake::Open(const std::string& dir, bool dryRun)
+{
+  this->SetHomeDirectory("");
+  this->SetHomeOutputDirectory("");
+  if (!cmSystemTools::FileIsDirectory(dir)) {
+    std::cerr << "Error: " << dir << " is not a directory\n";
+    return false;
+  }
+
+  std::string cachePath = FindCacheFile(dir);
+  if (!this->LoadCache(cachePath)) {
+    std::cerr << "Error: could not load cache\n";
+    return false;
+  }
+  const char* genName = this->State->GetCacheEntryValue("CMAKE_GENERATOR");
+  if (!genName) {
+    std::cerr << "Error: could not find CMAKE_GENERATOR in Cache\n";
+    return false;
+  }
+  const char* extraGenName =
+    this->State->GetInitializedCacheValue("CMAKE_EXTRA_GENERATOR");
+  std::string fullName =
+    cmExternalMakefileProjectGenerator::CreateFullGeneratorName(
+      genName, extraGenName ? extraGenName : "");
+
+  std::unique_ptr<cmGlobalGenerator> gen(
+    this->CreateGlobalGenerator(fullName));
+  if (!gen.get()) {
+    std::cerr << "Error: could create CMAKE_GENERATOR \"" << fullName
+              << "\"\n";
+    return false;
+  }
+
+  const char* cachedProjectName =
+    this->State->GetCacheEntryValue("CMAKE_PROJECT_NAME");
+  if (!cachedProjectName) {
+    std::cerr << "Error: could not find CMAKE_PROJECT_NAME in Cache\n";
+    return false;
+  }
+
+  return gen->Open(dir, cachedProjectName, dryRun);
+}
+
 void cmake::WatchUnusedCli(const std::string& var)
 {
 #ifdef CMAKE_BUILD_WITH_CMAKE

+ 3 - 0
Source/cmake.h

@@ -401,6 +401,9 @@ public:
             const std::string& config,
             const std::vector<std::string>& nativeOptions, bool clean);
 
+  ///! run the --open option
+  bool Open(const std::string& dir, bool dryRun);
+
   void UnwatchUnusedCli(const std::string& var);
   void WatchUnusedCli(const std::string& var);
 

+ 43 - 0
Source/cmakemain.cxx

@@ -66,6 +66,7 @@ static const char* cmDocumentationOptions[][2] = {
   { "-E", "CMake command mode." },
   { "-L[A][H]", "List non-advanced cached variables." },
   { "--build <dir>", "Build a CMake-generated project binary tree." },
+  { "--open <dir>", "Open generated project in the associated application." },
   { "-N", "View mode only." },
   { "-P <file>", "Process script mode." },
   { "--find-package", "Run in pkg-config like mode." },
@@ -100,6 +101,7 @@ static int do_command(int ac, char const* const* av)
 
 int do_cmake(int ac, char const* const* av);
 static int do_build(int ac, char const* const* av);
+static int do_open(int ac, char const* const* av);
 
 static cmMakefile* cmakemainGetMakefile(void* clientdata)
 {
@@ -186,6 +188,9 @@ int main(int ac, char const* const* av)
     if (strcmp(av[1], "--build") == 0) {
       return do_build(ac, av);
     }
+    if (strcmp(av[1], "--open") == 0) {
+      return do_open(ac, av);
+    }
     if (strcmp(av[1], "-E") == 0) {
       return do_command(ac, av);
     }
@@ -423,3 +428,41 @@ static int do_build(int ac, char const* const* av)
   return cm.Build(dir, target, config, nativeOptions, clean);
 #endif
 }
+
+static int do_open(int ac, char const* const* av)
+{
+#ifndef CMAKE_BUILD_WITH_CMAKE
+  std::cerr << "This cmake does not support --open\n";
+  return -1;
+#else
+  std::string dir;
+
+  enum Doing
+  {
+    DoingNone,
+    DoingDir,
+  };
+  Doing doing = DoingDir;
+  for (int i = 2; i < ac; ++i) {
+    switch (doing) {
+      case DoingDir:
+        dir = cmSystemTools::CollapseFullPath(av[i]);
+        doing = DoingNone;
+        break;
+      default:
+        std::cerr << "Unknown argument " << av[i] << std::endl;
+        dir.clear();
+        break;
+    }
+  }
+  if (dir.empty()) {
+    std::cerr << "Usage: cmake --open <dir>\n";
+    return 1;
+  }
+
+  cmake cm(cmake::RoleInternal);
+  cmSystemTools::SetMessageCallback(cmakemainMessageCallback, &cm);
+  cm.SetProgressCallback(cmakemainProgressCallback, &cm);
+  return cm.Open(dir, false) ? 0 : 1;
+#endif
+}