Commit 9bc97a57 authored by Péter Szilágyi's avatar Péter Szilágyi Committed by Felix Lange

build: NSIS based Windows installer (#3240)

This commit adds support for creating Windows installers to ci.go
parent 6707f70b
os: Visual Studio 2015
# Clone directly into GOPATH.
clone_folder: c:\gopath\src\github.com\ethereum\go-ethereum
clone_folder: C:\gopath\src\github.com\ethereum\go-ethereum
clone_depth: 5
version: "{branch}.{build}"
environment:
global:
GOPATH: c:\gopath
GOPATH: C:\gopath
CC: gcc.exe
matrix:
- GETH_ARCH: amd64
MSYS2_ARCH: x86_64
MSYS2_BITS: 64
MSYSTEM: MINGW64
PATH: C:\msys64\mingw64\bin\;%PATH%
PATH: C:\msys64\mingw64\bin\;C:\Program Files (x86)\NSIS\;%PATH%
- GETH_ARCH: 386
MSYS2_ARCH: i686
MSYS2_BITS: 32
MSYSTEM: MINGW32
PATH: C:\msys64\mingw32\bin\;%PATH%
PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH%
install:
- rmdir c:\go /s /q
- rmdir C:\go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.7.3.windows-amd64.zip
- 7z x go1.7.3.windows-amd64.zip -y -oC:\ > NUL
- go version
- gcc --version
build_script:
- go run build\\ci.go install -arch %GETH_ARCH%
- go run build\ci.go install -arch %GETH_ARCH%
after_build:
- go run build\\ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
- go run build\ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
- go run build\ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds
test_script:
- set GOARCH=%GETH_ARCH%
- set CGO_ENABLED=1
- go run build\\ci.go test -vet -coverage
- go run build\ci.go test -vet -coverage
......@@ -28,6 +28,7 @@ Available commands are:
archive [-arch architecture] [ -type zip|tar ] [ -signer key-envvar ] [ -upload dest ] -- archives build artefacts
importkeys -- imports signing keys from env
debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package
nsis -- creates a Windows NSIS installer
xgo [ options ] -- cross builds according to options
For all commands, -n prevents execution of external programs (dry run mode).
......@@ -122,6 +123,8 @@ func main() {
doArchive(os.Args[2:])
case "debsrc":
doDebianSource(os.Args[2:])
case "nsis":
doWindowsInstaller(os.Args[2:])
case "xgo":
doXgo(os.Args[2:])
default:
......@@ -429,7 +432,7 @@ func makeWorkdir(wdflag string) string {
if wdflag != "" {
err = os.MkdirAll(wdflag, 0744)
} else {
wdflag, err = ioutil.TempDir("", "eth-deb-build-")
wdflag, err = ioutil.TempDir("", "geth-build-")
}
if err != nil {
log.Fatal(err)
......@@ -559,6 +562,76 @@ func stageDebianSource(tmpdir string, meta debMetadata) (pkgdir string) {
return pkgdir
}
// Windows installer
func doWindowsInstaller(cmdline []string) {
// Parse the flags and make skip installer generation on PRs
var (
arch = flag.String("arch", runtime.GOARCH, "Architecture for cross build packaging")
signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. WINDOWS_SIGNING_KEY)`)
upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`)
workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`)
)
flag.CommandLine.Parse(cmdline)
*workdir = makeWorkdir(*workdir)
env := build.Env()
maybeSkipArchive(env)
// Aggregate binaries that are included in the installer
var (
devTools []string
allTools []string
gethTool string
)
for _, file := range allToolsArchiveFiles {
if file == "COPYING" { // license, copied later
continue
}
allTools = append(allTools, filepath.Base(file))
if filepath.Base(file) == "geth.exe" {
gethTool = file
} else {
devTools = append(devTools, file)
}
}
// Render NSIS scripts: Installer NSIS contains two installer sections,
// first section contains the geth binary, second section holds the dev tools.
templateData := map[string]interface{}{
"License": "COPYING",
"Geth": gethTool,
"DevTools": devTools,
}
build.Render("build/nsis.geth.nsi", filepath.Join(*workdir, "geth.nsi"), 0644, nil)
build.Render("build/nsis.install.nsh", filepath.Join(*workdir, "install.nsh"), 0644, templateData)
build.Render("build/nsis.uninstall.nsh", filepath.Join(*workdir, "uninstall.nsh"), 0644, allTools)
build.Render("build/nsis.envvarupdate.nsh", filepath.Join(*workdir, "EnvVarUpdate.nsh"), 0644, nil)
build.CopyFile(filepath.Join(*workdir, "SimpleFC.dll"), "build/nsis.simplefc.dll", 0755)
build.CopyFile(filepath.Join(*workdir, "COPYING"), "COPYING", 0755)
// Build the installer. This assumes that all the needed files have been previously
// built (don't mix building and packaging to keep cross compilation complexity to a
// minimum).
version := strings.Split(build.VERSION(), ".")
if env.Commit != "" {
version[2] += "-" + env.Commit[:8]
}
installer, _ := filepath.Abs("geth-" + archiveBasename(*arch, env) + ".exe")
build.MustRunCommand("makensis.exe",
"/DOUTPUTFILE="+installer,
"/DMAJORVERSION="+version[0],
"/DMINORVERSION="+version[1],
"/DBUILDVERSION="+version[2],
"/DARCH="+*arch,
filepath.Join(*workdir, "geth.nsi"),
)
// Sign and publish installer.
if err := archiveUpload(installer, *upload, *signer); err != nil {
log.Fatal(err)
}
}
// Cross compilation
func doXgo(cmdline []string) {
......
This diff is collapsed.
# Builds a Windows installer with NSIS.
# It expects the following command line arguments:
# - OUTPUTFILE, filename of the installer (without extension)
# - MAJORVERSION, major build version
# - MINORVERSION, minor build version
# - BUILDVERSION, build id version
#
# The created installer executes the following steps:
# 1. install geth for all users
# 2. install optional development tools such as abigen
# 3. create an uninstaller
# 4. configures the Windows firewall for geth
# 5. create geth, attach and uninstall start menu entries
# 6. configures the registry that allows Windows to manage the package through its platform tools
# 7. adds the environment system wide variable ETHEREUM_SOCKET
# 8. adds the install directory to %PATH%
#
# Requirements:
# - NSIS, http://nsis.sourceforge.net/Main_Page
# - SFP, http://nsis.sourceforge.net/NSIS_Simple_Firewall_Plugin (put dll in NSIS\Plugins\x86-ansi)
#
# based on: http://nsis.sourceforge.net/A_simple_installer_with_start_menu_shortcut_and_uninstaller
#
# TODO:
# - sign installer
CRCCheck on
!define GROUPNAME "Ethereum"
!define APPNAME "Geth"
!define DESCRIPTION "Official Go implementation of the Ethereum protocol"
!addplugindir .\
# Require admin rights on NT6+ (When UAC is turned on)
RequestExecutionLevel admin
# Use LZMA compression
SetCompressor /SOLID lzma
!include LogicLib.nsh
!include EnvVarUpdate.nsh
!macro VerifyUserIsAdmin
UserInfo::GetAccountType
pop $0
${If} $0 != "admin" # Require admin rights on NT4+
messageBox mb_iconstop "Administrator rights required!"
setErrorLevel 740 # ERROR_ELEVATION_REQUIRED
quit
${EndIf}
!macroend
function .onInit
# make vars are global for all users since geth is installed global
setShellVarContext all
!insertmacro VerifyUserIsAdmin
${If} ${ARCH} == "amd64"
StrCpy $InstDir "$PROGRAMFILES64\${APPNAME}"
${Else}
StrCpy $InstDir "$PROGRAMFILES32\${APPNAME}"
${Endif}
functionEnd
!include install.nsh
!include uninstall.nsh
Name "geth ${MAJORVERSION}.${MINORVERSION}.${BUILDVERSION}" # VERSION variables set through command line arguments
InstallDir "$InstDir"
OutFile "${OUTPUTFILE}" # set through command line arguments
# Links for "Add/Remove Programs"
!define HELPURL "https://github.com/ethereum/go-ethereum/issues"
!define UPDATEURL "https://github.com/ethereum/go-ethereum/releases"
!define ABOUTURL "https://github.com/ethereum/go-ethereum#ethereum-go"
!define /date NOW "%Y%m%d"
PageEx license
LicenseData {{.License}}
PageExEnd
# Install geth binary
Section "Geth" GETH_IDX
SetOutPath $INSTDIR
file {{.Geth}}
# Create start menu launcher
createDirectory "$SMPROGRAMS\${APPNAME}"
createShortCut "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk" "$INSTDIR\geth.exe" "--fast" "--cache=512"
createShortCut "$SMPROGRAMS\${APPNAME}\Attach.lnk" "$INSTDIR\geth.exe" "attach" "" ""
createShortCut "$SMPROGRAMS\${APPNAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "" ""
# Firewall - remove rules (if exists)
SimpleFC::AdvRemoveRule "Geth incoming peers (TCP:30303)"
SimpleFC::AdvRemoveRule "Geth outgoing peers (TCP:30303)"
SimpleFC::AdvRemoveRule "Geth UDP discovery (UDP:30303)"
# Firewall - add rules
SimpleFC::AdvAddRule "Geth incoming peers (TCP:30303)" "" 6 1 1 2147483647 1 "$INSTDIR\geth.exe" "" "" "Ethereum" 30303 "" "" ""
SimpleFC::AdvAddRule "Geth outgoing peers (TCP:30303)" "" 6 2 1 2147483647 1 "$INSTDIR\geth.exe" "" "" "Ethereum" "" 30303 "" ""
SimpleFC::AdvAddRule "Geth UDP discovery (UDP:30303)" "" 17 2 1 2147483647 1 "$INSTDIR\geth.exe" "" "" "Ethereum" "" 30303 "" ""
# Set default IPC endpoint (https://github.com/ethereum/EIPs/issues/147)
${EnvVarUpdate} $0 "ETHEREUM_SOCKET" "R" "HKLM" "\\.\pipe\geth.ipc"
${EnvVarUpdate} $0 "ETHEREUM_SOCKET" "A" "HKLM" "\\.\pipe\geth.ipc"
# Add geth to PATH
${EnvVarUpdate} $0 "PATH" "A" "HKLM" $INSTDIR
SectionEnd
# Install optional develop tools.
Section /o "Development tools" DEV_TOOLS_IDX
SetOutPath $INSTDIR
{{range .DevTools}}file {{.}}
{{end}}
SectionEnd
# Return on top of stack the total size (as DWORD) of the selected/installed sections.
Var GetInstalledSize.total
Function GetInstalledSize
StrCpy $GetInstalledSize.total 0
${if} ${SectionIsSelected} ${GETH_IDX}
SectionGetSize ${GETH_IDX} $0
IntOp $GetInstalledSize.total $GetInstalledSize.total + $0
${endif}
${if} ${SectionIsSelected} ${DEV_TOOLS_IDX}
SectionGetSize ${DEV_TOOLS_IDX} $0
IntOp $GetInstalledSize.total $GetInstalledSize.total + $0
${endif}
IntFmt $GetInstalledSize.total "0x%08X" $GetInstalledSize.total
Push $GetInstalledSize.total
FunctionEnd
# Write registry, Windows uses these values in various tools such as add/remove program.
# PowerShell: Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, InstallLocation, InstallDate | Format-Table –AutoSize
function .onInstSuccess
# Save information in registry in HKEY_LOCAL_MACHINE branch, Windows add/remove functionality depends on this
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "DisplayName" "${GROUPNAME} - ${APPNAME} - ${DESCRIPTION}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "InstallLocation" "$INSTDIR"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "InstallDate" "${NOW}"
# Wait for Alex
#WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "DisplayIcon" "$\"$INSTDIR\logo.ico$\""
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "Publisher" "${GROUPNAME}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "HelpLink" "${HELPURL}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "URLUpdateInfo" "${UPDATEURL}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "URLInfoAbout" "${ABOUTURL}"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "DisplayVersion" "${MAJORVERSION}.${MINORVERSION}.${BUILDVERSION}"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "VersionMajor" ${MAJORVERSION}
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "VersionMinor" ${MINORVERSION}
# There is no option for modifying or repairing the install
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "NoRepair" 1
Call GetInstalledSize
Pop $0
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}" "EstimatedSize" "$0"
# Create uninstaller
writeUninstaller "$INSTDIR\uninstall.exe"
functionEnd
Page components
Page directory
Page instfiles
Section "Uninstall"
# uninstall for all users
setShellVarContext all
# Delete (optionally) installed files
{{range $}}Delete $INSTDIR\{{.}}
{{end}}
Delete $INSTDIR\uninstall.exe
# Delete install directory
rmDir $INSTDIR
# Delete start menu launcher
Delete "$SMPROGRAMS\${APPNAME}\${APPNAME}.lnk"
Delete "$SMPROGRAMS\${APPNAME}\Attach.lnk"
Delete "$SMPROGRAMS\${APPNAME}\Uninstall.lnk"
rmDir "$SMPROGRAMS\${APPNAME}"
# Firewall - remove rules if exists
SimpleFC::AdvRemoveRule "Geth incoming peers (TCP:30303)"
SimpleFC::AdvRemoveRule "Geth outgoing peers (TCP:30303)"
SimpleFC::AdvRemoveRule "Geth UDP discovery (UDP:30303)"
# Remove IPC endpoint (https://github.com/ethereum/EIPs/issues/147)
${un.EnvVarUpdate} $0 "ETHEREUM_SOCKET" "R" "HKLM" "\\.\pipe\geth.ipc"
# Remove install directory from PATH
${un.EnvVarUpdate} $0 "PATH" "R" "HKLM" $INSTDIR
# Cleanup registry (deletes all sub keys)
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${GROUPNAME} ${APPNAME}"
SectionEnd
......@@ -20,6 +20,7 @@ import (
"bytes"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
......@@ -117,3 +118,25 @@ func render(tpl *template.Template, outputFile string, outputPerm os.FileMode, x
log.Fatal(err)
}
}
// CopyFile copies a file.
func CopyFile(dst, src string, mode os.FileMode) {
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
log.Fatal(err)
}
destFile, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode)
if err != nil {
log.Fatal(err)
}
defer destFile.Close()
srcFile, err := os.Open(src)
if err != nil {
log.Fatal(err)
}
defer srcFile.Close()
if _, err := io.Copy(destFile, srcFile); err != nil {
log.Fatal(err)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment