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 }