Skip to content

Commit

Permalink
Add a script to create local development environment (#417)
Browse files Browse the repository at this point in the history
#### Summary <!-- Provide a general summary of your changes -->
Create a script to create local development environment for working with
apps from the repository.

The NewDevEnv.ps1 script creates a docker container (if it doesn't
exist), builds the apps, based on the provided parameters, and publishes
the apps to the container.

Test run:
https://github.com/mazhelez/BCApps/actions/runs/7225774768/job/19689897656

Note:
- The created container contains only apps from the BCApps repository.

To be considered for future enhancement:
- The created container contains all BC apps (by Microsoft): the ones
from this repo are rebuilt, the rest are taken from the BC artifact.
- Rebuild apps functionality. Now the rebuild happens only after manual
clean up the cache folder.

#### Work Item(s) <!-- Add the issue number here after the #. The issue
needs to be open and approved. Submitting PRs with no linked issues or
unapproved issues is highly discouraged. -->
Fixes AB#492033

---------

Co-authored-by: Alexander Holstrup <[email protected]>
  • Loading branch information
mazhelez and aholstrup1 authored Dec 18, 2023
1 parent 8695b1f commit 956334e
Show file tree
Hide file tree
Showing 6 changed files with 622 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/powershell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
with:
path: .\
recurse: true
excludeRule: '"PSAvoidUsingInvokeExpression", "PSUseShouldProcessForStateChangingFunctions", "PSAvoidUsingWriteHost", "PSAvoidUsingCmdletAliases", "PSUseSingularNouns"'
excludeRule: '"PSAvoidUsingInvokeExpression", "PSUseShouldProcessForStateChangingFunctions", "PSAvoidUsingWriteHost", "PSAvoidUsingCmdletAliases", "PSUseSingularNouns", "PSUseApprovedVerbs"'
output: results.sarif

# Upload the SARIF file generated in the previous step
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ bld/
[Oo]bj/
[Ll]og/
[Oo]ut/
.artifactsCache

# Visual Studio 2015 cache/options directory
.vs/
Expand Down
78 changes: 78 additions & 0 deletions build/scripts/DevEnv/ALGoProjectInfo.class.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using module .\AppProjectInfo.class.psm1

<#
.SYNOPSIS
This class is used to store information about an AL-Go project.
#>
class ALGoProjectInfo {
[string] $ProjectFolder
[PSCustomObject] $Settings


hidden ALGoProjectInfo([string] $projectFolder) {
$alGoFolder = Join-Path $projectFolder '.AL-Go'

if (-not (Test-Path -Path $alGoFolder -PathType Container)) {
throw "Could not find .AL-Go folder in $projectFolder"
}

$settingsJsonFile = Join-Path $alGoFolder 'settings.json'

if (-not (Test-Path -Path $settingsJsonFile -PathType Leaf)) {
throw "Could not find settings.json in $alGoFolder"
}

$this.ProjectFolder = $projectFolder
$this.Settings = Get-Content -Path $settingsJsonFile -Raw | ConvertFrom-Json
}

<#
Gets the AL-Go project info from the specified folder.
#>
static [ALGoProjectInfo] Get([string] $projectFolder) {
$alGoProjectInfo = [ALGoProjectInfo]::new($projectFolder)

return $alGoProjectInfo
}

<#
Finds all AL-Go projects in the specified folder.
#>
static [ALGoProjectInfo[]] FindAll([string] $folder) {
$alGoProjects = @()

$alGoProjectFolders = Get-ChildItem -Path $folder -Filter '.AL-Go' -Recurse -Directory | Select-Object -ExpandProperty Parent | Select-Object -ExpandProperty FullName

foreach($alGoProjectFolder in $alGoProjectFolders) {
$alGoProjects += [ALGoProjectInfo]::Get($alGoProjectFolder)
}

return $alGoProjects
}

<#
Gets the app folders.
#>
[string[]] GetAppFolders([switch] $Resolve) {
$appFolders = $this.Settings.appFolders

if ($Resolve) {
$appFolders = $appFolders | ForEach-Object { Join-Path $this.ProjectFolder $_ -Resolve -ErrorAction SilentlyContinue } | Where-Object { [AppProjectInfo]::IsAppProjectFolder($_) }| Select-Object -Unique
}

return $appFolders
}

<#
Gets the test folders.
#>
[string[]] GetTestFolders([switch] $Resolve) {
$testFolders = $this.Settings.testFolders

if ($Resolve) {
$testFolders = $testFolders | ForEach-Object { Join-Path $this.ProjectFolder $_ -Resolve -ErrorAction SilentlyContinue } | Where-Object { [AppProjectInfo]::IsAppProjectFolder($_) }| Select-Object -Unique
}

return $testFolders
}
}
75 changes: 75 additions & 0 deletions build/scripts/DevEnv/AppProjectInfo.class.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<#
.SYNOPSIS
This class is used to store information about an AL project.
#>
class AppProjectInfo {
[string] $AppProjectFolder
[string] $Id
[ValidateSet('app', 'test')]
[string] $Type
[PSCustomObject] $AppJson


hidden AppProjectInfo([string] $appProjectFolder, [string] $type = 'app') {

if(-not [AppProjectInfo]::IsAppProjectFolder($appProjectFolder)) {
throw "$appProjectFolder is not an app project folder"
}

$appJsonFile = Join-Path $appProjectFolder 'app.json' -Resolve
$_appJson = Get-Content -Path $appJsonFile -Raw | ConvertFrom-Json

$this.AppProjectFolder = $appProjectFolder
$this.Type = $type
$this.Id = $_appJson.id
$this.AppJson = $_appJson
}

static [AppProjectInfo] Get([string] $appProjectFolder) {
$appInfo = [AppProjectInfo]::new($appProjectFolder, 'app')

return $appInfo
}

static [AppProjectInfo] Get([string] $appProjectFolder, [string] $type) {
$appInfo = [AppProjectInfo]::new($appProjectFolder, $type)

return $appInfo
}

static [boolean] IsAppProjectFolder([string] $folder) {
return (Test-Path -Path (Join-Path $folder 'app.json') -PathType Leaf)
}

<#
Gets the app publisher.
#>
[string] GetAppPublisher() {
return $this.AppJson.publisher
}

<#
Gets the app name.
#>
[string] GetAppName() {
return $this.AppJson.name
}

<#
Gets the app version.
#>
[string] GetAppVersion() {
return $this.AppJson.version
}

<#
Gets the app file name.
#>
[string] GetAppFileName() {
$appPublisher = $this.GetAppPublisher()
$appName = $this.GetAppName()
$appVersion = $this.GetAppVersion()

return "$($appPublisher)_$($appName)_$($appVersion).app"
}
}
110 changes: 110 additions & 0 deletions build/scripts/DevEnv/NewDevEnv.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using module .\AppProjectInfo.class.psm1
using module .\ALGoProjectInfo.class.psm1

<#
.Synopsis
Creates a docker-based development environment for AL apps.
.Parameter containerName
The name of the container to use. The container will be created if it does not exist.
.Parameter userName
The user name to use for the container.
.Parameter password
The password to use for the container.
.Parameter projectPaths
The paths of the AL projects to build. May contain wildcards.
.Parameter workspacePath
The path of the workspace to build. The workspace file must be in JSON format.
.Parameter alGoProject
The path of the AL-Go project to build.
.Parameter packageCacheFolder
The folder to store the built artifacts.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '', Justification = 'local build')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'local build')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'packageCacheFolder', Justification = 'false-postiive, used in Measure-Command')]
[CmdletBinding(DefaultParameterSetName = 'ProjectPaths')]
param(
[Parameter(Mandatory = $false)]
[string] $containerName = "BC-$(Get-Date -Format 'yyyyMMdd')",

[Parameter(Mandatory = $false)]
[string] $userName = 'admin',

[Parameter(Mandatory = $true)]
[string] $password,

[Parameter(Mandatory = $true, ParameterSetName = 'ProjectPaths')]
[string[]] $projectPaths,

[Parameter(Mandatory = $true, ParameterSetName = 'WorkspacePath')]
[string] $workspacePath,

[Parameter(Mandatory = $true, ParameterSetName = 'ALGoProject')]
[string] $alGoProject,

[Parameter(Mandatory = $false)]
[string] $packageCacheFolder = ".artifactsCache"
)

