--!A cross-platform build utility based on Lua -- -- Licensed under the Apache License, Version 2.0 (the "License"); -- you may not use this file except in compliance with the License. -- You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. -- -- Copyright (C) 2015-present, Xmake Open Source Community. -- -- @author JXMaster -- @file get_xcode_info.lua -- -- imports import("core.base.option") import("core.project.config") import("core.project.project") function _get_project_modes() local ret_modes = {} local modes = option.get("modes") if modes then if not modes:find("\"") then modes = modes:gsub(",", path.envsep()) end for _, mode in ipairs(path.splitenv(modes)) do table.insert(ret_modes, mode:trim()) end else ret_modes = project.modes() end return ret_modes end -- tranlate path function _translate_path(filepath, outputdir) filepath = path.translate(filepath) if filepath == "" then return "" end if path.is_absolute(filepath) then if filepath:startswith(project.directory()) then return path.relative(filepath, outputdir) end return filepath else return path.relative(path.absolute(filepath), outputdir) end end function _add_PBXFileReference(xcinfo, file_info) local obj = {} obj.explicitFileType = file_info.explicitFileType obj.lastKnownFileType = file_info.lastKnownFileType obj.name = file_info.name obj.path = file_info.path obj.sourceTree = file_info.sourceTree obj.includeInIndex = file_info.includeInIndex local uuid = xcinfo:gen_uuid() xcinfo.sections.PBXFileReference = xcinfo.sections.PBXFileReference or {} xcinfo.sections.PBXFileReference[uuid] = obj return uuid end function _add_PBXGroup(xcinfo, name, sourceTree) local obj = {} obj.name = name obj.sourceTree = sourceTree obj.children = {} local uuid = xcinfo:gen_uuid() xcinfo.sections.PBXGroup = xcinfo.sections.PBXGroup or {} xcinfo.sections.PBXGroup[uuid] = obj return uuid end -- target may be nil. function _add_XCBuildConfiguration(xcinfo, mode, target) local obj = {} obj.name = mode obj.buildSettings = {} -- This allows scripts to read/write files that are not in the Input/Output list of -- the phase. obj.buildSettings.ENABLE_USER_SCRIPT_SANDBOXING = false if target then local rules = target:rules() local is_app_bundle = false if rules["xcode.application"] ~= nil then is_app_bundle = true end if is_app_bundle then obj.buildSettings.GENERATE_INFOPLIST_FILE = true obj.buildSettings.PRODUCT_NAME = "\"$(TARGET_NAME)\"" end else -- This is for root project. obj.buildSettings.SUPPORTED_PLATFORMS = "\"macosx iphoneos\"" obj.buildSettings.SDKROOT = "macosx" obj.buildSettings["\"SDKROOT[sdk=macosx*]\""] = "macosx" obj.buildSettings["\"SDKROOT[sdk=iphoneos]\""] = "iphoneos" end local uuid = xcinfo:gen_uuid() xcinfo.sections.XCBuildConfiguration = xcinfo.sections.XCBuildConfiguration or {} xcinfo.sections.XCBuildConfiguration[uuid] = obj return uuid end -- target may be nil. function _add_XCConfigurationList(xcinfo, target) local obj = {} local modes = _get_project_modes() obj.buildConfigurations = {} for _, mode in ipairs(modes) do table.insert(obj.buildConfigurations, _add_XCBuildConfiguration(xcinfo, mode, target)) end local uuid = xcinfo:gen_uuid() xcinfo.sections.XCConfigurationList = xcinfo.sections.XCConfigurationList or {} xcinfo.sections.XCConfigurationList[uuid] = obj return uuid end function _add_PBXShellScriptBuildPhase(xcinfo, target) local obj = {} local shellscript = {} local projectdir = _translate_path(os.projectdir(), xcinfo.project_dir) if projectdir ~= "." then table.insert(shellscript, "cd " .. projectdir) end table.insert(shellscript, [[ export XMAKE_PROGRAM_FILE=\"]] .. os.programfile() .. [[\" export XMAKE_PROGRAM_DIR=\"]] .. os.programdir() .. [[\" export XMAKE_COLORTERM=\"nocolor\" # Running xmake scripts. ${XMAKE_PROGRAM_FILE} f -y -m ${CONFIGURATION} -p ${PLATFORM_NAME} -a ${NATIVE_ARCH} -o ${BUILD_DIR} || exit -1 ${XMAKE_PROGRAM_FILE} build ${TARGET_NAME} || exit -1 # Copy files to build path. XMAKE_BUILD_DIR=${BUILD_DIR}/${PLATFORM_NAME}/${NATIVE_ARCH}/${CONFIGURATION} if test -e ${CONFIGURATION_BUILD_DIR}; then rm -r ${CONFIGURATION_BUILD_DIR}/* fi if test -e ${XMAKE_BUILD_DIR}; then cp -r ${XMAKE_BUILD_DIR}/* ${CONFIGURATION_BUILD_DIR} fi ]]) obj.shellScript = table.concat(shellscript, "\n") local uuid = xcinfo:gen_uuid() xcinfo.sections.PBXShellScriptBuildPhase = xcinfo.sections.PBXShellScriptBuildPhase or {} xcinfo.sections.PBXShellScriptBuildPhase[uuid] = obj return uuid end function _add_target_files(xcinfo, group, files) for _, v in table.orderpairs(files) do local file_info = {} file_info.name = path.filename(v) file_info.path = path.join(project.directory(), v) file_info.sourceTree = "\"\"" local ext = path.extension(v) if ext == ".h" then file_info.lastKnownFileType = "sourcecode.c.h" elseif ext == ".c" then file_info.lastKnownFileType = "sourcecode.c.c" elseif ext == ".m" then file_info.lastKnownFileType = "sourcecode.c.objc" elseif ext == ".hpp" then file_info.lastKnownFileType = "sourcecode.cpp.h" elseif ext == ".cpp" then file_info.lastKnownFileType = "sourcecode.cpp.cpp" elseif ext == ".mm" then file_info.lastKnownFileType = "sourcecode.cpp.objcpp" elseif ext == ".plist" then file_info.lastKnownFileType = "text.plist" end table.insert(group.children, _add_PBXFileReference(xcinfo, file_info)) end end function _add_PBXNativeTarget(xcinfo, target_name, target) local obj = {} obj.name = target_name obj.productName = target_name local rules = target:rules() local is_app_bundle = false if rules["xcode.application"] ~= nil then is_app_bundle = true end -- Add product file. local product_file = {} if target:kind() == "static" then product_file.explicitFileType = "com.apple.product-type.library.static" elseif target:kind() == "shared" then product_file.explicitFileType = "com.apple.product-type.library.dynamic" elseif target:kind() == "binary" then if is_app_bundle then product_file.explicitFileType = "wrapper.application" else product_file.explicitFileType = "com.apple.product-type.tool" end end if is_app_bundle then product_file.name = target:filename() .. ".app" else product_file.name = target:filename() end product_file.path = product_file.name product_file.sourceTree = "BUILT_PRODUCTS_DIR" product_file.includeInIndex = 0 obj.productReference = _add_PBXFileReference(xcinfo, product_file) if is_app_bundle then obj.productType = "com.apple.product-type.application" else obj.productType = product_file.explicitFileType end -- Add product file to product group. local project_obj = xcinfo.sections.PBXProject[xcinfo.root_object] local product_group = xcinfo.sections.PBXGroup[project_obj.productRefGroup] table.insert(product_group.children, obj.productReference) -- Add files. local headerfiles = target:headerfiles() local sourcefiles = target:sourcefiles() obj.headerFileGroup = _add_PBXGroup(xcinfo, "Header Files", "") obj.sourceFileGroup = _add_PBXGroup(xcinfo, "Source Files", "") local header_group = xcinfo.sections.PBXGroup[obj.headerFileGroup] local source_group = xcinfo.sections.PBXGroup[obj.sourceFileGroup] _add_target_files(xcinfo, header_group, headerfiles) _add_target_files(xcinfo, source_group, sourcefiles) obj.mainGroup = _add_PBXGroup(xcinfo, obj.productName, "") local main_group = xcinfo.sections.PBXGroup[obj.mainGroup] table.insert(main_group.children, obj.headerFileGroup) table.insert(main_group.children, obj.sourceFileGroup) -- Add configuration list for target. obj.buildConfigurationList = _add_XCConfigurationList(xcinfo, target) -- Add build phases. obj.buildPhases = {} table.insert(obj.buildPhases, _add_PBXShellScriptBuildPhase(xcinfo, target)) local uuid = xcinfo:gen_uuid() xcinfo.sections.PBXNativeTarget = xcinfo.sections.PBXNativeTarget or {} xcinfo.sections.PBXNativeTarget[uuid] = obj return uuid end function main(outputdir) local xcinfo = {} xcinfo.project_dir = outputdir xcinfo.project_bundle = (project.name() or path.filename(project.directory())) .. ".xcodeproj" xcinfo.project_dir_uuid = string.upper(hash.strhash32(xcinfo.project_dir)) xcinfo.project_bundle_uuid = string.upper(hash.strhash32(xcinfo.project_bundle)) xcinfo.uuid_counter = 0 xcinfo.gen_uuid = function(self) local counter = self.uuid_counter self.uuid_counter = self.uuid_counter + 1 return self.project_dir_uuid .. self.project_bundle_uuid .. string.format("%08X", counter) end -- Used to collect Xcode nodes. -- nodes are grouped by their types. For example, all PBXFileReference -- nodes will be in xcinfo.sections.PBXFileReference, indexed by their UUIDs. xcinfo.sections = {} -- Add project node local sections = xcinfo.sections sections.PBXProject = {} local project_uuid = xcinfo:gen_uuid() xcinfo.root_object = project_uuid sections.PBXProject[project_uuid] = {} local project_obj = sections.PBXProject[project_uuid] -- collect buildConfigurationList project_obj.buildConfigurationList = _add_XCConfigurationList(xcinfo) -- collect main group project_obj.mainGroup = _add_PBXGroup(xcinfo, nil, "") local main_group = xcinfo.sections.PBXGroup[project_obj.mainGroup] -- collect product ref group project_obj.productRefGroup = _add_PBXGroup(xcinfo, "Products", ""); -- collect targets. project_obj.targets = {} for target_name, target in table.orderpairs(project.targets()) do local target = _add_PBXNativeTarget(xcinfo, target_name, target) table.insert(project_obj.targets, target) local target_obj = xcinfo.sections.PBXNativeTarget[target] table.insert(main_group.children, target_obj.mainGroup) end table.insert(main_group.children, project_obj.productRefGroup) return xcinfo end