LeetABit.Build.psm1

#requires -version 6
using namespace System.Collections
using namespace System.Collections.Generic
using namespace Microsoft.PowerShell.Commands
using module LeetABit.Build.Common
using module LeetABit.Build.Extensibility

Set-StrictMode -Version 3.0
Import-LocalizedData -BindingVariable LocalizedData -FileName LeetABit.Build.Resources.psd1

$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
    Remove-Module LeetABit.Build.* -Force
}


##################################################################################################################
# Public Commands
##################################################################################################################


function Build-Repository {
    <#
    .SYNOPSIS
        Performs a build operation on all projects located in the specified repository.
    .DESCRIPTION
        Build-Repository cmdlet runs project resolution for all registered extensions against specified repository root directory. And then run specified task for all projects and its extensions.
    .EXAMPLE
        PS> Build-Repository '~/repository' 'help'
 
        Runs a help task for all extensions that supports it using no additional arguments.
    .EXAMPLE
        PS> Build-Repository '~/repository' 'build' -ExtensionModule @{ModuleName = "PowerShell"; ModuleVersion = "1.0.0"}
 
        Loads "PowerShell" extension and runs a build task for all extensions that supports it using no additional arguments.
    .EXAMPLE
        PS> Build-Repository '~/repository' -NamedArguments @{ 'CompilerVersion' = '1.0.0' } -UnknownArguments ("-Debug")
 
        Runs a default build task for all extensions that supports it using specified additional arguments.
    #>

    [CmdletBinding(PositionalBinding = $False)]

    param (
        # The path to the repository root folder.
        [Parameter(HelpMessage = "Provide path to the repository's root directory.",
                   Position = 0,
                   Mandatory = $True,
                   ValueFromPipeline = $False,
                   ValueFromPipelineByPropertyName = $True)]
        [ValidateContainerPathAttribute()]
        [String]
        $RepositoryRoot,

        # Name of the build task to invoke.
        [Parameter(Position = 1,
                   Mandatory = $True,
                   ValueFromPipeline = $False,
                   ValueFromPipelineByPropertyName = $True)]
        [ValidateIdentifierOrEmptyAttribute()]
        [AllowEmptyString()]
        [String]
        $TaskName,

        # Extension modules to import.
        [Parameter(Mandatory = $False,
                   ValueFromPipeline = $False,
                   ValueFromPipelineByPropertyName = $True)]
        [AllowNull()]
        [AllowEmptyCollection()]
        [ModuleSpecification[]]
        $ExtensionModule,

        # Dictionary of buildstrapper arguments (including dynamic ones) that have been successfully bound.
        [Parameter(Mandatory = $False,
                   ValueFromPipeline = $False,
                   ValueFromPipelineByPropertyName = $True)]
        [IDictionary]
        $NamedArguments,

        # Arguments to be passed to the target.
        [Parameter(Mandatory = $False,
                   ValueFromPipeline = $False,
                   ValueFromPipelineByPropertyName = $True)]
        [String[]]
        $UnknownArguments)

    begin {
        LeetABit.Build.Common\Import-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
    }

    process {
        LeetABit.Build.Logging\Write-Invocation -Invocation $MyInvocation
        LeetABit.Build.Arguments\Set-CommandArgumentSet -RepositoryRoot $RepositoryRoot -NamedArguments $NamedArguments -UnknownArguments $UnknownArguments
        Initialize-WellKnownParameters -RepositoryRoot $RepositoryRoot -ExtensionModule $ExtensionModule

        $ExtensionModule = LeetABit.Build.Arguments\Find-CommandArgument -ParameterName 'ExtensionModule'
        if ($ExtensionModule) {
            $ExtensionModule | ForEach-Object {
                if (-not (Get-Module -FullyQualifiedName @{ ModuleName="$($_.Name)"; ModuleVersion=$_.RequiredVersion } -ListAvailable)) {
                    Write-Verbose "Installing $($_.Name) v$($_.RequiredVersion) from the available PowerShell repositories..."
                    Install-Module -Name $_.Name -RequiredVersion $_.RequiredVersion -Scope CurrentUser -AllowPrerelease -Force
                }
            }

            Import-Module -FullyQualifiedName $ExtensionModule
        }

        Import-RepositoryExtension -RepositoryRoot $RepositoryRoot

        $TaskName = LeetABit.Build.Arguments\Find-CommandArgument -ParameterName 'TaskName' -DefaultValue $TaskName
        $projectPath = LeetABit.Build.Arguments\Find-CommandArgument -ParameterName 'SourceRoot'

        LeetABit.Build.Extensibility\Resolve-Project $projectPath 'LeetABit.Build.Repository' $TaskName | Select-Object -Unique | ForEach-Object {
            $projectPath, $extensionName = $_
            LeetABit.Build.Extensibility\Invoke-BuildTask $extensionName $TaskName $projectPath
        }
    }
}


##################################################################################################################
# Private Commands
##################################################################################################################


function Initialize-WellKnownParameters {
    <#
    .SYNOPSIS
        Initializes a set of well known parameters with its default values.
    #>

    [CmdletBinding(PositionalBinding = $False)]

    param (
        # The directory to the repository's root directory path.
        [Parameter(HelpMessage = "Provide path to the repository's root directory.",
                   Position = 0,
                   Mandatory = $True,
                   ValueFromPipeline = $False,
                   ValueFromPipelineByPropertyName = $False)]
        [String]
        $RepositoryRoot,
        
        # Collection fo extension modules to import.
        [Parameter(Mandatory = $False,
                   ValueFromPipeline = $False,
                   ValueFromPipelineByPropertyName = $False)]
        [AllowNull()]
        [AllowEmptyCollection()]
        [ModuleSpecification[]]
        $ExtensionModule)

    process {
        LeetABit.Build.Arguments\Add-CommandArgument 'ArtifactsRoot' (Join-Path $RepositoryRoot 'artifacts') -ErrorAction SilentlyContinue
        LeetABit.Build.Arguments\Add-CommandArgument 'SourceRoot' (Join-Path $RepositoryRoot 'src') -ErrorAction SilentlyContinue
        LeetABit.Build.Arguments\Add-CommandArgument 'TestRoot' (Join-Path $RepositoryRoot 'test') -ErrorAction SilentlyContinue
        [ModuleSpecification[]]$existingExtensionModule = LeetABit.Build.Arguments\Find-CommandArgument -ParameterName 'ExtensionModule'
        [ModuleSpecification[]]$uniqueExtensionModule = (($existingExtensionModule + $ExtensionModule) | Select-Object -Unique)
        LeetABit.Build.Arguments\Add-CommandArgument 'ExtensionModule' $uniqueExtensionModule -ErrorAction SilentlyContinue
    }
}


function Import-RepositoryExtension {
    <#
    .SYNOPSIS
        Executes LeetABit.Build.Repository scripts from the specified repository.
    #>

    [CmdletBinding(PositionalBinding = $False)]

    param (
        # The directory to the repository's root directory path.
        [Parameter(HelpMessage = "Provide path to the repository's root directory.",
                   Position = 0,
                   Mandatory = $True,
                   ValueFromPipeline = $False,
                   ValueFromPipelineByPropertyName = $False)]
        [String]
        $RepositoryRoot)

    process {
        Get-ChildItem -Path $RepositoryRoot -Filter "LeetABit.Build.Repository.ps1" -Recurse | ForEach-Object {
            . "$_"
        }
    }
}


Export-ModuleMember -Function '*' -Variable '*' -Alias '*' -Cmdlet '*'