-- Copyright (C) 1997-2016 Sam Lantinga -- -- This software is provided 'as-is', without any express or implied -- warranty. In no event will the authors be held liable for any damages -- arising from the use of this software. -- -- Permission is granted to anyone to use this software for any purpose, -- including commercial applications, and to alter it and redistribute it -- freely. -- -- Meta-build system using premake created and maintained by -- Benjamin Henning --[[ premake4.lua This script sets up the entire premake system. It's responsible for executing all of the definition scripts for the SDL2 library and the entire test suite, or demos for the iOS platform. It handles each specific platform and uses the setup state to generate both the configuration header file needed to build SDL2 and the premake lua script to generate the target project files. ]] -- string utility functions dofile "util/sdl_string.lua" -- utility file wrapper for some useful functions dofile "util/sdl_file.lua" -- system for defining SDL projects dofile "util/sdl_projects.lua" -- offers a utility function for finding dependencies specifically on windows dofile "util/sdl_depends.lua" -- system for generating a *config.h file used to build the SDL2 library dofile "util/sdl_gen_config.lua" -- functions to handle complicated dependency checks using CMake-esque functions dofile "util/sdl_check_compile.lua" -- a list of dependency functions for the SDL2 project and any other projects dofile "util/sdl_dependency_checkers.lua" -- the following are various options for configuring the meta-build system newoption { trigger = "to", value = "path", description = "Set the base output directory for the generated and executed lua file." } newoption { trigger = "mingw", description = "Runs the premake generation script targeted to MinGW." } newoption { trigger = "cygwin", description = "Runs the premake generation script targeted to Cygwin." } newoption { trigger = "ios", description = "Runs the premake generation script targeted to iOS." } -- determine the localized destination path local baseLoc = "./" if _OPTIONS["to"] then baseLoc = _OPTIONS["to"]:gsub("\\", "/") end local deps = SDL_getDependencies() for _,v in ipairs(deps) do newoption { trigger = v:lower(), description = "Force on the dependency: " .. v } end -- clean action if _ACTION == "clean" then -- this is kept the way it is because premake's default method of cleaning the -- build tree is not very good standalone, whereas the following correctly -- cleans every build option print("Cleaning the build environment...") os.rmdir(baseLoc .. "/SDL2") os.rmdir(baseLoc .. "/SDL2main") os.rmdir(baseLoc .. "/SDL2test") os.rmdir(baseLoc .. "/tests") os.rmdir(baseLoc .. "/Demos") os.rmdir(baseLoc .. "/ipch") -- sometimes shows up os.remove(baseLoc .. "/SDL.sln") os.remove(baseLoc .. "/SDL.suo") os.remove(baseLoc .. "/SDL.v11.suo") os.remove(baseLoc .. "/SDL.sdf") os.remove(baseLoc .. "/SDL.ncb") os.remove(baseLoc .. "/SDL-gen.lua") os.remove(baseLoc .. "/SDL_config_premake.h") os.remove(baseLoc .. "/Makefile") os.rmdir(baseLoc .. "/SDL.xcworkspace") os.exit() end -- only run through standard execution if not in help mode if _OPTIONS["help"] == nil then -- load all of the project definitions local results = os.matchfiles("projects/**.lua") for _,dir in ipairs(results) do dofile(dir) end -- figure out which configuration template to use local premakeConfigHeader = baseLoc .. "/SDL_config_premake.h" -- minimal configuration is the default local premakeTemplateHeader = "./config/SDL_config_minimal.template.h" if SDL_getos() == "windows" or SDL_getos() == "mingw" then premakeTemplateHeader = "./config/SDL_config_windows.template.h" elseif SDL_getos() == "macosx" then premakeTemplateHeader = "./config/SDL_config_macosx.template.h" elseif SDL_getos() == "ios" then premakeTemplateHeader = "./config/SDL_config_iphoneos.template.h" elseif os.get() == "linux" then premakeTemplateHeader = "./config/SDL_config_linux.template.h" elseif SDL_getos() == "cygwin" then premakeTemplateHeader = "./config/SDL_config_cygwin.template.h" end local genFile = baseLoc .. "/SDL-gen.lua" local file = fileopen(genFile, "wt") print("Generating " .. genFile .. "...") -- begin generating the config header file startGeneration(premakeConfigHeader, premakeTemplateHeader) -- begin generating the actual premake script file:print(0, "-- Premake script generated by Simple DirectMedia Layer meta-build script") file:print(1, 'solution "SDL"') local platforms = { } local platformsIndexed = { } for n,p in pairs(projects) do if p.platforms and #p.platforms ~= 0 then for k,v in pairs(p.platforms) do platforms[v] = true end end end for n,v in pairs(platforms) do platformsIndexed[#platformsIndexed + 1] = n end file:print(2, implode(platformsIndexed, 'platforms {', '"', '"', ', ', '}')) file:print(2, 'configurations { "Debug", "Release" }') for n,p in pairs(projects) do if p.compat then local proj = {} if p.projectLocation ~= nil then proj.location = p.projectLocation .. "/" .. p.name else proj.location = p.name .. "/" end proj.includedirs = { path.getrelative(baseLoc, path.getdirectory(premakeConfigHeader)), path.getrelative(baseLoc, "../include") } proj.libdirs = { } proj.files = { } local links = { } local dbgCopyTable = { } local relCopyTable = { } -- custom links that shouldn't exist... -- (these should always happen before dependencies) if p.customLinks ~= nil then for k,lnk in pairs(p.customLinks) do table.insert(links, lnk) end end -- setup project dependencies local dependencyLocs = { } if p.projectDependencies ~= nil and #p.projectDependencies ~= 0 then for k,projname in pairs(p.projectDependencies) do local depproj = projects[projname] -- validation that it exists and can be linked to if depproj ~= nil and (depproj.kind == "SharedLib" or depproj.kind == "StaticLib") then if depproj.kind == "SharedLib" then local deplocation = nil if depproj.projectLocation ~= nil then deplocation = depproj.projectLocation .. "/" .. p.name else deplocation = depproj.name .. "/" end table.insert(dependencyLocs, { location = deplocation, name = projname }) else -- static lib -- we are now dependent on everything the static lib is dependent on if depproj.customLinks ~= nil then for k,lnk in pairs(depproj.customLinks) do table.insert(links, lnk) end end -- also include links from dependencies for i,d in pairs(depproj.dependencyTree) do if d.links then for k,v in pairs(d.links) do local propPath = v:gsub("\\", "/") table.insert(links, propPath) end end end end -- finally, depend on the project itself table.insert(links, projname) elseif depproj == nil then print("Warning: Missing external dependency for project: ".. p.name .. ". Be sure you setup project dependencies in a logical order.") else print("Warning: Cannot link " .. p.name .. " to second project " .. projname .. " because the second project is not a library.") end end end -- iterate across all root directories, matching source directories local dirs = createDirTable(p.sourcedir) -- but first, handle any files specifically set in the project, rather than -- its dependencies -- register c and h files in this directory if (p.files ~= nil and #p.files ~= 0) or (p.paths ~= nil and #p.paths ~= 0) then -- handle all lists of files if p.files ~= nil and #p.files ~= 0 then for k,filepat in pairs(p.files) do for k,f in pairs(os.matchfiles(p.sourcedir .. filepat)) do table.insert(proj.files, path.getrelative(baseLoc, f)) end end end -- end props files if -- add all .c/.h files from each path -- handle all related paths if p.paths ~= nil and #p.paths ~= 0 then for j,filepat in ipairs(p.paths) do for k,f in pairs(os.matchfiles(p.sourcedir .. filepat .. "*.c")) do table.insert(proj.files, path.getrelative(baseLoc, f)) end for k,f in pairs(os.matchfiles(p.sourcedir .. filepat .. "*.h")) do table.insert(proj.files, path.getrelative(baseLoc, f)) end -- mac osx for k,f in pairs(os.matchfiles(p.sourcedir .. filepat .. "*.m")) do table.insert(proj.files, path.getrelative(baseLoc, f)) end end end -- end of props paths if end -- end of check for files/paths in main project -- if this project has any configuration flags, add them to the current file if p.config then addConfig(p.config) end -- now, handle files and paths for dependencies for i,props in ipairs(p.dependencyTree) do if props.compat then -- register c and h files in this directory -- handle all lists of files if props.files ~= nil and #props.files ~= 0 then for k,filepat in pairs(props.files) do for k,f in pairs(os.matchfiles(p.sourcedir .. filepat)) do table.insert(proj.files, path.getrelative(baseLoc, f)) end end end -- end props files if -- add all .c/.h files from each path -- handle all related paths if props.paths ~= nil and #props.paths ~= 0 then for j,filepat in ipairs(props.paths) do for k,f in pairs(os.matchfiles(p.sourcedir .. filepat .. "*.c")) do table.insert(proj.files, path.getrelative(baseLoc, f)) end for k,f in pairs(os.matchfiles(p.sourcedir .. filepat .. "*.h")) do table.insert(proj.files, path.getrelative(baseLoc, f)) end -- mac osx for k,f in pairs(os.matchfiles(p.sourcedir .. filepat .. "*.m")) do table.insert(proj.files, path.getrelative(baseLoc, f)) end end end -- end of props paths if -- if this dependency has any special configuration flags, add 'em if props.config then addConfig(props.config) end -- end of props config if check end -- end check for compatibility end -- end of props loop --local debugConfig = configuration("Debug") local debugConfig = {} local releaseConfig = {} debugConfig.defines = { "USING_PREMAKE_CONFIG_H", "_DEBUG" } releaseConfig.defines = { "USING_PREMAKE_CONFIG_H", "NDEBUG" } -- setup per-project defines if p.defines ~= nil then for k,def in pairs(p.defines) do table.insert(debugConfig.defines, def) table.insert(releaseConfig.defines, def) end end debugConfig.buildoptions = { } if SDL_getos() == "windows" then table.insert(debugConfig.buildoptions, "/MDd") end debugConfig.linkoptions = { } releaseConfig.buildoptions = {} releaseConfig.linkoptions = {} local baseBuildDir = "/Build" if os.get() == "windows" then baseBuildDir = "/Win32" end debugConfig.flags = { "Symbols" } debugConfig.targetdir = proj.location .. baseBuildDir .. "/Debug" releaseConfig.flags = { "OptimizeSpeed" } releaseConfig.targetdir = proj.location .. baseBuildDir .. "/Release" -- setup postbuild options local dbgPostbuildcommands = { } local relPostbuildcommands = { } -- handle copying depended shared libraries to correct folders if os.get() == "windows" then for k,deploc in pairs(dependencyLocs) do table.insert(dbgCopyTable, { src = deploc.location .. baseBuildDir .. "/Debug/" .. deploc.name .. ".dll", dst = debugConfig.targetdir .. "/" .. deploc.name .. ".dll" }) table.insert(relCopyTable, { src = deploc.location .. baseBuildDir .. "/Release/" .. deploc.name .. ".dll", dst = releaseConfig.targetdir .. "/" .. deploc.name .. ".dll" }) end end if p.copy ~= nil then for k,file in pairs(p.copy) do -- the following builds relative paths native to the current system for copying, other -- than the copy command itself, this is essentially cross-platform for paths -- all custom copies should be relative to the current working directory table.insert(dbgCopyTable, { src = path.getrelative(baseLoc, p.sourcedir .. "/" .. file), dst = debugConfig.targetdir .. "/" .. file }) table.insert(relCopyTable, { src = path.getrelative(baseLoc, p.sourcedir .. "/" .. file), dst = releaseConfig.targetdir .. "/" .. file }) end end for k,file in pairs(dbgCopyTable) do -- all copies should be relative to project location, based on platform local relLocation = "./" --if os.get() == "windows" then relLocation = proj.location --end local fromPath = "./" .. path.getrelative(relLocation, file.src) local toPath = "./" .. path.getrelative(relLocation, file.dst) local toPathParent = path.getdirectory(toPath) local copyCommand = "cp" local destCheck = "if [ ! -d \\\"" .. toPathParent .. "\\\" ]; then mkdir -p \\\"" .. toPathParent .. "\\\"; fi" if SDL_getos() ~= "windows" and fromPath:find("*") ~= nil then -- to path must be a directory for * copies toPath = path.getdirectory(toPath) end if SDL_getos() == "windows" then fromPath = path.translate(fromPath, "/"):gsub("/", "\\\\") toPath = path.translate(toPath, "/"):gsub("/", "\\\\") toPathParent = path.translate(toPathParent, "/"):gsub("/", "\\\\") copyCommand = "copy" destCheck = "if not exist \\\"" .. toPathParent .. "\\\" ( mkdir \\\"" .. toPathParent .. "\\\" )" else fromPath = path.translate(fromPath, nil):gsub("\\", "/") toPath = path.translate(toPath, nil):gsub("\\", "/") end -- command will check for destination directory to exist and, if it doesn't, -- it will make the directory and then copy over any assets local quotedFromPath = fromPath if SDL_getos() == "windows" or fromPath:find("*") == nil then quotedFromPath = '\\"' .. quotedFromPath .. '\\"' end table.insert(dbgPostbuildcommands, destCheck) table.insert(dbgPostbuildcommands, copyCommand .. " " .. quotedFromPath .. " \\\"" .. toPath .. "\\\"") end for k,file in pairs(relCopyTable) do -- all copies should be relative to project location, based on platform local relLocation = "./" relLocation = proj.location local fromPath = "./" .. path.getrelative(relLocation, file.src) local toPath = "./" .. path.getrelative(relLocation, file.dst) local toPathParent = path.getdirectory(toPath) local copyCommand = "cp" local destCheck = "if [ ! -d \\\"" .. toPathParent .. "\\\" ]; then mkdir -p \\\"" .. toPathParent .. "\\\"; fi" if SDL_getos() ~= "windows" and fromPath:find("*") ~= nil then -- to path must be a directory for * copies toPath = path.getdirectory(toPath) end if SDL_getos() == "windows" then fromPath = path.translate(fromPath, "/"):gsub("/", "\\\\") toPath = path.translate(toPath, "/"):gsub("/", "\\\\") toPathParent = path.translate(toPathParent, "/"):gsub("/", "\\\\") copyCommand = "copy" destCheck = "if not exist \\\"" .. toPathParent .. "\\\" ( mkdir \\\"" .. toPathParent .. "\\\" )" else fromPath = path.translate(fromPath, nil):gsub("\\", "/") toPath = path.translate(toPath, nil):gsub("\\", "/") end -- command will check for destination directory to exist and, if it doesn't, -- it will make the directory and then copy over any assets local quotedFromPath = fromPath if SDL_getos() == "windows" or fromPath:find("*") == nil then quotedFromPath = '\\"' .. quotedFromPath .. '\\"' end table.insert(relPostbuildcommands, destCheck) table.insert(relPostbuildcommands, copyCommand .. " " .. quotedFromPath .. " \\\"" .. toPath .. "\\\"") end debugConfig.postbuildcommands = dbgPostbuildcommands debugConfig.links = links releaseConfig.postbuildcommands = relPostbuildcommands releaseConfig.links = links -- release links? for i,d in pairs(p.dependencyTree) do if d.includes then for k,v in pairs(d.includes) do local propPath = v:gsub("\\", "/") proj.includedirs[propPath] = propPath end end if d.libs then for k,v in pairs(d.libs) do local propPath = v:gsub("\\", "/") proj.libdirs[propPath] = propPath end end if d.links then for k,v in pairs(d.links) do local propPath = v:gsub("\\", "/") debugConfig.links[#debugConfig.links + 1] = propPath end end end if #proj.files > 0 then file:print(1, 'project "' .. p.name .. '"') file:print(2, 'targetname "' .. p.name .. '"') -- note: commented out because I think this hack is unnecessary --if iOSMode and p.kind == "ConsoleApp" then -- hack for iOS where we cannot build "tools"/ConsoleApps in -- Xcode for iOS, so we convert them over to WindowedApps -- p.kind = "WindowedApp" --end file:print(2, 'kind "' .. p.kind .. '"') file:print(2, 'language "' .. p.language .. '"') file:print(2, 'location "' .. proj.location .. '"') file:print(2, 'flags { "NoExceptions" }') -- NoRTTI file:print(2, 'buildoptions { }')--"/GS-" }') file:print(2, implode(proj.includedirs, 'includedirs {', '"', '"', ', ', '}')) file:print(2, implode(proj.libdirs, 'libdirs {', '"', '"', ', ', '}')) file:print(2, implode(proj.files, 'files {', '"', '"', ', ', '}')) -- debug configuration file:print(2, 'configuration "Debug"') file:print(3, 'targetdir "' .. debugConfig.targetdir .. '"') -- debug dir is relative to the solution's location file:print(3, 'debugdir "' .. debugConfig.targetdir .. '"') file:print(3, implode(debugConfig.defines, 'defines {', '"', '"', ', ', '}')) file:print(3, implode(debugConfig.links, "links {", '"', '"', ', ', "}")) if SDL_getos() == "mingw" then -- static runtime file:print(3, 'linkoptions { "-lmingw32 -static-libgcc" }') end if SDL_getos() == "cygwin" then file:print(3, 'linkoptions { "-static-libgcc" }') end file:print(3, implode(debugConfig.flags, "flags {", '"', '"', ', ', "}")) file:print(3, implode(debugConfig.postbuildcommands, "postbuildcommands {", '"', '"', ', ', "}")) -- release configuration file:print(2, 'configuration "Release"') file:print(3, 'targetdir "' .. releaseConfig.targetdir .. '"') -- debug dir is relative to the solution's location file:print(3, 'debugdir "' .. releaseConfig.targetdir .. '"') file:print(3, implode(releaseConfig.defines, 'defines {', '"', '"', ', ', '}')) file:print(3, implode(releaseConfig.links, "links {", '"', '"', ', ', "}")) if SDL_getos() == "mingw" then -- static runtime file:print(3, 'linkoptions { "-lmingw32 -static-libgcc" }') end file:print(3, implode(releaseConfig.flags, "flags {", '"', '"', ', ', "}")) file:print(3, implode(releaseConfig.postbuildcommands, "postbuildcommands {", '"', '"', ', ', "}")) end -- end check for valid project (files to build) end -- end compatibility check for projects end -- end for loop for projects endGeneration() -- finish generating the config header file file:close() -- generation is over, now execute the generated file, setup the premake -- solution, and let premake execute the action and generate the project files dofile(genFile) end -- end check for not being in help mode