$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0

# Install BCContainerHelper module if not already installed
if (-not (Get-Module -ListAvailable -Name "BCContainerHelper")) {
Write-Host "BCContainerHelper module not found. Installing..."
Install-Module -Name "BCContainerHelper" -Scope CurrentUser -AllowPrerelease -Force
}

Import-Module "BCContainerHelper" -DisableNameChecking
Import-Module "$PSScriptRoot\..\EnlistmentHelperFunctions.psm1" -DisableNameChecking
Import-Module "$PSScriptRoot\NewDevEnv.psm1" -DisableNameChecking

$baseFolder = Get-BaseFolder

# Create BC container
$credential = New-Object System.Management.Automation.PSCredential ($userName, $(ConvertTo-SecureString $password -AsPlainText -Force))
$createContainerJob = Create-BCContainer -containerName $containerName -credential $credential -backgroundJob

# Resolve AL project paths
$projectPaths = Resolve-ProjectPaths -projectPaths $projectPaths -workspacePath $workspacePath -alGoProject $alGoProject -baseFolder $baseFolder
Write-Host "Resolved project paths: $($projectPaths -join [Environment]::NewLine)"

# Build apps
$appFiles = @()
$buildingAppsStats = Measure-Command {
Write-Host "Building apps..." -ForegroundColor Yellow
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'false-postiive')]
$appFiles = Build-Apps -projectPaths $projectPaths -packageCacheFolder $packageCacheFolder
}

Write-Host "Building apps took $($buildingAppsStats.TotalSeconds) seconds"
Write-Host "App files: $($appFiles -join [Environment]::NewLine)" -ForegroundColor Green

# Wait for container creation to finish
if($createContainerJob) {
Write-Host 'Waiting for container creation to finish...' -ForegroundColor Yellow
Wait-Job -Job $createContainerJob -Timeout 1
Receive-Job -Job $createContainerJob -Wait -AutoRemoveJob

if($createContainerJob.State -eq 'Failed'){
Write-Output "Creating container failed:"
throw $($createContainerJob.ChildJobs | ForEach-Object { $_.JobStateInfo.Reason.Message } | Out-String)
}
}

if(Test-ContainerExists -containerName $containerName) {
Write-Host "Container $containerName is available" -ForegroundColor Green
} else {
throw "Container $containerName not available. Check if the container was created successfully and is running."
}


# Publish apps
Write-Host "Publishing apps..." -ForegroundColor Yellow
$publishingAppsStats = Measure-Command {
foreach($currentAppFile in $appFiles) {
Publish-BcContainerApp -containerName $containerName -appFile $currentAppFile -syncMode ForceSync -sync -credential $credential -skipVerification -install -useDevEndpoint -replacePackageId
}
}

Write-Host "Publishing apps took $($publishingAppsStats.TotalSeconds) seconds"

Loading

0 comments on commit 956334e

Please sign in to comment.