Skip to content

Instantly share code, notes, and snippets.

@axefrog
Created October 15, 2015 13:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save axefrog/57dff5f909cd1f2f8508 to your computer and use it in GitHub Desktop.
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.
# ----------------------------------------------------------------------------------------------------------------------------
# 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
}
#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