Created
October 15, 2015 13:24
-
-
Save axefrog/57dff5f909cd1f2f8508 to your computer and use it in GitHub Desktop.
Power Shell make script I wrote for my game project; can be easily adapted for other projects too. Note: BUILDING_DLL #define is used for DLLs, for use with __declspec import/export switching. I've included an example header file that illustrates this.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ---------------------------------------------------------------------------------------------------------------------------- | |
# Generic VC++ Make Script | |
# ---------------------------------------------------------------------------------------------------------------------------- | |
# Note: Make sure that vcvars.bat has been run in the current powershell session | |
# | |
# Power Shell Reference: http://ss64.com/ps/ | |
# VC++ Compiler Options: https://msdn.microsoft.com/en-us/library/19z1t1wy.aspx | |
# VC++ Linker Options: https://msdn.microsoft.com/en-us/library/y0zzbyt4.aspx | |
# Plugin handling library: http://apolukhin.github.io/Boost.DLL/ | |
# ---------------------------------------------------------------------------------------------------------------------------- | |
# Appends the full paths of files with a specified extension and directory to an array (non-recursive) | |
function addFilesInDir ([ref] $paths, $dir, $ext) { | |
foreach ($file in $dir.GetFiles()) { | |
if ($file.Extension -eq $ext) { | |
$paths.value += "`"$($dir.FullName)\*$($ext)`"" | |
return | |
} | |
} | |
} | |
# Builds (then returns) an array of full file paths with extension $ext within path $path (recursive - all subdirectories included) | |
function get-paths ([string]$path, [string]$ext) { | |
if ([String]::IsNullOrEmpty($path)) { return @() } | |
$base = get-item $path | |
$paths = @() | |
addFilesInDir -paths ([ref] $paths) -dir $base -ext $ext | |
$srcdirs = get-childitem -recurse -Attribute Directory $base.FullName | |
foreach ($dir in $srcdirs) { | |
addFilesInDir -paths ([ref] $paths) -dir $dir -ext $ext | |
} | |
return $paths | |
} | |
filter select-paths ($ext) { | |
get-paths $_ $ext | |
} | |
# Resolves a relative directory path to an absolute path. The directory is also created if it doesn't already exist. | |
function resolve-path ($path) { | |
if (!(test-path $path)) { mkdir $path | out-null } | |
$fullname = (get-item $path).FullName | |
return $fullname | |
} | |
function ensure-path-exists ($path) { | |
resolve-path $path | out-null | |
} | |
filter resolve-each-path { | |
resolve-path $_ | |
} | |
function dump-args-to-console ($psprms) { | |
write-host "=====ARGS DUMP=====" | |
foreach ($arg in $psprms.GetEnumerator()) { | |
write-host -foreground yellow "ARG: $($arg.Key)" | |
write-host -foreground darkcyan "TYPE: $($arg.Value.GetType())" | |
if ($arg.Value -is [array]) { | |
$arg.Value | foreach-object { write-host $_ } | out-null | |
} | |
else { | |
write-host $arg.Value | |
} | |
} | |
write-host "===================" | |
} | |
# ---------------------------------------------------------------------------------------------------------------------------- | |
# Generic build function. All specified paths must be absolute. | |
function build ([string]$name, # the (extensionless) filename of the binary to build | |
[string]$srcpath, # relative path to the source files | |
[string]$objpath, # relative path to where the compiler and linker should deposit their handywork | |
[string]$binpath, # relative path where final binaries should end up | |
[array]$libpaths = @(), # an array of paths to look for .lib files (will look recursively) | |
[array]$headerpaths = @(), # an array of paths where header files (h, hpp, hxx, etc.) can be found | |
[array]$deployables = @(), # the set of obj paths that will be included in the copy to $binpath upon a successful build (if empty, nothing will be copied) | |
[bool]$isdll = $false # compile a DLL instead of an EXE | |
) { | |
$ext = @{$true="exe";$false="dll"}[$isdll -eq $false] | |
$opts = "/Zi", "/EHsc", "/Fo", "/MD" | |
$opts += "/Gm" # minimal rebuild | |
$opts += $headerpaths | foreach-object { "/I$($_)" } | |
$opts += "/Fe$($objpath)\$($name).$($ext)" | |
$opts += $srcpath | select-paths ".cpp" | foreach-object { $_ } | |
$opts += "user32.lib", "Shell32.lib" | |
$opts += $libpaths | select-paths ".lib" | foreach-object { $_ } | |
if ($isdll) { | |
$opts += "/DBUILDING_DLL" | |
$opts += "/link", "/DLL" | |
} | |
# DEBUG VALUES: | |
# dump-args-to-console $psboundparameters | |
# $opts | %{write-host $_} | out-null | |
ensure-path-exists $objpath | |
pushd $objpath | |
&cl $opts | |
popd | |
if($LASTEXITCODE) { # if broken, exit. | |
exit | |
} | |
foreach ($path in $deployables) { | |
# Copy EXE, PDB and DLL files from obj directory to the target bin directory | |
get-childitem $path -filter *.pdb | where-object {$_.basename -notmatch "vc[0-9]{3}"} | copy-item -destination $binpath # copy PDB files, but exclude the compiler's incremental build files | |
get-childitem $path -filter *.exe | copy-item -destination $binpath | |
get-childitem $path -filter *.dll | copy-item -destination $binpath | |
} | |
} | |
# ---------------------------------------------------------------------------------------------------------------------------- | |
# World Garden Custom Build Script | |
# This script is built for my World Garden project, but could be used for any project that uses the same conventions. I've | |
# tried to make it clear and easy to modify, so it should serve as good basis for any custom build script that needs to handle | |
# multiple projects with plugins and miscellaneous inter-dependent modules. The current setup supports plugins built as DLL | |
# libraries, general library modules, and executables, with each being deployed to appropriate destination directories. It | |
# also supports module non-deployment for cases where a module exists only for use by a given plugin that depends on it and | |
# thus does not require deployment to the base (non-plugin) build directory. | |
# Usage: ./make.ps1 [target] [target] ... [target] [postbuild] | |
# e.g. To build the core and tests, then run the tests, use: ./make.ps1 core tests !test | |
# Notes: [target] args are the names of modules and plugins. Module names are assumed first and if not found, plugin names are | |
# assumed. For this reason, don't give any plugins the same names as primary modules (i.e. host, core or tests) | |
# Directory structure should be: | |
# / # base dev directory where this script exists | |
# /src # no code goes here | |
# /src/[module] # non-plugin module (host, core, tests, whatever) | |
# /src/plugins/[name] # same as above, but for plugin modules | |
# /dependencies # nothing in here | |
# /dependencies/lib # third-party lib files (static libraries) | |
# /dependencies/dll # third-party dll files (shared libraries) | |
# /dependencies/inc # third-party headers (for the above) | |
# /assets # optional; contents will be merged into the final build directory | |
# Directories that will exist after a build (see global vars below to change these): | |
# /.build # see below for the value of $buildpath | |
# /.build/obj # builds are performed in module-named subdirectories here | |
# /.build/bin # primary modules/executables will end up here | |
# /.build/bin/plugins # plugin modules will end up in individual subdirectories here | |
# Postbuild option determines what to do after a successful build: | |
# !run : run the host executable | |
# !debug : launch the host exe with Visual Studio in debug mode | |
# !test : run the tests executable | |
# All directory names and so forth mentioned above can be changed by editing variables below: | |
$targets = @() # the set of build targets (modules and plugins) to include (populated from command line) | |
$exename = "worldgarden" # the name of the primary executable | |
$buildpath = ".build" # change this if you want | |
$pluginfilename = "plugin" # the .dll extension is automatically appended by the linker | |
$srcfilesdirname = "src" # the name of the directory where modules can be found | |
$binariesdirname = "bin" # the destination directory for the final files that we might want to eventually deploy | |
$objfilesdirname = "obj" # the working directory compiler and linker outputs | |
$pluginsdirname = "plugins" # created inside the the binaries directory (also used for finding plugins in the src directory) | |
$dependenciespath = "dependencies" # for third-party dependencies | |
$dependentlibspath = "lib" # static libraries subdirectory of dependencies | |
$dependentheaderspath = "inc" # header files subdirectory of dependencies | |
$dependentdllspath = "dll" # shared library subdirectory of dependencies | |
$assetspath = "assets" # assets directory; contents to be copied to binaries directory | |
function should-build ($name) { | |
($targets.Length -eq 0) -or ($targets -contains $name) | |
} | |
function get-relative-srcpath ($name, $type) { | |
$subpath = "" | |
if ($type -eq "plugin") { $subpath = "$($pluginsdirname)\" } | |
return "$($srcfilesdirname)\$($subpath)$($name)" | |
} | |
function get-relative-objpath ($name, $type) { | |
$subpath = "" | |
if ($type -eq "plugin") { $subpath = "$($pluginsdirname)\" } | |
return "$($buildpath)\$($objfilesdirname)\$($subpath)$($name)" | |
} | |
function get-relative-binpath ($name, $type) { | |
$subpath = "" | |
if ($type -eq "plugin") { $subpath = "\$($pluginsdirname)\$($name)" } | |
return "$($buildpath)\$($binariesdirname)$($subpath)" | |
} | |
filter select-relative-srcpath ($type) { | |
get-relative-srcpath $_ $type | |
} | |
filter select-relative-objpath ($type) { | |
get-relative-objpath $_ $type | |
} | |
filter select-relative-binpath ($type) { | |
get-relative-binpath $_ $type | |
} | |
function build-project ($name, $type = "library", [array] $libpaths = @(), [array] $headerpaths = @(), $altname = $null, $deployself = $true, [array] $dependentmodules = @()) { | |
if (-not (should-build $name)) { return } | |
switch ($type) { | |
"plugin" { $isdll = $true } | |
"library" { $isdll = $true } | |
"executable" { $isdll = $false } | |
default { $isdll = $false } | |
} | |
if ($libpaths -isnot [array]) { $libpaths = @(libpaths) } | |
if ($headerpaths -isnot [array]) { $headerpaths = @(headerpaths) } | |
if ($dependentmodules -isnot [array]) { $dependentmodules = @(dependentmodules) } | |
write-host -foreground "cyan" "Building $($type) module: $($name)" | |
[string] $srcpath = get-relative-srcpath $name $type | |
[string] $objpath = get-relative-objpath $name $type | |
[string] $binpath = get-relative-binpath $name $type | |
$libpaths += "$($dependenciespath)\$($dependentlibspath)" | |
$libpaths += $dependentmodules | select-relative-objpath -type $type | |
$headerpaths_temp = $headerpaths | |
$headerpaths = @($srcpath) | |
$headerpaths += $headerpaths_temp | |
$headerpaths += $dependentmodules | select-relative-srcpath -type $type | |
$headerpaths += "$($dependenciespath)\$($dependentheaderspath)" | |
[array] $deployables = $dependentmodules | select-relative-objpath -type $type | |
if ($deployself) { | |
$deployables += $objpath | |
} | |
if ($altname) { | |
$name = $altname | |
} | |
build -name $name ` | |
-isdll $isdll ` | |
-srcpath (resolve-path $srcpath) ` | |
-objpath (resolve-path $objpath) ` | |
-binpath (resolve-path $binpath) ` | |
-libpaths ($libpaths | resolve-each-path) ` | |
-headerpaths ($headerpaths | resolve-each-path) ` | |
-deployables ($deployables | resolve-each-path) | |
} | |
# ---------------------------------------------------------------------------------------------------------------------------- | |
# And finally we actually initiate the builds... | |
function build-plugins { | |
$dirs = get-childitem -recurse -Attribute Directory "src\$($pluginsdirname)" | |
foreach ($dir in $dirs) { | |
build-project $dir.Name "plugin" -altname $pluginfilename -dependentmodules "core" | |
} | |
} | |
$postbuild = "" | |
foreach ($arg in $args) { | |
switch ($arg) { | |
"!run" { $postbuild = "run" } | |
"!test" { $postbuild = "test" } | |
"!debug" { $postbuild = "debug" } | |
default { $targets += $arg } | |
} | |
} | |
# World Garden core library is built first because other things depend on it | |
build-project "core" -altname "wgcore" | |
# Host is our main executable that will bootstrap the core | |
build-project "host" -type "executable" -altname $exename -dependentmodules "core" | |
# Plugins will go into separate subdirectories of a "plugins" directory hanging off of the build directory | |
get-childitem -recurse -Attribute Directory "src\$($pluginsdirname)" ` | |
| foreach-object { build-project $_.Name "plugin" -altname $pluginfilename -headerpaths "$($srcfilesdirname)\core" } | |
# The unit test executable will go into the main build directory but can be ignored for distribution | |
build-project "tests" -type "executable" -dependentmodules "core" | |
# ---------------------------------------------------------------------------------------------------------------------------- | |
# If all went nicely, copy any assets and content files | |
if (!$LASTEXITCODE) { | |
# Copy DLLs from libraries that we depend on in the dependencies directory | |
get-childitem $dependenciespath\$dependentdllspath -recurse -filter *.dll | copy-item -dest $buildpath\$binariesdirname | |
# Copy everything verbatim from the assets directory into the build directory | |
robocopy /S /XO /NJS /NJH /NFL $assetspath $buildpath\$binariesdirname | |
pushd $buildpath\$binariesdirname | |
switch ($postbuild) { | |
"run" { | |
cls | |
&.\$exename | |
} | |
"test" { | |
&.\tests | |
} | |
"debug" { | |
start-process devenv -ArgumentList ".\$($exename).exe" | |
exit | |
} | |
} | |
popd | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifdef BUILDING_DLL | |
#define DLLEXPORT __declspec(dllexport) | |
#define EXTERN_C_DLLEXPORT extern "C" __declspec(dllexport) | |
#else | |
#define DLLEXPORT __declspec(dllimport) | |
#define EXTERN_C_DLLEXPORT extern "C" __declspec(dllimport) | |
#endif | |
EXTERN_C_DLLEXPORT void run(); | |
class DLLEXPORT FooClass { /* definition here */ }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment