Files
meteor/scripts/generate-dev-bundle.ps1
Jesse Rosenberger c81c2fb2b8 Re-work Windows "Generate Dev Bundle" process.
This is a re-write of the generate-dev-bundle.ps1 script, which occurred
during debugging of an unrelated concern of the (new) 64-bit Windows
build on our Jenkins server.  Ultimately, I'm afraid this script doesn't
solve the problem I originally set out to fix, which was a Windows
long-file path concern.

The hunch behind that thinking was that the use of npm@1 to install
npm@5 could be causing problems, since npm@1 had no concept of nested
node_modules directories.  We had used npm@1 because Node.js
for Windows hasn't always offered (via nodejs.org/dist/) versions
including npm which we could use to install our own requirements.
Happily, that is no longer the case!

While this script now deals with long paths much more gracefully itself,
I'm not sure it completely quelled the long-path issue, and there are
still some directory trees which are longer than I think they should be.

The warnings I was seeing may not have harmed the actual bundle and were
more problematic for this build script itself when it tried to deal with
the aftermath of all those files, since native Windows commands struggle
to deal with long file paths (when cleaning up, etc.).

In the end, this script does have performance enhancements though! For
starters, it's nearly twice as fast at finishing.  Most of this was
gained by avoiding back-and-forth moving of large file structures,
opting instead to directly install into the targets when possible.

It also ensures that the npm build cache is not bundled, which started
occurring since our modification of the $HOME and $USERPROFILE variables
led npm@5 to think the npm cache was in the root of the bundle.

Additionally, it no longer modifies the $PATH, in any way, during the
build. This became particularly helpful during testing when I found that
PowerShell maintained that $PATH in the environment of the host shell.

I'd like to say it increases readability of the script, which had
become a bit of a patchwork quilt, but that's YTBD and YMMV.

This is my first "complete" PowerShell script myself so it probably
still leaves some things to desired, formatting wise.  Functionality-
wise, I hope it's improved.
2017-10-14 13:36:29 -04:00

425 lines
14 KiB
PowerShell

$ErrorActionPreference = "Stop"
$DebugPreference = "Continue"
Import-Module -Force "$PSScriptRoot\windows\dev-bundle-lib.psm1"
$PLATFORM = Get-MeteorPlatform
$PYTHON_VERSION = "2.7.14" # For node-gyp
$dirCheckout = (Get-Item $PSScriptRoot).parent.FullName
$shCommon = Join-Path $PSScriptRoot 'build-dev-bundle-common.sh'
$tempSrcNode = Join-Path $(Join-Path $dirCheckout 'temp_build_src') 'node.7z'
# This will be the temporary directory we build the dev bundle in.
$DIR = Join-Path $dirCheckout 'gdbXXX'
# extract the bundle version from the meteor bash script
$BUNDLE_VERSION = Read-VariableFromShellScript "${dirCheckout}\meteor" 'BUNDLE_VERSION'
# extract the major package versions from the build-dev-bundle-common script.
$MONGO_VERSION_64BIT = Read-VariableFromShellScript $shCommon 'MONGO_VERSION_64BIT'
$MONGO_VERSION_32BIT = Read-VariableFromShellScript $shCommon 'MONGO_VERSION_32BIT'
$NODE_VERSION = Read-VariableFromShellScript $shCommon 'NODE_VERSION'
$NPM_VERSION = Read-VariableFromShellScript $shCommon 'NPM_VERSION'
# 7-zip path.
$system7zip = "C:\Program Files\7-zip\7z.exe"
# Since we reuse the same temp directory, cleanup from previous failed runs.
Remove-DirectoryRecursively $DIR
# Some commonly used paths in this script.
$dirBin = Join-Path $DIR 'bin'
$dirLib = Join-Path $DIR 'lib'
$dirServerLib = Join-Path $DIR 'server-lib'
$dirTemp = Join-Path $DIR 'temp'
# Use a cache just for this build.
$dirNpmCache = Join-Path $dirTemp 'npm-cache'
# Build our directory framework.
New-Item -ItemType Directory -Force -Path $DIR | Out-Null
New-Item -ItemType Directory -Force -Path $dirTemp | Out-Null
New-Item -ItemType Directory -Force -Path $dirBin | Out-Null
New-Item -ItemType Directory -Force -Path $dirLib | Out-Null
New-Item -ItemType Directory -Force -Path $dirServerLib | Out-Null
# add bin to the front of the path so we can use our own node for building
Add-ToExistingEnvPath $dirBin
$webclient = New-Object System.Net.WebClient
$shell = New-Object -com shell.application
Function Invoke-Install7ZipApplication {
Write-Host "Downloading 7-zip..." -ForegroundColor Magenta
$7zMsiPath = Join-Path $dirTemp '7z.msi'
# 32-bit, right now. But this does not go in the bundle.
$webclient.DownloadFile("http://www.7-zip.org/a/7z1604.msi", $7zMsiPath)
Write-Host "Installing 7-zip system-wide..." -ForegroundColor Magenta
& "msiexec.exe" /i $7zMsiPath /quiet /qn /norestart | Out-Null
# Cleanup.
Remove-Item $7zMsiPath
}
Function Add-7ZipTool {
Write-Host "Downloading 7-zip 'extra'..." -ForegroundColor Magenta
$extraArchive = Join-Path $dirTemp 'extra.7z'
$webclient.DownloadFile("http://www.7-zip.org/a/7z1604-extra.7z", $extraArchive)
$pathToExtract = 'x64/7za.exe'
if ($PLATFORM -eq "windows_x86") {
$pathToExtract = '7za.exe'
}
Write-Host 'Placing 7za.exe from extra.7z in \bin...' -ForegroundColor Magenta
& "$system7zip" e $extraArchive -o"$dirTemp" $pathToExtract | Out-Null
Move-Item $(Join-Path $dirTemp '7za.exe') $(Join-Path $dirBin "7z.exe")
# Cleanup
Remove-Item $extraArchive
}
Function Add-Python {
# On Windows we provide a reliable version of python.exe for use by
# node-gyp (the tool that rebuilds binary node modules).
# This self-hosted 7z is created by archiving the result of running the
# Python MSI installer (from python.org), targeted at a temp directory, and
# only including: "Python" and "Utility Scripts". Then, 7z the temp directory.
$pythonUrl = "https://s3.amazonaws.com/com.meteor.static/windows-python/",
"$PLATFORM/python-${PYTHON_VERSION}.7z" -Join ''
$pythonArchive = Join-Path $dirTemp 'python.7z'
$webclient.DownloadFile($pythonUrl, $pythonArchive)
Expand-7zToDirectory $pythonArchive $DIR
$pythonDir = Join-Path $DIR 'python'
$pythonExe = Join-Path $pythonDir 'python.exe'
# Make sure the version is right, when python is called.
if (!(cmd /c python.exe --version '2>&1' -Eq "Python ${PYTHON_VERSION}")) {
throw "Python was not the version we expected it to be ($PYTHON_VERSION)"
}
Remove-Item $pythonArchive
"$pythonExe"
}
Function Add-NodeAndNpm {
$nodeUrlBase = 'https://nodejs.org/dist'
if ($PLATFORM -eq "windows_x86") {
$nodeArchitecture = 'win-x86'
} else {
$nodeArchitecture = 'win-x64'
}
# Various variables which are used as part of directory paths and
# inside Node release and header archives.
$nodeVersionSegment = "v${NODE_VERSION}"
$nodeNameVersionSegment = "node-${nodeVersionSegment}"
$nodeNameSegment = "${nodeNameVersionSegment}-${nodeArchitecture}"
# The URL for the Node 7z archive, which includes its shipped version of npm.
$nodeUrl = $nodeUrlBase, $nodeVersionSegment,
"${nodeNameSegment}.7z" -Join '/'
$archiveNode = Join-Path $dirTemp 'node.7z'
Write-Host "Downloading Node.js from ${nodeUrl}" -ForegroundColor Magenta
$webclient.DownloadFile($nodeUrl, $archiveNode)
Write-Host "Extracting Node 7z file..." -ForegroundColor Magenta
& "$system7zip" x $archiveNode -o"$dirTemp" | Out-Null
# This will be the location of the extracted Node tarball.
$dirTempNode = Join-Path $dirTemp $nodeNameSegment
# Delete the no longer necessary Node archive.
Remove-Item $archiveNode
$tempNodeExe = Join-Path $dirTempNode 'node.exe'
$tempNpmCmd = Join-Path $dirTempNode 'npm.cmd'
# Get additional values we'll need to fetch to complete this release.
$nodeProcessRelease = & "$tempNodeExe" -p 'process.release' | ConvertFrom-Json
if (!($nodeProcessRelease.headersUrl -And $nodeProcessRelease.libUrl)) {
throw "No 'headersUrl' or 'libUrl' in Node.js's 'process.release' output."
}
$nodeHeadersTarGz = Join-Path $dirTemp 'node-headers.tar.gz'
Write-Host "Downloading Node headers from $($nodeProcessRelease.headersUrl)" `
-ForegroundColor Magenta
$webclient.DownloadFile($nodeProcessRelease.headersUrl, $nodeHeadersTarGz)
$dirTempNodeHeaders = Join-Path $dirTemp 'node-headers'
if (!(Expand-TarGzToDirectory $nodeHeadersTarGz $dirTempNodeHeaders)) {
throw "Couldn't extract Node headers."
}
# Move the extracted include directory to the Node dir.
$dirTempNodeHeadersInclude = `
Join-Path $dirTempNodeHeaders $nodeNameVersionSegment |
Join-Path -ChildPath 'include'
Move-Item $dirTempNodeHeadersInclude $dirTempNode
$dirTempNodeHeadersInclude = Join-Path $dirTempNode 'include'
# The node.lib goes into a \Release directory.
$dirNodeRelease = Join-Path $dirTempNode 'Release'
New-Item -ItemType Directory -Force -Path $dirNodeRelease | Out-Null
Write-Host "Downloading node.lib from $($nodeProcessRelease.libUrl)" `
-ForegroundColor Magenta
$nodeLibTarget = Join-Path $dirNodeRelease 'node.lib'
$webclient.DownloadFile($nodeProcessRelease.libUrl, $nodeLibTarget)
#
# We should now have a fully functionaly local Node with headers to use.
#
# Let's install the npm version we really want.
Write-Host "Installing npm@${NPM_VERSION}..." -ForegroundColor Magenta
& "$tempNpmCmd" install --prefix="$dirLib" --no-bin-links --save `
--cache="$dirNpmCache" --nodedir="$dirTempNode" npm@${NPM_VERSION} |
Write-Debug
if ($LASTEXITCODE -ne 0) {
throw "Couldn't install npm@${NPM_VERSION}."
}
# After finishing up with our Node, let's put it in its final home
# and abandon this local npm directory.
# Move exe and cmd files to the \bin directory.
Move-Item $(Join-Path $dirTempNode '*.exe') $dirBin
# Move-Item $(Join-Path $dirTempNode '*.cmd') $dirBin
Move-Item $dirTempNodeHeadersInclude $DIR
Move-Item $dirNodeRelease $DIR
$finalNodeExe = Join-Path $dirBin 'node.exe'
$finalNpmCmd = Join-Path $dirBin 'npm.cmd'
# We use our own npm.cmd.
Copy-Item "${dirCheckout}\scripts\npm.cmd" $finalNpmCmd
Remove-DirectoryRecursively $dirTempNodeHeaders
Remove-DirectoryRecursively $dirTempNode
return New-Object -Type PSObject -Prop $(@{
node = $finalNodeExe
npm = $finalNpmCmd
})
}
Function Add-Mongo {
# Mongo 3.4 no longer supports 32-bit (x86) architectures, so we package
# the latest 3.2 version of Mongo for those builds and 3.4+ for x64.
$mongo_filenames = @{
windows_x86 = "mongodb-win32-i386-${MONGO_VERSION_32BIT}"
windows_x64 = "mongodb-win32-x86_64-2008plus-${MONGO_VERSION_64BIT}"
}
$previousCwd = $PWD
cd "$DIR"
mkdir "$DIR\mongodb"
mkdir "$DIR\mongodb\bin"
$mongo_name = $mongo_filenames.Item($PLATFORM)
$mongo_link = "https://fastdl.mongodb.org/win32/${mongo_name}.zip"
$mongo_zip = "$DIR\mongodb\mongo.zip"
Write-Host "Downloading Mongo from ${mongo_link}..." -ForegroundColor Magenta
$webclient.DownloadFile($mongo_link, $mongo_zip)
Write-Host "Extracting Mongo ${mongo_zip}..." -ForegroundColor Magenta
$zip = $shell.NameSpace($mongo_zip)
foreach($item in $zip.items()) {
$shell.Namespace("$DIR\mongodb").copyhere($item, 0x14) # 0x10 - overwrite, 0x4 - no dialog
}
Write-Host "Putting MongoDB mongod.exe in \mongodb\bin\" -ForegroundColor Magenta
cp "$DIR\mongodb\$mongo_name\bin\mongod.exe" $DIR\mongodb\bin
Write-Host "Putting MongoDB mongo.exe in \mongodb\bin\" -ForegroundColor Magenta
cp "$DIR\mongodb\$mongo_name\bin\mongo.exe" $DIR\mongodb\bin
Write-Host "Removing the old Mongo zip..." -ForegroundColor Magenta
rm -Recurse -Force $mongo_zip
Write-Host "Removing the old Mongo directory..." -ForegroundColor Magenta
rm -Recurse -Force "$DIR\mongodb\$mongo_name"
cd "$previousCwd"
}
Function Add-NpmModulesFromJsBundleFile {
Param (
[Parameter(Mandatory=$True, Position=0)]
[string]$SourceJs,
[Parameter(Mandatory=$True, Position=1)]
[string]$Destination,
[Parameter(Mandatory=$True)]
$Commands,
[bool]$Shrinkwrap = $False
)
$previousCwd = $PWD
If (!(Test-Path $SourceJs)) {
throw "Couldn't find the source: $SourceJs"
}
New-Item -ItemType Directory -Force -Path $Destination | Out-Null
cd "$Destination"
Write-Host "Writing 'package.json' from ${SourceJs} to ${Destination}" `
-ForegroundColor Magenta
& "$($Commands.node)" $SourceJs |
Out-File -FilePath $(Join-Path $Destination 'package.json') -Encoding ascii
# No bin-links because historically, they weren't used anyway.
& "$($Commands.npm)" install
if ($LASTEXITCODE -ne 0) {
throw "Couldn't install npm packages."
}
# As of npm@5, this just renames `package-lock.json` to `npm-shrinkwrap.json`.
if ($Shrinkwrap -eq $True) {
& "$($Commands.npm)" shrinkwrap
if ($LASTEXITCODE -ne 0) {
throw "Couldn't make shrinkwrap."
}
}
cd "$previousCwd"
}
# Install the global 7zip application, if necessary.
if (!(Test-Path "$system7zip")) {
Write-Host "Installing 7-zip since not found at ${system7zip}" `
-ForegroundColor Magenta
Invoke-Install7ZipApplication
}
# Download and install 7zip command-line tool into \bin
Add-7ZipTool
# Download and install Mongo binaries into \bin
Add-Mongo
# Add Python to \bin, and use it for Node Gyp.
$env:PYTHON = Add-Python
# Set additional options for node-gyp
$env:GYP_MSVS_VERSION = "2015"
$env:npm_config_nodedir = "$DIR"
$env:npm_config_cache = "$dirNpmCache"
# Install Node.js and npm and get their paths to use from here on.
$toolCmds = Add-NodeAndNpm
"Node process.versions:"
& "$($toolCmds.node)" -p 'process.versions'
"Npm 'version':"
& "$($toolCmds.npm)" version
npm config set loglevel error
#
# Install the npms for the 'server'.
#
$npmServerArgs = @{
sourceJs = "${dirCheckout}\scripts\dev-bundle-server-package.js"
destination = $dirServerLib
commands = $toolCmds
shrinkwrap = $True
}
Add-NpmModulesFromJsBundleFile @npmServerArgs
# These are used by the Meteor tool bundler and written to the Meteor build.
# For information, see the 'ServerTarget' class in tools/isobuild/bundler.js,
# and look for 'serverPkgJson' and 'npm-shrinkwrap.json'
mkdir -Force "${DIR}\etc"
Move-Item $(Join-Path $dirServerLib 'package.json') "${DIR}\etc\"
Move-Item $(Join-Path $dirServerLib 'npm-shrinkwrap.json') "${DIR}\etc\"
#
# Install the npms for the 'tool'.
#
$npmToolArgs = @{
sourceJs = "${dirCheckout}\scripts\dev-bundle-tool-package.js"
destination = $dirLib
commands = $toolCmds
}
Add-NpmModulesFromJsBundleFile @npmToolArgs
# Leaving these probably doesn't hurt, but are removed for consistency w/ Unix.
Remove-Item $(Join-Path $dirLib 'package.json')
Remove-Item $(Join-Path $dirLib 'package-lock.json')
Write-Host "Done writing node_modules build(s)..." -ForegroundColor Magenta
Write-Host "Removing temp scratch $dirTemp" -ForegroundColor Magenta
Remove-DirectoryRecursively $dirTemp
# mark the version
Write-Host "Writing out the bundle version..." -ForegroundColor Magenta
echo "${BUNDLE_VERSION}" | Out-File $(Join-Path $DIR '.bundle_version.txt') -Encoding ascii
$devBundleName = "dev_bundle_${PLATFORM}_${BUNDLE_VERSION}"
$dirBundlePreArchive = Join-Path $dirCheckout $devBundleName
$devBundleTmpTar = Join-Path $dirCheckout "dev_bundle.tar"
$devBundleTarGz = Join-Path $dirCheckout "${devBundleName}.tar.gz"
# Cleanup from previous builds, if there are things in our way.
Remove-DirectoryRecursively $dirBundlePreArchive
If (Test-Path $devBundleTmpTar) {
Remove-Item -Force $devBundleTmpTar
}
If (Test-Path $devBundleTarGz) {
Remove-Item -Force $devBundleTarGz
}
# Get out of this directory, before we rename it.
cd "$DIR\.."
# rename the folder with the devbundle
Write-Host "Renaming to $dirBundlePreArchive" -ForegroundColor Magenta
Rename-Item "$DIR" $dirBundlePreArchive
Write-Host "Compressing $dirBundlePreArchive to $devBundleTmpTar"
& "$system7zip" a -ttar $devBundleTmpTar $dirBundlePreArchive
if ($LASTEXITCODE -ne 0) {
throw "Failure while building $devBundleTmpTar"
}
if ((Get-Item $devBundleTmpTar).length -lt 50mb) {
throw "Dev bundle .tar is <50mb. If this is correct, update this message!"
}
Write-Host "Compressing $devBundleTmpTar into $devBundleTarGz" `
-ForegroundColor Magenta
& "$system7zip" a -tgzip $devBundleTarGz $devBundleTmpTar
if ($LASTEXITCODE -ne 0) {
throw "Failure while building $devBundleTarGz"
}
if ((Get-Item $devBundleTarGz).length -lt 30mb) {
throw "Dev bundle .tar.gz is <30mb. If this is correct, update this message!"
}
Write-Host "Removing $devBundleTmpTar" -ForegroundColor Magenta
Remove-Item -Force $devBundleTmpTar
Write-Host "Removing the '$devBundleName' temp directory." `
-ForegroundColor Magenta
Remove-DirectoryRecursively $dirBundlePreArchive
Write-Host "Done building Dev Bundle!" -ForegroundColor Green
Exit 0