Includes/PwSh.Fw.Build.Macos.psm1

# @var ConvertIsInstalled
# @brief $true if 'convert' from Image Magick package is installed
$Script:ConvertIsInstalled = Get-Command -Name "convert" -ErrorAction SilentlyContinue
$Script:ConvertNotInstalledMessage = "'convert' command not found. Automatic icon handling is disabled. Please read the FAQ if you need it."
$Script:Png2icnsIsInstalled = Get-Command -Name "png2icns" -ErrorAction SilentlyContinue
$Script:Png2icnsNotInstalledMessage = "'png2icns' command not found. Automatic icon handling is disabled. Please read the FAQ if you need it."

<#
.SYNOPSIS
Write Info.plist
 
.DESCRIPTION
Output a fully-formated Info.plist file based on build configuration
 
.PARAMETER build
build object read from build.rc
 
.PARAMETER Destination
destination folder
 
.EXAMPLE
$project | Out-InfoPlist -Destination /tmp/project.build/Contents
 
.NOTES
 
#>


function Out-InfoPlist {
    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [Parameter(Mandatory = $true,ValueFromPipeLine = $true)][hashtable]$project,
        [Parameter(Mandatory = $true,ValueFromPipeLine = $false)][string]$Destination
    )
    Begin {
        Write-EnterFunction
        if (!(dirExist($Destination))) { New-Item $($Destination) -Force -ItemType Directory }
    }

    Process {
@"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>CFBundlePackageType</key> <string>APPL</string>
        <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string>
        <key>CFBundleName</key> <string>$($project.Name)</string>
        <key>CFBundleDisplayName</key> <string>$($project.DisplayName)</string>
        <key>CFBundleExecutable</key> <string>run-$($project.Name)-app.sh</string>
        <key>CFBundleIdentifier</key> <string>$($project.productId)</string>
        <key>CFBundleIconFile</key> <string>$($project.Name).icns</string>
        <key>CFBundleShortVersionString</key> <string>$($project.version)</string>
        <key>CFBundleVersion</key> <string>$($project.fqVersion)</string>
        <key>CFBundleGetInfoString</key> <string>$($project.Description)</string>
    </dict>
</plist>
"@
 | Set-Content "$Destination/Info.plist"
        return "$Destination/Info.plist"
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
Write Distribution.xml
 
.DESCRIPTION
Output a fully-formated Distribution.xml file based on build configuration
 
.PARAMETER build
build object read from build.rc
 
.PARAMETER Destination
destination folder
 
.OUTPUTS
Full path to Distribution.xml file
 
.EXAMPLE
$project | Out-DistributionXML -Destination /tmp/project.build
 
.NOTES
 
#>

function Out-DistributionXML {
    [CmdletBinding()]
    [OutputType([String])]
    Param (
        [Parameter(Mandatory = $true,ValueFromPipeLine = $true)][hashtable]$project,
        [Parameter(Mandatory = $true,ValueFromPipeLine = $false)][string]$Destination
    )
    Begin {
        Write-EnterFunction
        if (!(dirExist($Destination))) { New-Item $($Destination) -Force -ItemType Directory }
    }

    Process {
@"
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<installer-gui-script minSpecVersion="1">
    <title>$($project.DisplayName)</title>
    <organization>$($project.orgId)</organization>
    <domains enable_localSystem="true"/>
    <options customize="never" require-scripts="true" rootVolumeOnly="true" />
    <!-- Define documents displayed at various steps -->
    <welcome file="welcome.html" mime-type="text/html" />
    <license file="LICENSE" mime-type="text/plain" />
    <conclusion file="conclusion.html" mime-type="text/html" />
    <!-- List all component packages -->
    <pkg-ref id="$($project.productId)"
             version="0"
             auth="root">/tmp/$($project.Name).pkg</pkg-ref>
    <!-- List them again here. They can now be organized
         as a hierarchy if you want. -->
    <choices-outline>
        <line choice="$($project.productId)"/>
    </choices-outline>
    <!-- Define each choice above -->
    <choice
        id="$($project.productId)"
        visible="false"
        title="$($project.Name) daemon"
        description="$($project.Name) daemon"
        start_selected="true">
      <pkg-ref id="$($project.productId)"/>
    </choice>
</installer-gui-script>
"@
 | Set-Content "$Destination/Distribution.xml"
        return "$Destination/Distribution.xml"
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
Convert an image to icns Apple format
 
.DESCRIPTION
Convert an image to icns format to be used on macOS system.
 
.PARAMETER Image
Full path to an image file
 
.PARAMETER Destination
Destination folder
 
.PARAMETER Filename
Optional. New filename of the image
 
.EXAMPLE
ConvertTo-LinuxIcons -Image /path/to/favicon.png
 
.NOTES
This function do not convert image size. It just convert format.
#>

function ConvertTo-MacOSIcons {
    [CmdletBinding()]
    [OutputType([string], [Boolean])]
    Param (
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][string]$Image,
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][string]$Destination,
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][string]$Filename
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        if (!(Test-FileExist $Image)) {
            eerror "Image '$Image' not found."
            $rc = $false
        }
        $basename = (Get-Item $Image).BaseName
        # $ext = (Get-Item $Image).Extension
        if ([string]::IsNullOrEmpty($Filename)) {
            $Filename = $basename
        }
        if ([string]::IsNullOrEmpty($Destination)) {
            $Destination = (Get-Item $Image).DirectoryName
        }

        if ($Script:Png2icnsIsInstalled) {
            $null = Execute-Command -exe "png2icns" -args "$Destination/$Filename.icns $Image"
            $rc = "$Destination/$Filename"
        } else {
            Write-Warning $Script:Png2icnsNotInstalledMessage
            $rc = $Image
        }

        return $rc
    }

    End {
        Write-LeaveFunction
    }
}

