示例#1
0
void SBTarget::resolveVCProjectDependecies(VCProject* proj, std::multimap<SBTarget*, VCProject*>& vcProjects)
{
  // Get the VCProject's platforms
  StringSet platforms;
  proj->getPlatforms(platforms);
 
  // Iterate over the target's dependencies
  for (auto dep : m_dependencies) {
    // Find all VCProjects generated from the SBTarget
    auto possibleDeps = vcProjects.equal_range(dep);

    // Look for the best-matching VCProject
    // BIG ASSUMPTION: Projects will have distinct platform sets, so only one match exists
    VCProject* match = NULL;
    for (auto it = possibleDeps.first; it != possibleDeps.second; ++it) {
      VCProject* depVCProject = it->second;
      StringSet depPlatforms;
      depVCProject->getPlatforms(depPlatforms);

      if (isSubset(platforms, depPlatforms)) {
        match = depVCProject;
        break;
      }
    }

    sbAssert(match);
    proj->addProjectReference(match);
  }
}
示例#2
0
VCProject* SBNativeTarget::constructVCProject(VSTemplateProject* projTemplate)
{
  VCProject* proj = SBTarget::constructVCProject(projTemplate);
  String vsProjDir = sb_dirname(proj->getPath());

  // Write variables file for App targets
  for (auto bs : m_buildSettings) {
    if (getProductType() == TargetApplication || getProductType() == TargetBundle) {
      String configName = bs.first;
      BuildSettings* configBS = bs.second;

      // Figure out where the file should go
      String varsFilePath = joinPaths(vsProjDir, getName() + "-" + configName + "-xcvars.txt");

      // Open a file stream to write to
      OFStream varsOut;
      openOutputFileStream(varsOut, varsFilePath);

      // Write the build settings out
      XCConfigPrinter varsPrinter(varsOut);
      configBS->print(varsPrinter);

      // Add the variables file to the project
      VCProjectItem* xcvarsFile = addRelativeFilePathToVS("Text", varsFilePath, "Xcode Variable Files", *proj, *configBS);

      // Mark the file as non-deployable
      xcvarsFile->setDefinition("DeploymentContent", "false");
    }
  }

  return proj;
}
示例#3
0
VCProject* SBTarget::constructVCProject(VSTemplateProject* projTemplate)
{
  // Create the project
  VCProject* proj = new VCProject(projTemplate);

  // Get path to WinObjC SDK
  const BuildSettings& projBS = m_parentProject.getBuildSettings();
  String useRelativeSdkPath = projBS.getValue("VSIMPORTER_RELATIVE_SDK_PATH");
  String sdkDir = projBS.getValue("WINOBJC_SDK_ROOT");

  // Try to create a relative path to the SDK, if requested
  if (strToUpper(useRelativeSdkPath) == "YES") {
    String projectDir = sb_dirname(projTemplate->getPath());
    sdkDir = getRelativePath(projectDir, sdkDir);
  }
  proj->addGlobalProperty("WINOBJC_SDK_ROOT", platformPath(sdkDir), "'$(WINOBJC_SDK_ROOT)' == ''");

  // Set configuration properties
  for (auto configBS : m_buildSettings) {
    VCProjectConfiguration *projConfig = proj->addConfiguration(configBS.first);
    String productName = configBS.second->getValue("PRODUCT_NAME");
    if (!productName.empty()) {
      projConfig->setProperty("TargetName", productName);
    }
  }

  // Write files associated with each build phase
  SBBuildPhaseList::const_iterator phaseIt = m_buildPhases.begin();
  for (; phaseIt != m_buildPhases.end(); ++phaseIt)
    (*phaseIt)->writeVCProjectFiles(*proj);

  return proj;
}
示例#4
0
VCProject* SBTarget::constructVCProject(VSTemplateProject* projTemplate)
{
  // Create the project
  VCProject* proj = new VCProject(projTemplate);

  // Set global properties on the project
  const BuildSettings& projBS = m_parentProject.getBuildSettings();
  String sdkDir = projBS.getValue("WINOBJC_SDK_ROOT");
  proj->setGlobalProperty("WINOBJC_SDK_ROOT", sdkDir);

  // Set configuration properties
  for (auto configBS : m_buildSettings) {
    VCProjectConfiguration *projConfig = proj->addConfiguration(configBS.first);
    String execName = configBS.second->getValue("EXECUTABLE_NAME");
    if (getProductType() == TargetStaticLib)
      execName = sb_fname(execName);
    if (!execName.empty())
      projConfig->setProperty("TargetName", execName);
  }

  // Write files associated with each build phase
  SBBuildPhaseList::const_iterator phaseIt = m_buildPhases.begin();
  for (; phaseIt != m_buildPhases.end(); ++phaseIt)
    (*phaseIt)->writeVCProjectFiles(*proj);

  return proj;
}
示例#5
0
void SBWorkspace::generateFiles(bool genProjectionsProj)
{
  // Detect and warn about about any collisions
  detectProjectCollisions();

  // Get a set of all configurations appearing in all projects
  StringSet slnConfigs;
  for (auto project : m_openProjects) {
    const StringSet& configs = project.second->getSelectedConfigurations();
    slnConfigs.insert(configs.begin(), configs.end());
  }

  // Create a solution
  BuildSettings globalBS(NULL);
  String outputFormat = globalBS.getValue("VSIMPORTER_OUTPUT_FORMAT");
  String solutionPath = sb_fname(getPath()) + "-" + outputFormat + ".sln";
  VSSolution* sln = new VSSolution(solutionPath);

  // Register all configurations with the solution
  for (auto configName : slnConfigs) {
    sln->addConfiguration(configName);
  }

  // Construct VS Projects
  std::multimap<SBTarget*, VCProject*> vcProjects;
  for (auto project : m_openProjects) {
    project.second->constructVCProjects(*sln, slnConfigs, vcProjects);
  }

  // Construct a projections project, if required
  VCProject* glueProject = nullptr;
  if (genProjectionsProj) {
    glueProject = generateGlueProject();
    sln->addProject(glueProject);
  }

  // Resolve dependencies
  for (auto proj : vcProjects) {
    proj.first->resolveVCProjectDependecies(proj.second, vcProjects);

    // Add a dependency on all static/framework target projects
    if (glueProject && proj.first->getProductType() == TargetStaticLib) {
      glueProject->addProjectReference(proj.second);
    }
  }

  // Write solution/projects to disk
  sbValidateWithTelemetry(!vcProjects.empty(), "No valid targets to import.");
  sln->write();
}
示例#6
0
VCProject* SBWorkspace::generateGlueProject() const
{
  // Get a set of all configurations appearing in all projects
  StringSet slnConfigs;
  for (auto project : m_openProjects) {
    const StringSet& configs = project.second->getSelectedConfigurations();
    slnConfigs.insert(configs.begin(), configs.end());
  }

  // Get the template
  VSTemplate* vstemplate = VSTemplate::getTemplate("WinRT");
  sbAssertWithTelemetry(vstemplate, "Failed to get WinRT VS template");

  // Set up basis template parameters
  string projectName = getName() + "WinRT";
  VSTemplateParameters templateParams;
  templateParams.setProjectName(projectName);

  // Expand the template and get the template project
  vstemplate->expand(sb_dirname(getPath()), templateParams);
  const VSTemplateProjectVec& projTemplates = vstemplate->getProjects();
  sbAssertWithTelemetry(projTemplates.size() == 1, "Unexpected WinRT template size");

  // Create the glue project and add it to the solution
  VCProject* glueProject = new VCProject(projTemplates.front());

  // Get path to WinObjC SDK
  BuildSettings globalBS(NULL);
  String useRelativeSdkPath = globalBS.getValue("VSIMPORTER_RELATIVE_SDK_PATH");
  String sdkDir = globalBS.getValue("WINOBJC_SDK_ROOT");

  // Try to create a relative path to the SDK, if requested
  if (strToUpper(useRelativeSdkPath) == "YES") {
    String projectDir = sb_dirname(projTemplates.front()->getPath());
    sdkDir = getRelativePath(projectDir, sdkDir);
  }
  glueProject->addGlobalProperty("WINOBJC_SDK_ROOT", platformPath(sdkDir), "'$(WINOBJC_SDK_ROOT)' == ''");

  // Set configuration properties
  for (auto configName : slnConfigs) {
    VCProjectConfiguration *projConfig = glueProject->addConfiguration(configName);
    projConfig->setProperty("TargetName", getName());
  }

  // Set RootNamespace
  glueProject->addGlobalProperty("RootNamespace", getName());

  return glueProject;
}
示例#7
0
void addBuildFileToVS(const PBXBuildFile* buildFile, VCProject& proj, const BuildSettings& bs, const VCItemHint* itemHint)
{
  const String& compilerFlags = buildFile->getCompilerFlags();
  int attribs = buildFile->getAttributes();
  const PBXFile* file = buildFile->getFile();
  if (!file)
    return;

  VCProjectItem* item = addFileToVSInternal(file, proj, bs, false, itemHint);

  // If the filetype doesn't match the file extension, specify the actual type
  String filePath = file->getFullPath();
  String fileType = file->getFileType();
  String inferredType = PBXFile::getFileType(filePath);
  String compileAs = getVSCompileAsType(fileType);
  if (item && !compileAs.empty() &&
       (fileType != inferredType || fileType == "sourcecode.c.c" || fileType == "sourcecode.cpp.cpp")) {
    item->setDefinition("CompileAs", compileAs);
  }

  // Record file compiler flags
  if (item && !compilerFlags.empty()) {
    String fixedFlags = "$(AdditionalOptions) " + compilerFlags;
    String xcProjectDir = bs.getValue("PROJECT_DIR");
    String vsProjectDir = sb_dirname(proj.getPath());
    processClangFlags(fixedFlags, xcProjectDir, vsProjectDir);
    item->setDefinition("AdditionalOptions", fixedFlags);
  }

  // Mark public headers
  if ((attribs & ATTR_PUBLIC) &&
      (fileType == "sourcecode.c.h" || fileType == "sourcecode.cpp.h")) {
    item->setDefinition("PublicHeader", "true");
  }
}
void SBResourcesBuildPhase::writeVCProjectFiles(VCProject& proj) const
{
  TargetProductType productType = m_parentTarget.getProductType();
  if (productType != TargetApplication && productType != TargetBundle) {
    return;
  }

  // Process build files
  const BuildSettings& projBS = m_parentTarget.getProject().getBuildSettings();
  const BuildFileList& buildFiles = m_phase->getBuildFileList();
  sbAssert(buildFiles.size() == m_buildFileTargets.size());
  for (size_t i = 0; i < buildFiles.size(); i++) {
    // Construct a path for Bundle build products, relative to the SolutionDir,
    // instead of using the Xcode path
    String pathOverride;
    if (m_buildFileTargets[i]) {
      String productFileName = sb_basename(buildFiles[i]->getFile()->getFullPath());
      String productFileType = buildFiles[i]->getFile()->getFileType();
      if (productFileType == "wrapper.cfbundle") {
        pathOverride = "$(SolutionDir)$(Configuration)\\" + productFileName;
      } else {
        SBLog::warning() << "Unexpected build product in ResourceBuildPhase: " << productFileName << std::endl;
      }
    }

    VCItemHint itemHint = { "SBResourceCopy" , pathOverride };
    addBuildFileToVS(buildFiles[i], proj, projBS, &itemHint);
  }

  // Process all Info.plist files
  std::map<std::string, VCProjectItem*> infoPlistMap;
  for (auto bs : m_parentTarget.getBuildSettings()) {
    VCProjectConfiguration* config = proj.addConfiguration(bs.first);

    // Exclude all plist from building, by default
    config->setItemDefinition("SBInfoPlistCopy", "ExcludedFromBuild", "true");
    
    // Get absolute path to plist
    String plistPath = bs.second->getValue("INFOPLIST_FILE");
    plistPath = m_parentTarget.makeAbsolutePath(plistPath);

    // Add plist file to project (only once)
    if (infoPlistMap.find(plistPath) == infoPlistMap.end()) {
      infoPlistMap[plistPath] = addRelativeFilePathToVS("SBInfoPlistCopy", plistPath, "", proj, *bs.second);
    }

    // Un-exclude building plist for configuration
    String condition = "'$(Configuration)'=='" + bs.first + "'";
    infoPlistMap[plistPath]->setDefinition("ExcludedFromBuild", "false", condition);

    // Specify which variables files to use
    String varsFile = m_parentTarget.getName() + "-" + bs.first + "-xcvars.txt";
    infoPlistMap[plistPath]->setDefinition("VariableFile", varsFile, condition);
  }
}
示例#9
0
VCProject* SBWorkspace::generateGlueProject(bool packageable) const {
    // Get a set of all configurations appearing in all projects
    StringSet slnConfigs;
    for (auto project : m_openProjects) {
        const StringSet& configs = project.second->getSelectedConfigurations();
        slnConfigs.insert(configs.begin(), configs.end());
    }

    // Get the template
    VSTemplate* vstemplate = VSTemplate::getTemplate("WinRT");
    sbAssertWithTelemetry(vstemplate, "Failed to get WinRT VS template");

    // Set up basis template parameters
    string projectName = getName() + "WinRT";
    VSTemplateParameters templateParams;
    templateParams.setProjectName(projectName);
    templateParams.setIsPackageable(packageable);

    // Expand the template and get the template project
    vstemplate->expand(sb_dirname(getPath()), templateParams);
    const VSTemplateProjectVec& projTemplates = vstemplate->getProjects();
    sbAssertWithTelemetry(projTemplates.size() == 1, "Unexpected WinRT template size");

    // Create the glue project and add it to the solution
    VCProject* glueProject = new VCProject(projTemplates.front());

    // Set configuration properties
    for (auto configName : slnConfigs) {
        VCProjectConfiguration* projConfig = glueProject->addConfiguration(configName);
        projConfig->setProperty("TargetName", getName());
    }

    // Set RootNamespace
    glueProject->addGlobalProperty("RootNamespace", getName());

    return glueProject;
}
void SBFrameworksBuildPhase::writeVCProjectFiles(VCProject& proj) const
{
  // We don't support linking with frameworks when building bundles
  TargetProductType productType = m_parentTarget.getProductType();
  if (productType == TargetBundle) {
    if (!m_phase->getBuildFileList().empty()) {
      SBLog::warning() << "Ignoring all frameworkss in \"" << m_parentTarget.getName() << "\" bundle target." << std::endl;
    }
    return;
  }

  String linkTarget;
  if (productType == TargetApplication)
    linkTarget = "Link";
  else if (productType == TargetStaticLib)
    linkTarget = "Lib";

  // Get paths to all the build files (frameworks)
  StringVec buildFilePaths;
  if (m_phase) {
    const BuildFileList& buildFiles = m_phase->getBuildFileList();
    sbAssert(buildFiles.size() == m_buildFileTargets.size());
    for (size_t i = 0; i < buildFiles.size(); i++) {
      const PBXFile* file = buildFiles[i]->getFile();
      // Ignore any frameworks build from source (they will be added as project references)
      if (file && !m_buildFileTargets[i])
        buildFilePaths.push_back(file->getFullPath());
    }
  }

  for (auto bs : m_parentTarget.getBuildSettings()) {
    VCProjectConfiguration* config = proj.addConfiguration(bs.first);

    // Extrace libs/frameworks from OTHER_LDFLAGS
    StringVec buildFilePaths(buildFilePaths);
    processLDFlags(bs.second->getValue("OTHER_LDFLAGS"), buildFilePaths);

    // Construct a list of libraries to link against
    StringSet linkedLibs;
    linkedLibs.insert("%(AdditionalDependencies)");
    for (auto filePath : buildFilePaths) {
      if (productType == TargetStaticLib && !strEndsWith(filePath, ".a"))
        continue;

      String winLibName = sb_fname(sb_basename(filePath)) + ".lib";

      // If the library is blocked then add the replacement library to our additional dependencies 
      auto it = s_blockedLibraries.find(winLibName);
      while (it != s_blockedLibraries.end())
      {
          // get the replacement library.
          winLibName = it->second;

          // follow any transitive replacement.
          it = s_blockedLibraries.find(winLibName);
      }

      if (!winLibName.empty())
      {
          linkedLibs.insert(winLibName);
      }
    }

    // AdditionalDependencies
    String additionalDeps = joinStrings(linkedLibs, ";");
    if (!additionalDeps.empty()) {
      config->setItemDefinition(linkTarget, "AdditionalDependencies", additionalDeps);
    }
  }
}
示例#11
0
void SBSourcesBuildPhase::writeVCProjectFiles(VCProject& proj) const
{
  // We don't support source compilation when building bundles
  TargetProductType productType = m_parentTarget.getProductType();
  if (productType == TargetBundle)
  {
    if (!m_phase->getBuildFileList().empty()) {
      SBLog::warning() << "Ignoring all source files in \"" << m_parentTarget.getName() << "\" bundle target." << std::endl;
    }
    return;
  }

  SBBuildPhase::writeVSFileDescriptions(proj, "Text");

  String xcProjectDir = m_parentTarget.getProject().getProjectDir();
  String vsProjectDir = sb_dirname(proj.getPath());
  StringSet prefixHeaders;
  for (auto bs : m_parentTarget.getBuildSettings()) {
    VCProjectConfiguration* config = proj.addConfiguration(bs.first);

    // Prefix header (recalculate relative path)
    String prefixHeader = bs.second->getValue("GCC_PREFIX_HEADER");
    if (!prefixHeader.empty()) {
      String absHeaderPath = m_parentTarget.makeAbsolutePath(prefixHeader);
      String relHeaderPath = m_parentTarget.makeRelativePath(prefixHeader, vsProjectDir);;
      relHeaderPath = winPath(relHeaderPath);
      config->setItemDefinition("ClangCompile", "PrefixHeader", relHeaderPath);

      // Add plist file to project (only once)
      if (prefixHeaders.find(absHeaderPath) == prefixHeaders.end()) {
        addRelativeFilePathToVS("ClInclude", absHeaderPath, "", proj, *bs.second);
        prefixHeaders.insert(absHeaderPath);
      }
    }

    // Preprocessor definitions
    StringVec preprocessorTokens;
    bs.second->getValue("GCC_PREPROCESSOR_DEFINITIONS", preprocessorTokens);
    String preprocessorDefs = joinStrings(preprocessorTokens, ";");
    if (!preprocessorDefs.empty()) {
      config->setItemDefinition("ClangCompile", "PreprocessorDefinitions", preprocessorDefs);
    }

    // Optimization level
    String optimizationLevel = bs.second->getValue("GCC_OPTIMIZATION_LEVEL");
    if (!optimizationLevel.empty()) {
      String vsOptimizationLevel;
      if (optimizationLevel == "s") {
        vsOptimizationLevel = "MinSpace";
      } else if (optimizationLevel == "0") {
        vsOptimizationLevel = "Disabled";
      } else {
        vsOptimizationLevel = "MaxSpeed";
      }
      config->setItemDefinition("ClangCompile", "OptimizationLevel", vsOptimizationLevel);
    }

    // ARC
    String enableARC = bs.second->getValue("CLANG_ENABLE_OBJC_ARC");
    if (enableARC == "YES") {
      config->setItemDefinition("ClangCompile", "ObjectiveCARC", "true");
    }

    // Modules
    String enableModules = bs.second->getValue("CLANG_ENABLE_MODULES");
    if (enableModules == "YES") {
        config->setItemDefinition("ClangCompile", "ObjectiveCModules", "true");
    }

    // Header search paths (make them relative)
    StringVec includePaths;
    bs.second->getValue("HEADER_SEARCH_PATHS", includePaths);
    for (auto &cur : includePaths) {
      cur = m_parentTarget.makeRelativePath(cur, vsProjectDir);
      cur = winPath(cur);
    }
    includePaths.insert(includePaths.begin(), "$(SolutionPublicHeadersDir)");
    config->setItemDefinition("ClangCompile", "IncludePaths", joinStrings(includePaths, ";"));

    // User header search paths (make them relative)
    StringVec userIncludePaths;
    bs.second->getValue("USER_HEADER_SEARCH_PATHS", userIncludePaths);
    for (auto &cur : userIncludePaths) {
      cur = m_parentTarget.makeRelativePath(cur, vsProjectDir);
      cur = winPath(cur);
    }
    if (!userIncludePaths.empty()) {
      config->setItemDefinition("ClangCompile", "UserIncludePaths", joinStrings(userIncludePaths, ";"));
    }

    // Exclude search path subdirectories
    StringVec excludeSubDirectories;
    bs.second->getValue("EXCLUDED_RECURSIVE_SEARCH_PATH_SUBDIRECTORIES", excludeSubDirectories);
    if (!excludeSubDirectories.empty()) {
        config->setItemDefinition("ClangCompile", "ExcludedSearchPathSubdirectories", joinStrings(excludeSubDirectories, ";"));
    }

    // Header map
    if (bs.second->getValue("USE_HEADERMAP") == "YES") {
      if (bs.second->getValue("ALWAYS_SEARCH_USER_PATHS") == "YES") {
        config->setItemDefinition("ClangCompile", "HeaderMap", "Combined");
      } else if (bs.second->getValue("HEADERMAP_INCLUDES_PROJECT_HEADERS") == "YES") {
        config->setItemDefinition("ClangCompile", "HeaderMap", "Project");
      }
    }

    // Other C flags
    String otherCFlags = bs.second->getValue("OTHER_CFLAGS");
    processClangFlags(otherCFlags, xcProjectDir, vsProjectDir);
    if (!otherCFlags.empty()) {
      config->setItemDefinition("ClangCompile", "OtherCFlags", otherCFlags);
    }

    // Other C++ flags
    String otherCPlusPlusFlags = bs.second->getValue("OTHER_CPLUSPLUSFLAGS");
    processClangFlags(otherCPlusPlusFlags, xcProjectDir, vsProjectDir);
    if (!otherCPlusPlusFlags.empty()) {
      config->setItemDefinition("ClangCompile", "OtherCPlusPlusFlags", otherCPlusPlusFlags);
    }

    // CRT
    String configNameUpper = strToUpper(bs.first);
    if (configNameUpper.find("DEBUG") != String::npos) {
      config->setItemDefinition("ClangCompile", "RuntimeLibrary", "MultiThreadedDebugDLL");
    }
  }
}
示例#12
0
void SBWorkspace::generateFiles(bool genProjectionsProj, bool genPackagingProj) {
    // Detect and warn about about any collisions
    detectProjectCollisions();

    // Don't generate packaging project if the solution only contains an app
    bool solutionContainsPackagebleProject = false;
    for (auto project : m_openProjects) {
        solutionContainsPackagebleProject = solutionContainsPackagebleProject || project.second->containsPackagebleProject();
    }
    genPackagingProj = genPackagingProj && solutionContainsPackagebleProject;

    // Get a set of all configurations appearing in all projects
    StringSet slnConfigs;
    for (auto project : m_openProjects) {
        const StringSet& configs = project.second->getSelectedConfigurations();
        slnConfigs.insert(configs.begin(), configs.end());
    }

    // Create a solution
    BuildSettings globalBS(NULL);
    String outputFormat = globalBS.getValue("VSIMPORTER_OUTPUT_FORMAT");
    String solutionPath = sb_fname(getPath()) + "-" + outputFormat + ".sln";
    VSSolution* sln = new VSSolution(solutionPath);

    // Register all configurations with the solution
    for (auto configName : slnConfigs) {
        sln->addConfiguration(configName);
    }

    // Construct VS Projects
    std::multimap<SBTarget*, VCProject*> vcProjects;
    for (auto project : m_openProjects) {
        project.second->constructVCProjects(*sln, slnConfigs, vcProjects, genPackagingProj);
    }

    VCProject* glueProject = nullptr;
    if (genProjectionsProj) {
        // Construct a WinRT projections project
        glueProject = generateGlueProject(genPackagingProj);
        sln->addProject(glueProject);
    }

    VCProject* packageProject = nullptr;
    if (genPackagingProj) {
        // Construct a packaging project
        packageProject = generatePackageProject();
        packageProject->addProjectReference(glueProject);
        sln->addProject(packageProject);
        sln->addPlatform("AnyCPU");

        // Copy nuget.config into the solution directory
        String templatesDir = globalBS.getValue("VSIMPORTER_TEMPLATES_DIR");
        String nugetConfigSource = joinPaths(templatesDir, "nuget.config");
        String nugetConfigDest = joinPaths(sb_dirname(getPath()), "nuget.config");
        CopyFile(nugetConfigSource.c_str(), nugetConfigDest.c_str(), false);
    }

    // Resolve dependencies
    for (auto proj : vcProjects) {
        proj.first->resolveVCProjectDependecies(proj.second, vcProjects);

        TargetProductType productType = proj.first->getProductType();
        // Add a dependency on all static/framework target projects
        if (glueProject && productType == TargetStaticLib) {
            glueProject->addProjectReference(proj.second);
        }

        // Make the packaging project dependent on all framework components
        if (packageProject && productType != TargetProductUnknown && productType != TargetApplication) {
            packageProject->addProjectReference(proj.second);
        }
    }

    // Write solution/projects to disk
    sbValidateWithTelemetry(!vcProjects.empty(), "No valid targets to import.");
    sln->write();
}