src/addons/Tools.ps1
# # Copyright (c), Adam Edwards # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # function InstallLibs([bool] $forcePrivateDotNet, [bool] $forceOverWriteFiles = $false) { # WORKAROUND: Wow. So we need to install some libraries, some dll's that we # can't package with the module without blowing it up to 100 GB... compressed # in the module repository. You'd *think* we could just use PowerShell's # Install-Package command to do this, right? You are wrong! For some reason, # the packages we need to install "cannot be found" even with AllowPrerelease set. # But strangely a wildcard search with Find-Module finds it (but not an exact # search). Many workarounds were explored, and in the end, we just decided to # do the ony thing known to work: use build tools since this is actually how # we originally generated this prior to caring about module size. So... step 1 # is to install the build tools in an isolated location if they aren't present # some how already (e.g. user has installed them for their own use cases). Cuz # yeah, we're going to do a build. PowerShell truly be trippin. Write-Progress -Id 1 -Activity 'Installing .net tools' $dotNetToolPath = GetDotNet $forcePrivateDotNet write-verbose "Using dotnet executable '$dotNetToolPath' to install libraries." $installDirectory = GetInstallDirectory # WORKAROUND: So, it turns out Test-ModuleManifest won't let you just list # any file types in the module manifest, only certain file types. And text, # xml, cs files, many others aren't in the list. :(. Oh, and if you don't list the files # they won't get published by Publish-Module. So as a workaround, the two files # we need here, a .cs file and a .csproj file, are published as .ps1 files # (at least those are allowed :)), and we then rename the to the correct types # when we copy them. Wow. copy-item $psscriptroot/addons.ps1 "$installDirectory/addons.csproj" copy-item $psscriptroot/Program.ps1 "$installDirectory/Program.cs" # Use of -force with get-item is useful on Linux where dot files / dirs are only # accessible if you use -force -- if the install directory happens to have # a dot named directory in the path. $projectFilePath = (get-item $installDirectory/addons.csproj -force).fullname $buildCommand = "'$dotNetToolPath' build $projectFilePath --configuration release" $buildCommand | write-debug Write-Progress -Id 1 -Activity 'Installing libraries' -percentcomplete 15 # WORKAROUND: We need to run the build, but in local testing, we have a global.json. # If you are a .net developer you know this forces a certain SDK version on you, which # will cause errors when the dotnet tool detects a mismatch between it and global.json. # Yes, I know this is a developer setup that is testing directly from source rather than # published module, but it's a huge efficiency win for contributors to be able to do this. # So to work around global.json, we change to a different directory becauase apparently # you can't override global.json on the command line. $output = try { pushd / # Run the build! Invoke-Expression "& $buildCommand" } finally { popd } $output | write-debug # Now that the build is done, it has downloaded the libraries and placed them in a # temporary directory of our choosing. $tempLibrarySource = GetTempLibrarySourceDirectory $installDirectory # Copy the libraries from the temporary directory into the actual module where they can # be used. Write-Progress -id 1 -Activity 'Copying libraries to ChatGPS' -percentcomplete 90 copy-item -r "$tempLibrarySource/*" (join-path $psscriptroot ../../lib) -erroraction ignore -Force:$forceOverwriteFiles # This last step moves some additional files in the module to the correct location. This # was required even when we pre-packaged the libraries. Write-Progress -id 1 -Activity 'Configuring ChatGPS to use libraries' -percentcomplete 100 ConfigureNativeLibraries -WarningActionValue Stop Write-Progress -Id 1 -Activity 'Installation compete' -Completed # Cleaning up is not strictly necessary, but it does free up some unneeded space # and also removes state that might impact future execution of this script, say # if errors were encountered the first time the script was run (network failure?). # Starting off clean with less state means greater odds of success on re-run. remove-item -r -force $tempLibrarySource -erroraction ignore remove-item -r -force "$installDirectory/bin" -erroraction ignore remove-item -r -force "$installDirectory/obj" -erroraction ignore $dotNetToolPath } function GetInstallDirectory { $targetInstallDirectory = join-path $psscriptroot ../../install if ( ! ( test-path $targetInstallDirectory ) ) { mkdir $targetInstallDirectory | out-null } (get-item $targetInstallDirectory -force).FullName } function GetTempLibrarySourceDirectory { param( [parameter(mandatory=$true)] [string] $installDirectory ) # This must match what is in the csproj file join-path $installDirectory tmplib } function GetDotNet([bool] $forcePrivate) { $isWindowsOS = $PSVersionTable.Platform -eq 'Win32NT' $installDirectory = GetInstallDirectory $dotnetDirectory = join-path $installDirectory dotnet $targetExe = if ( $isWindowsOS ) { 'dotnet.exe' } else { 'dotnet' } $privateDotNetPath = join-path $dotnetDirectory $targetExe $dotNetToolCommand = get-command dotnet -erroraction ignore $existingDotnetToolPath = if ( $dotNetToolCommand -and ! $forcePrivate ) { $dotNetToolCommand.Path } else { write-verbose "Required 'dotnet' tool not detected, looking under install directory $dotnetDirectory and retrying..." if ( test-path $privateDotNetPath ) { (Get-Item $privateDotNetPath -force).FullName } } # This minimum version thing is likely not needed in practice since # any version of the .net tool from the past 5 years will probably suffice for # what we need it to do (download dependencies during a build). But just to be safe... $hasValidVersion = $false $minimumVersionString = '8.0.3' if ( $existingDotNetToolPath ) { write-verbose "Found dotnet tool at '$existingDotNetToolPath', will check version" $minimumVersion = $minimumVersionString -split '\.' # WORKAROUND: Using the global.json workaround mentioned elsewhere in this file. $dotNetVersionString = try { pushd / ( & $existingDotNetToolPath --version ) } finally { popd } $dotNetVersion = $dotNetVersionString -split '\.' write-verbose "Looking for minimum version '$minimumVersionString'" write-verbose "Found dotnet version '$dotNetVersionString'" if ( ([int]$dotNetVersion[0]) -ge [int]$minimumVersion[0] -and ([int]$dotNetVersion[1]) -ge [int]$minimumVersion[1] -and ([int]$dotNetVersion[2]) -ge [int]$minimumVersion[2] ) { write-verbose 'Detected version meets minimum version requirement, no download necessary.' $hasValidVersion = $true } else { write-verbose 'Detected version does not meet minimum version requirement, download will be required.' } } if ( ! $hasValidVersion ) { $destinationPath = $installDirectory write-verbose "Executable not found or incorrect version, will install required .net runtime in default location '$destinationPath'..." $noPathArgument = '-NoPath' $installDirectoryArgument = '-InstallDir' $dotNetInstallerFile = if ( $isWindowsOS ) { 'dotnet-install.ps1' } else { 'dotnet-install.sh' } $dotNetInstallerPath = join-path $destinationPath $dotNetInstallerFile if ( ! ( test-path $dotNetInstallerPath ) ) { $installerUri = if ( $isWindowsOS ) { 'https://dot.net/v1/dotnet-install.ps1' } else { 'https://dot.net/v1/dotnet-install.sh' } Write-Progress -ParentId 1 -Id 2 -Activity 'Downloading .net installer script' -PercentComplete 5 write-verbose "Downloading .net installer script to '$dotNetInstallerPath'..." Invoke-WebRequest -usebasicparsing $installerUri -OutFile $dotNetInstallerPath if ( ! $isWindowsOS ) { & chmod +x $dotNetInstallerPath } } # Installs runtime and SDK Write-Progress -ParentId 1 -Id 2 -Activity 'Installing dotnet tool' -PercentComplete 10 write-verbose 'Installing new .net version...' $installerCommand = "$dotNetInstallerPath $noPathArgument $installDirectoryArgument $dotNetDirectory" write-verbose "Executing command: $installerCommand" # TODO: Improve error detection for cases where invoke-expression # masks errors. invoke-expression $installerCommand | write-verbose Write-Progress -ParentId 1 -Id 2 -Activity 'Verifying dotnet installation' -PercentComplete 100 $dotNetToolFinalVerification = test-path $privateDotNetPath if ( ! $dotNetToolFinalVerification ) { throw "Unable to install or detect required .net runtime tool 'dotnet' at location '$privateDotNetPath'" } $existingDotNetToolPath = $privateDotNetPath write-verbose "After installation dotnet tool '$privateDotNetPath' will be returned." } $existingDotNetToolPath } |