golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/installer/windowsmsi/windowsmsi.go (about)

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package windowsmsi encodes the process of building a Windows MSI
     6  // installer from the given Go toolchain .tar.gz binary archive.
     7  package windowsmsi
     8  
     9  import (
    10  	"archive/zip"
    11  	"bytes"
    12  	"context"
    13  	"crypto/sha256"
    14  	"errors"
    15  	"fmt"
    16  	"io"
    17  	"net/http"
    18  	"os"
    19  	"os/exec"
    20  	"path/filepath"
    21  	"regexp"
    22  	"strconv"
    23  	"strings"
    24  
    25  	"golang.org/x/build/internal/untar"
    26  )
    27  
    28  // InstallerOptions holds options for constructing the installer.
    29  type InstallerOptions struct {
    30  	GOARCH string // The target GOARCH.
    31  }
    32  
    33  // ConstructInstaller constructs an installer for the provided Go toolchain .tar.gz
    34  // binary archive using workDir as a working directory, and returns the output path.
    35  //
    36  // It's intended to run on a Windows system where the WiX tools can run.
    37  func ConstructInstaller(_ context.Context, workDir, tgzPath string, opt InstallerOptions) (msiPath string, _ error) {
    38  	var errs []error
    39  	if opt.GOARCH == "" {
    40  		errs = append(errs, fmt.Errorf("GOARCH is empty"))
    41  	}
    42  	if err := errors.Join(errs...); err != nil {
    43  		return "", err
    44  	}
    45  
    46  	oldDir, err := os.Getwd()
    47  	if err != nil {
    48  		panic(err)
    49  	}
    50  	if err := os.Chdir(workDir); err != nil {
    51  		panic(err)
    52  	}
    53  	defer func() {
    54  		if err := os.Chdir(oldDir); err != nil {
    55  			panic(err)
    56  		}
    57  	}()
    58  
    59  	fmt.Println("Extracting the Go toolchain .tar.gz binary archive.")
    60  	putTar(tgzPath, ".")
    61  	version := readVERSION("go")
    62  
    63  	fmt.Println("\nInstalling WiX tools.")
    64  	const wixDir = "wix"
    65  	switch opt.GOARCH {
    66  	default:
    67  		if err := installWix(wixRelease311, wixDir); err != nil {
    68  			return "", err
    69  		}
    70  	case "arm", "arm64":
    71  		if err := installWix(wixRelease314, wixDir); err != nil {
    72  			return "", err
    73  		}
    74  	}
    75  
    76  	fmt.Println("\nWriting out installer data used by the packaging process.")
    77  	const winDir = "windows"
    78  	if err := writeDataFiles(windowsData, winDir); err != nil {
    79  		return "", err
    80  	}
    81  
    82  	fmt.Println("\nGathering files (running wix heat).")
    83  	const goDir = "go"
    84  	appFiles := filepath.Join(winDir, "AppFiles.wxs")
    85  	if err := run(filepath.Join(wixDir, "heat"),
    86  		"dir", goDir,
    87  		"-nologo",
    88  		"-gg", "-g1", "-srd", "-sfrag", "-sreg",
    89  		"-cg", "AppFiles",
    90  		"-template", "fragment",
    91  		"-dr", "INSTALLDIR",
    92  		"-var", "var.SourceDir",
    93  		"-out", appFiles,
    94  	); err != nil {
    95  		return "", err
    96  	}
    97  
    98  	fmt.Println("\nBuilding package (running wix candle).")
    99  	verMajor, verMinor, err := splitVersion(version)
   100  	if err != nil {
   101  		return "", fmt.Errorf("failed to split version %q: %v", version, err)
   102  	}
   103  	var msArch string
   104  	switch opt.GOARCH {
   105  	case "386":
   106  		msArch = "x86"
   107  	case "amd64":
   108  		msArch = "x64"
   109  	case "arm":
   110  		// Historically the installer for the windows/arm port
   111  		// used the same value as for the windows/arm64 port.
   112  		fallthrough
   113  	case "arm64":
   114  		msArch = "arm64"
   115  	default:
   116  		panic("unknown arch for windows " + opt.GOARCH)
   117  	}
   118  	if err := run(filepath.Join(wixDir, "candle"),
   119  		"-nologo",
   120  		"-arch", msArch,
   121  		"-dGoVersion="+version,
   122  		fmt.Sprintf("-dGoMajorVersion=%v", verMajor),
   123  		fmt.Sprintf("-dWixGoVersion=1.%v.%v", verMajor, verMinor),
   124  		"-dArch="+opt.GOARCH,
   125  		"-dSourceDir="+goDir,
   126  		filepath.Join(winDir, "installer.wxs"),
   127  		appFiles,
   128  	); err != nil {
   129  		return "", err
   130  	}
   131  
   132  	fmt.Println("\nLinking the .msi installer (running wix light).")
   133  	if err := os.Mkdir("msi-out", 0755); err != nil {
   134  		return "", err
   135  	}
   136  	if err := run(filepath.Join(wixDir, "light"),
   137  		"-b", winDir,
   138  		"-nologo",
   139  		"-dcl:high",
   140  		"-ext", "WixUIExtension",
   141  		"-ext", "WixUtilExtension",
   142  		"AppFiles.wixobj",
   143  		"installer.wixobj",
   144  		"-o", filepath.Join("msi-out", version+"-unsigned.msi"),
   145  	); err != nil {
   146  		return "", err
   147  	}
   148  
   149  	return filepath.Join(workDir, "msi-out", version+"-unsigned.msi"), nil
   150  }
   151  
   152  type wixRelease struct {
   153  	BinaryURL string
   154  	SHA256    string
   155  }
   156  
   157  var (
   158  	wixRelease311 = wixRelease{
   159  		BinaryURL: "https://storage.googleapis.com/go-builder-data/wix311-binaries.zip",
   160  		SHA256:    "da034c489bd1dd6d8e1623675bf5e899f32d74d6d8312f8dd125a084543193de",
   161  	}
   162  	wixRelease314 = wixRelease{
   163  		BinaryURL: "https://storage.googleapis.com/go-builder-data/wix314-binaries.zip",
   164  		SHA256:    "34dcbba9952902bfb710161bd45ee2e721ffa878db99f738285a21c9b09c6edb", // WiX v3.14.0.4118 release, SHA 256 of wix314-binaries.zip from https://wixtoolset.org/releases/v3-14-0-4118/.
   165  	}
   166  )
   167  
   168  // installWix fetches and installs the wix toolkit to the specified path.
   169  func installWix(wix wixRelease, path string) error {
   170  	// Fetch wix binary zip file.
   171  	body, err := httpGet(wix.BinaryURL)
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	// Verify sha256.
   177  	sum := sha256.Sum256(body)
   178  	if fmt.Sprintf("%x", sum) != wix.SHA256 {
   179  		return errors.New("sha256 mismatch for wix toolkit")
   180  	}
   181  
   182  	// Unzip to path.
   183  	zr, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
   184  	if err != nil {
   185  		return err
   186  	}
   187  	for _, f := range zr.File {
   188  		name := filepath.FromSlash(f.Name)
   189  		err := os.MkdirAll(filepath.Join(path, filepath.Dir(name)), 0755)
   190  		if err != nil {
   191  			return err
   192  		}
   193  		rc, err := f.Open()
   194  		if err != nil {
   195  			return err
   196  		}
   197  		b, err := io.ReadAll(rc)
   198  		rc.Close()
   199  		if err != nil {
   200  			return err
   201  		}
   202  		err = os.WriteFile(filepath.Join(path, name), b, 0644)
   203  		if err != nil {
   204  			return err
   205  		}
   206  	}
   207  
   208  	return nil
   209  }
   210  
   211  func httpGet(url string) ([]byte, error) {
   212  	r, err := http.Get(url)
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	body, err := io.ReadAll(r.Body)
   217  	r.Body.Close()
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  	if r.StatusCode != 200 {
   222  		return nil, errors.New(r.Status)
   223  	}
   224  	return body, nil
   225  }
   226  
   227  func putTar(tgz, dir string) {
   228  	f, err := os.Open(tgz)
   229  	if err != nil {
   230  		panic(err)
   231  	}
   232  	err = untar.Untar(f, dir)
   233  	if err != nil {
   234  		panic(err)
   235  	}
   236  	err = f.Close()
   237  	if err != nil {
   238  		panic(err)
   239  	}
   240  }
   241  
   242  // run runs the specified command.
   243  // It prints the command line.
   244  func run(name string, args ...string) error {
   245  	fmt.Printf("$ %s %s\n", name, strings.Join(args, " "))
   246  	cmd := exec.Command(name, args...)
   247  	cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
   248  	return cmd.Run()
   249  }
   250  
   251  var versionRE = regexp.MustCompile(`^go1\.(\d+(\.\d+)?)`)
   252  
   253  // splitVersion splits a Go version string such as "go1.23.4" or "go1.24rc1"
   254  // (as matched by versionRE) into its parts: major and minor.
   255  func splitVersion(v string) (major, minor int, _ error) {
   256  	m := versionRE.FindStringSubmatch(v)
   257  	if len(m) < 2 {
   258  		return 0, 0, fmt.Errorf("no regexp match")
   259  	}
   260  	parts := strings.Split(m[1], ".")
   261  	major, err := strconv.Atoi(parts[0])
   262  	if err != nil {
   263  		return 0, 0, fmt.Errorf("parsing major part: %v", err)
   264  	}
   265  	if len(parts) >= 2 {
   266  		var err error
   267  		minor, err = strconv.Atoi(parts[1])
   268  		if err != nil {
   269  			return 0, 0, fmt.Errorf("parsing minor part: %v", err)
   270  		}
   271  	}
   272  	return major, minor, nil
   273  }
   274  
   275  const storageBase = "https://storage.googleapis.com/go-builder-data/release/"
   276  
   277  // writeDataFiles writes the files in the provided map to the provided base
   278  // directory. If the map value is a URL it fetches the data at that URL and
   279  // uses it as the file contents.
   280  func writeDataFiles(data map[string]string, base string) error {
   281  	for name, body := range data {
   282  		dst := filepath.Join(base, name)
   283  		err := os.MkdirAll(filepath.Dir(dst), 0755)
   284  		if err != nil {
   285  			return err
   286  		}
   287  		b := []byte(body)
   288  		if strings.HasPrefix(body, storageBase) {
   289  			b, err = httpGet(body)
   290  			if err != nil {
   291  				return err
   292  			}
   293  		}
   294  		// (We really mean 0755 on the next line; some of these files
   295  		// are executable, and there's no harm in making them all so.)
   296  		if err := os.WriteFile(dst, b, 0755); err != nil {
   297  			return err
   298  		}
   299  	}
   300  	return nil
   301  }
   302  
   303  var windowsData = map[string]string{
   304  
   305  	"installer.wxs": `<?xml version="1.0" encoding="UTF-8"?>
   306  <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
   307  <!--
   308  # Copyright 2010 The Go Authors. All rights reserved.
   309  # Use of this source code is governed by a BSD-style
   310  # license that can be found in the LICENSE file.
   311  -->
   312  
   313  <?if $(var.Arch) = 386 ?>
   314    <?define UpgradeCode = {1C3114EA-08C3-11E1-9095-7FCA4824019B} ?>
   315    <?define InstallerVersion="300" ?>
   316    <?define SysFolder=SystemFolder ?>
   317    <?define ArchProgramFilesFolder="ProgramFilesFolder" ?>
   318  <?elseif $(var.Arch) = arm64 ?>
   319    <?define UpgradeCode = {21ade9a3-3fdd-4ba6-bea6-c85abadc9488} ?>
   320    <?define InstallerVersion="500" ?>
   321    <?define SysFolder=System64Folder ?>
   322    <?define ArchProgramFilesFolder="ProgramFiles64Folder" ?>
   323  <?else?>
   324    <?define UpgradeCode = {22ea7650-4ac6-4001-bf29-f4b8775db1c0} ?>
   325    <?define InstallerVersion="300" ?>
   326    <?define SysFolder=System64Folder ?>
   327    <?define ArchProgramFilesFolder="ProgramFiles64Folder" ?>
   328  <?endif?>
   329  
   330  <Product
   331      Id="*"
   332      Name="Go Programming Language $(var.Arch) $(var.GoVersion)"
   333      Language="1033"
   334      Version="$(var.WixGoVersion)"
   335      Manufacturer="https://go.dev"
   336      UpgradeCode="$(var.UpgradeCode)" >
   337  
   338  <Package
   339      Id='*'
   340      Keywords='Installer'
   341      Description="The Go Programming Language Installer"
   342      Comments="The Go programming language is an open source project to make programmers more productive."
   343      InstallerVersion="$(var.InstallerVersion)"
   344      Compressed="yes"
   345      InstallScope="perMachine"
   346      Languages="1033" />
   347  
   348  <Property Id="ARPCOMMENTS" Value="The Go programming language is a fast, statically typed, compiled language that feels like a dynamically typed, interpreted language." />
   349  <Property Id="ARPCONTACT" Value="golang-nuts@googlegroups.com" />
   350  <Property Id="ARPHELPLINK" Value="https://go.dev/help" />
   351  <Property Id="ARPREADME" Value="https://go.dev" />
   352  <Property Id="ARPURLINFOABOUT" Value="https://go.dev" />
   353  <Property Id="LicenseAccepted">1</Property>
   354  <Icon Id="gopher.ico" SourceFile="images\gopher.ico"/>
   355  <Property Id="ARPPRODUCTICON" Value="gopher.ico" />
   356  <Property Id="EXISTING_GOLANG_INSTALLED">
   357    <RegistrySearch Id="installed" Type="raw" Root="HKCU" Key="Software\GoProgrammingLanguage" Name="installed" />
   358  </Property>
   359  <MediaTemplate EmbedCab="yes" CompressionLevel="high" MaximumUncompressedMediaSize="10" />
   360  <?if $(var.GoMajorVersion) < 21 ?>
   361  <Condition Message="Windows 7 (with Service Pack 1) or greater required.">
   362      ((VersionNT > 601) OR (VersionNT = 601 AND ServicePackLevel >= 1))
   363  </Condition>
   364  <?else?>
   365  <Condition Message="Windows 10 or greater required.">
   366  <!-- In true MS fashion, Windows 10 pretends to be windows 8.1.
   367  	See https://learn.microsoft.com/en-us/troubleshoot/windows-client/application-management/versionnt-value-for-windows-10-server .
   368  	Workarounds exist, but seem difficult/flaky.
   369  	1) We could build a "bootstrapper" with wix burn, but then we'll be building .exes and there might be implications to that.
   370  	2) We can try one of the things listed here: https://stackoverflow.com/q/31932646 but that takes us back to https://github.com/wixtoolset/issues/issues/5824 and needing a bootstrapper.
   371  	So we're stuck with checking for 8.1.
   372  -->
   373      (VersionNT >= 603)
   374  </Condition>
   375  <?endif?>
   376  <MajorUpgrade AllowDowngrades="yes" />
   377  
   378  <CustomAction
   379      Id="SetApplicationRootDirectory"
   380      Property="ARPINSTALLLOCATION"
   381      Value="[INSTALLDIR]" />
   382  
   383  <!-- Define the directory structure and environment variables -->
   384  <Directory Id="TARGETDIR" Name="SourceDir">
   385    <Directory Id="$(var.ArchProgramFilesFolder)">
   386      <Directory Id="INSTALLDIR" Name="Go"/>
   387    </Directory>
   388    <Directory Id="ProgramMenuFolder">
   389      <Directory Id="GoProgramShortcutsDir" Name="Go Programming Language"/>
   390    </Directory>
   391    <Directory Id="EnvironmentEntries">
   392      <Directory Id="GoEnvironmentEntries" Name="Go Programming Language"/>
   393    </Directory>
   394  </Directory>
   395  
   396  <!-- Programs Menu Shortcuts -->
   397  <DirectoryRef Id="GoProgramShortcutsDir">
   398    <Component Id="Component_GoProgramShortCuts" Guid="{f5fbfb5e-6c5c-423b-9298-21b0e3c98f4b}">
   399      <Shortcut
   400          Id="UninstallShortcut"
   401          Name="Uninstall Go"
   402          Description="Uninstalls Go and all of its components"
   403          Target="[$(var.SysFolder)]msiexec.exe"
   404          Arguments="/x [ProductCode]" />
   405      <RemoveFolder
   406          Id="GoProgramShortcutsDir"
   407          On="uninstall" />
   408      <RegistryValue
   409          Root="HKCU"
   410          Key="Software\GoProgrammingLanguage"
   411          Name="ShortCuts"
   412          Type="integer"
   413          Value="1"
   414          KeyPath="yes" />
   415    </Component>
   416  </DirectoryRef>
   417  
   418  <!-- Registry & Environment Settings -->
   419  <DirectoryRef Id="GoEnvironmentEntries">
   420    <Component Id="Component_GoEnvironment" Guid="{3ec7a4d5-eb08-4de7-9312-2df392c45993}">
   421      <RegistryKey
   422          Root="HKCU"
   423          Key="Software\GoProgrammingLanguage">
   424              <RegistryValue
   425                  Name="installed"
   426                  Type="integer"
   427                  Value="1"
   428                  KeyPath="yes" />
   429              <RegistryValue
   430                  Name="installLocation"
   431                  Type="string"
   432                  Value="[INSTALLDIR]" />
   433      </RegistryKey>
   434      <Environment
   435          Id="GoPathEntry"
   436          Action="set"
   437          Part="last"
   438          Name="PATH"
   439          Permanent="no"
   440          System="yes"
   441          Value="[INSTALLDIR]bin" />
   442      <Environment
   443          Id="UserGoPath"
   444          Action="create"
   445          Name="GOPATH"
   446          Permanent="no"
   447          Value="%USERPROFILE%\go" />
   448      <Environment
   449          Id="UserGoPathEntry"
   450          Action="set"
   451          Part="last"
   452          Name="PATH"
   453          Permanent="no"
   454          Value="%USERPROFILE%\go\bin" />
   455      <RemoveFolder
   456          Id="GoEnvironmentEntries"
   457          On="uninstall" />
   458    </Component>
   459  </DirectoryRef>
   460  
   461  <!-- Install the files -->
   462  <Feature
   463      Id="GoTools"
   464      Title="Go"
   465      Level="1">
   466        <ComponentRef Id="Component_GoEnvironment" />
   467        <ComponentGroupRef Id="AppFiles" />
   468        <ComponentRef Id="Component_GoProgramShortCuts" />
   469  </Feature>
   470  
   471  <!-- Update the environment -->
   472  <InstallExecuteSequence>
   473      <Custom Action="SetApplicationRootDirectory" Before="InstallFinalize" />
   474  </InstallExecuteSequence>
   475  
   476  <!-- Notify top level applications of the new PATH variable (go.dev/issue/18680)  -->
   477  <CustomActionRef Id="WixBroadcastEnvironmentChange" />
   478  
   479  <!-- Include the user interface -->
   480  <WixVariable Id="WixUILicenseRtf" Value="LICENSE.rtf" />
   481  <WixVariable Id="WixUIBannerBmp" Value="images\Banner.jpg" />
   482  <WixVariable Id="WixUIDialogBmp" Value="images\Dialog.jpg" />
   483  <Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
   484  <UIRef Id="Golang_InstallDir" />
   485  <UIRef Id="WixUI_ErrorProgressText" />
   486  
   487  </Product>
   488  <Fragment>
   489    <!--
   490      The installer steps are modified so we can get user confirmation to uninstall an existing golang installation.
   491  
   492      WelcomeDlg  [not installed]  =>                  LicenseAgreementDlg => InstallDirDlg  ..
   493                  [installed]      => OldVersionDlg => LicenseAgreementDlg => InstallDirDlg  ..
   494    -->
   495    <UI Id="Golang_InstallDir">
   496      <!-- style -->
   497      <TextStyle Id="WixUI_Font_Normal" FaceName="Tahoma" Size="8" />
   498      <TextStyle Id="WixUI_Font_Bigger" FaceName="Tahoma" Size="12" />
   499      <TextStyle Id="WixUI_Font_Title" FaceName="Tahoma" Size="9" Bold="yes" />
   500  
   501      <Property Id="DefaultUIFont" Value="WixUI_Font_Normal" />
   502      <Property Id="WixUI_Mode" Value="InstallDir" />
   503  
   504      <!-- dialogs -->
   505      <DialogRef Id="BrowseDlg" />
   506      <DialogRef Id="DiskCostDlg" />
   507      <DialogRef Id="ErrorDlg" />
   508      <DialogRef Id="FatalError" />
   509      <DialogRef Id="FilesInUse" />
   510      <DialogRef Id="MsiRMFilesInUse" />
   511      <DialogRef Id="PrepareDlg" />
   512      <DialogRef Id="ProgressDlg" />
   513      <DialogRef Id="ResumeDlg" />
   514      <DialogRef Id="UserExit" />
   515      <Dialog Id="OldVersionDlg" Width="240" Height="95" Title="[ProductName] Setup" NoMinimize="yes">
   516        <Control Id="Text" Type="Text" X="28" Y="15" Width="194" Height="50">
   517          <Text>A previous version of Go Programming Language is currently installed. By continuing the installation this version will be uninstalled. Do you want to continue?</Text>
   518        </Control>
   519        <Control Id="Exit" Type="PushButton" X="123" Y="67" Width="62" Height="17"
   520          Default="yes" Cancel="yes" Text="No, Exit">
   521          <Publish Event="EndDialog" Value="Exit">1</Publish>
   522        </Control>
   523        <Control Id="Next" Type="PushButton" X="55" Y="67" Width="62" Height="17" Text="Yes, Uninstall">
   524          <Publish Event="EndDialog" Value="Return">1</Publish>
   525        </Control>
   526      </Dialog>
   527  
   528      <!-- wizard steps -->
   529      <Publish Dialog="BrowseDlg" Control="OK" Event="DoAction" Value="WixUIValidatePath" Order="3">1</Publish>
   530      <Publish Dialog="BrowseDlg" Control="OK" Event="SpawnDialog" Value="InvalidDirDlg" Order="4"><![CDATA[NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"]]></Publish>
   531  
   532      <Publish Dialog="ExitDialog" Control="Finish" Event="EndDialog" Value="Return" Order="999">1</Publish>
   533  
   534      <Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="OldVersionDlg"><![CDATA[EXISTING_GOLANG_INSTALLED << "#1"]]> </Publish>
   535      <Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="LicenseAgreementDlg"><![CDATA[NOT (EXISTING_GOLANG_INSTALLED << "#1")]]></Publish>
   536  
   537      <Publish Dialog="OldVersionDlg" Control="Next" Event="NewDialog" Value="LicenseAgreementDlg">1</Publish>
   538  
   539      <Publish Dialog="LicenseAgreementDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg">1</Publish>
   540      <Publish Dialog="LicenseAgreementDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg">LicenseAccepted = "1"</Publish>
   541  
   542      <Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="LicenseAgreementDlg">1</Publish>
   543      <Publish Dialog="InstallDirDlg" Control="Next" Event="SetTargetPath" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
   544      <Publish Dialog="InstallDirDlg" Control="Next" Event="DoAction" Value="WixUIValidatePath" Order="2">NOT WIXUI_DONTVALIDATEPATH</Publish>
   545      <Publish Dialog="InstallDirDlg" Control="Next" Event="SpawnDialog" Value="InvalidDirDlg" Order="3"><![CDATA[NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"]]></Publish>
   546      <Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="4">WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1"</Publish>
   547      <Publish Dialog="InstallDirDlg" Control="ChangeFolder" Property="_BrowseProperty" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
   548      <Publish Dialog="InstallDirDlg" Control="ChangeFolder" Event="SpawnDialog" Value="BrowseDlg" Order="2">1</Publish>
   549  
   550      <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="InstallDirDlg" Order="1">NOT Installed</Publish>
   551      <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MaintenanceTypeDlg" Order="2">Installed AND NOT PATCH</Publish>
   552      <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">Installed AND PATCH</Publish>
   553  
   554      <Publish Dialog="MaintenanceWelcomeDlg" Control="Next" Event="NewDialog" Value="MaintenanceTypeDlg">1</Publish>
   555  
   556      <Publish Dialog="MaintenanceTypeDlg" Control="RepairButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
   557      <Publish Dialog="MaintenanceTypeDlg" Control="RemoveButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
   558      <Publish Dialog="MaintenanceTypeDlg" Control="Back" Event="NewDialog" Value="MaintenanceWelcomeDlg">1</Publish>
   559  
   560      <Property Id="ARPNOMODIFY" Value="1" />
   561    </UI>
   562  
   563    <UIRef Id="WixUI_Common" />
   564  </Fragment>
   565  </Wix>
   566  `,
   567  
   568  	"LICENSE.rtf":           storageBase + "windows/LICENSE.rtf",
   569  	"images/Banner.jpg":     storageBase + "windows/Banner.jpg",
   570  	"images/Dialog.jpg":     storageBase + "windows/Dialog.jpg",
   571  	"images/DialogLeft.jpg": storageBase + "windows/DialogLeft.jpg",
   572  	"images/gopher.ico":     storageBase + "windows/gopher.ico",
   573  }
   574  
   575  // readVERSION reads the VERSION file and
   576  // returns the first line of the file, the Go version.
   577  func readVERSION(goroot string) (version string) {
   578  	b, err := os.ReadFile(filepath.Join(goroot, "VERSION"))
   579  	if err != nil {
   580  		panic(err)
   581  	}
   582  	version, _, _ = strings.Cut(string(b), "\n")
   583  	return version
   584  }