<#
.SYNOPSIS
Build the project to a macos package
 
.DESCRIPTION
Build the project to a full featured MacOS package.
 
.EXAMPLE
New-MacOSBuild -Project (Get-Project)
 
.NOTES
 
#>

function New-MacOSBuild {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low', DefaultParameterSetName = 'PROJECT')]
    [OutputType([Boolean], [String])]
    Param (
        # The project object as returned by Get-Project
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][hashtable]$Project,

        # An optional MACOS folder where macos package scripts are stored. @see @url https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW1
        [Alias('Configuration')]
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][string]$MacOSFolder = "./build/macos",

        # The source folder of your project. Files and directory structure will be kept as-is
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][string]$Source = "./src",

        # Destination folder to create resulting package
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][string]$Destination = "./releases",

        # Override output package filename.
        # It defaults to projectName-Version-Arch.deb
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][string]$OutputFileName,

        # Build type. It can be 'app', 'pkg' or 'dmg'. Default to 'pkg'
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)]
        [ValidateSet("app", "pkg", "dmg")][string]$BuildType = "pkg",

        # Force/override/overwrite things
        [switch]$Force
    )
    Begin {
        Write-EnterFunction
        if (!(Test-DirExist $Destination)) {
            $null = New-Item -Path $Destination -ItemType Directory -Confirm:$false -Force:$Force
        }
        $MacOSFolder = ($MacOSFolder | Resolve-Path).path
    }

    Process {
        if (Test-DirExist $MacOSFolder) {
            # copy MACOS folder taking care of mustache template files
            Copy-Item $MacOSFolder/* -Recurse $Source -Force:$Force -Exclude "*.mustache"
            Get-ChildItem $MacOSFolder -Recurse -Filter "*.mustache" | ForEach-Object {
                $item = $_
                $destFilePath = $item.DirectoryName -replace "$MacOSFolder", "$Source"
                $destFileName = "$destFilePath/$($item.Basename)"
                ConvertFrom-MustacheTemplate -Template (Get-Content -Raw $item.fullname) -Values $Project | Out-File $destFileName
                # these are executable scripts (postinst / postrm) so ensure they are executable (Out-File create only regular files)
                $mode = (stat $item.fullname --printf %a)
                $null = Execute-Command -exe chmod -args "$mode $destFileName"
            }
        }
        # $project.size = (Get-ChildItem $Source -Recurse | Measure-Object -Property Length -sum).Sum | Convert-Size -From bytes -To KB -ValueOnly
        # $ControlFields = $project | Out-MacOSCONTROLFile -Destination $Source/MACOS -PassThru
        # copy icon to proper location
        # Copy-Item "$($Project.Root)/$($Project.IconFile)" -Destination $Source -Force -Confirm:$false
        if ($Project.IconFile) {
            $null = ConvertTo-MacOSIcons -Image $Project.IconFile -Destination "$Source/Content/Resources" -Filename $Project.Name
        }
        if ([string]::IsNullOrEmpty($OutputFileName)) {
            $Filename = "$($project.name)-$($project.fqVersion).$BuildType"
        } else {
            $Filename = $OutputFileName
        }
        if ($PSCmdlet.ShouldProcess("$Destination/$Filename", "Create macos package")) {
            switch ($BuildType) {
                "app" {
                    $rc = Invoke-MacOSAppBuildCommand -Project $Project -Source $Source -Destination $Destination -OutputFileName $Filename
                }
                "pkg" {
                    $rc = Invoke-MacOSPkgBuildCommand -Project $Project -Source $Source -Destination $Destination -OutputFileName $Filename
                }
                "dmg" {
                    $rc = Invoke-MacOSDmgBuildCommand -Project $Project -Source $Source -Destination $Destination -OutputFileName $Filename
                }
            }
            if (!(Get-Command -Name fakeroot)) { Write-Fatal "fakeroot command not found in system. Try to install the fakeroot package." }
            $rc = Execute-Command -exe fakeroot -args "dpkg -b $Source $Destination/$Filename" -AsBool
            Write-Devel "rc = $rc"
            if (Test-FileExist "$Destination/$Filename") {
                $value = (Resolve-Path "$Destination/$Filename").Path
            } else {
                $value = $false
            }
        } else {
            $value = "$Destination/$Filename"
        }

        Write-Devel "value = $value"
        return $value
    }

    End {
        Write-LeaveFunction
    }
}

function Invoke-MacOSPkgBuildCommand {
    [CmdletBinding()]
    [OutputType([Boolean], [String])]
    Param (
        # The project object as returned by Get-Project
        [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][hashtable]$Project,

        # The source folder of your project. Files and directory structure will be kept as-is
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][string]$Source = "./src",

        # Destination folder to create resulting package
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][string]$Destination = "./releases",

        # Install location on target system. Default to /Applications
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][string]$InstallLocation = "/Applications",

        # Override output package filename.
        # It defaults to projectName-Version-Arch.deb
        [Parameter(Mandatory = $false, ValueFromPipeLine = $false)][string]$OutputFileName
    )
    Begin {
        Write-EnterFunction
    }

    Process {
        $rc = Execute-Command -exe "pkgbuild" -args "--root $Source --identifier $($Project.productId) --version $($Project.fqVersion) --install-location $InstallLocation --ownership recommended --scripts $Source/macos/Contents/Scripts $Destination/$OutputFileName" -AsBool
        Write-Devel "rc = $rc"
        if (!$rc) {
            Write-Error "Failed to create pkg package."
            return $false
        }

        return $OutputFileName
    }

    End {
        Write-LeaveFunction
    }
}