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); } }
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; }
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; }
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; }
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(); }
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; }
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); } }
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); } } }
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"); } } }
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(); }