Skip to content

Commit 213b326

Browse files
committed
Merge branch 'feature/native-windows-packaging'
2 parents 9b43a0f + dcfb067 commit 213b326

File tree

6 files changed

+175
-65
lines changed

6 files changed

+175
-65
lines changed

assets/packaging/windows-msi/app.wxs.tmpl

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
3-
<Product Id="*" UpgradeCode="*" Version="{{.version}}" Language="1033" Name="{{.applicationName}}" Manufacturer="{{.author}}">
3+
<Product Id="*" UpgradeCode="{{.upgradeCode}}" Version="{{.version}}" Language="1033" Name="{{.applicationName}}" Manufacturer="{{.author}}">
44
<Package InstallerVersion="300" Compressed="yes"/>
55
<Media Id="1" Cabinet="{{.packageName}}.cab" EmbedCab="yes" />
66
<Directory Id="TARGETDIR" Name="SourceDir">
@@ -16,21 +16,25 @@
1616
<Directory Id="ApplicationProgramsFolder" Name="{{.applicationName}}"/>
1717
</Directory>
1818
</Directory>
19-
<Icon Id="ShortcutIcon" SourceFile="build/assets/icon.ico"/>
19+
<Icon Id="ShortcutIcon" SourceFile="build{{.pathSeparator}}assets{{.pathSeparator}}icon.ico"/>
20+
<Property Id="ARPPRODUCTICON" Value="ShortcutIcon"/>
2021
<DirectoryRef Id="APPLICATIONROOTDIRECTORY">
2122
<Component Id="{{.executableName}}.exe" Guid="*">
22-
<File Id="{{.executableName}}.exe" Source="build/{{.executableName}}.exe" KeyPath="yes"/>
23+
<File Id="{{.executableName}}.exe" Source="build{{.pathSeparator}}{{.executableName}}.exe" KeyPath="yes"/>
2324
</Component>
2425
<Component Id="flutter_engine.dll" Guid="*">
25-
<File Id="flutter_engine.dll" Source="build/flutter_engine.dll" KeyPath="yes"/>
26+
<File Id="flutter_engine.dll" Source="build{{.pathSeparator}}flutter_engine.dll" KeyPath="yes"/>
2627
</Component>
2728
<Component Id="icudtl.dat" Guid="*">
28-
<File Id="icudtl.dat" Source="build/icudtl.dat" KeyPath="yes"/>
29+
<File Id="icudtl.dat" Source="build{{.pathSeparator}}icudtl.dat" KeyPath="yes"/>
2930
</Component>
3031
</DirectoryRef>
3132
<DirectoryRef Id="ASSETSDIRECTORY">
3233
<Component Id="icon.png" Guid="*">
33-
<File Id="icon.png" Source="build/assets/icon.png" KeyPath="yes"/>
34+
<File Id="icon.png" Source="build{{.pathSeparator}}assets{{.pathSeparator}}icon.png" KeyPath="yes"/>
35+
</Component>
36+
<Component Id="icon.ico" Guid="*">
37+
<File Id="icon.ico" Source="build{{.pathSeparator}}assets{{.pathSeparator}}icon.ico" KeyPath="yes"/>
3438
</Component>
3539
</DirectoryRef>
3640
<?include directory_refs.wxi ?>

cmd/packaging/packaging.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ type packagingTask struct {
6262
linuxDesktopFileExecutablePath string // Path of the executable for linux .desktop file (only set on linux)
6363
linuxDesktopFileIconPath string // Path of the icon for linux .desktop file (only set on linux)
6464
generateBuildFiles func(packageName, path string) // Generate dynamic build files. Operates in the temporary directory
65+
generateInitFiles func(packageName, path string) // Generate dynamic init files
66+
extraTemplateData func(packageName, path string) map[string]string // Update the template data on build. This is used for inserting values that are generated on init
6567
flutterBuildOutputDirectory string // Path to copy the build output of the app to. Operates in the temporary directory
6668
packagingFunction func(tmpPath, applicationName, strippedApplicationName, packageName, executableName, version, release string) (string, error) // Function that actually packages the app. Needs to check for OS specific tools etc. . Returns the path of the packaged file
6769
skipAssertInitialized bool // Set to true when a task doesn't need to be initialized.
@@ -168,6 +170,10 @@ func (t *packagingTask) init(ignoreAlreadyExists bool) {
168170
}
169171
fileutils.CopyAsset(fmt.Sprintf("packaging/%s", sourceFile), destinationFile, fileutils.AssetsBox())
170172
}
173+
if t.generateInitFiles != nil {
174+
log.Infof("Generating dynamic init files")
175+
t.generateInitFiles(config.GetConfig().GetPackageName(projectName), dir)
176+
}
171177
log.Infof("go/packaging/%s has been created. You can modify the configuration files and add it to git.", t.packagingFormatName)
172178
log.Infof(fmt.Sprintf("You now can package the %s using `%s`", strings.Split(t.packagingFormatName, "-")[0], log.Au().Magenta("hover build "+t.packagingFormatName)))
173179
} else if !ignoreAlreadyExists {
@@ -182,8 +188,13 @@ func (t *packagingTask) Pack(fullVersion string) {
182188
}
183189

184190
func (t *packagingTask) pack(fullVersion string) {
191+
if t.extraTemplateData != nil {
192+
for key, value := range t.extraTemplateData(packageName, packagingFormatPath(t.packagingFormatName)) {
193+
templateData[key] = value
194+
}
195+
}
185196
for task := range t.dependsOn {
186-
task.Pack(fullVersion)
197+
task.pack(fullVersion)
187198
}
188199
tmpPath := getTemporaryBuildDirectory(projectName, t.packagingFormatName)
189200
defer func() {

cmd/packaging/windows-msi.go

Lines changed: 118 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
package packaging
22

33
import (
4+
"crypto/rand"
5+
"crypto/sha1"
6+
"encoding/hex"
47
"fmt"
8+
"image/png"
59
"io/ioutil"
610
"os"
711
"os/exec"
812
"path/filepath"
13+
"runtime"
914
"strings"
1015

16+
ico "github.com/Kodeworks/golang-image-ico"
17+
1118
"github.com/go-flutter-desktop/hover/internal/log"
1219
)
1320

@@ -24,26 +31,101 @@ var WindowsMsiTask = &packagingTask{
2431
flutterBuildOutputDirectory: "build",
2532
packagingFunction: func(tmpPath, applicationName, strippedApplicationName, packageName, executableName, version, release string) (string, error) {
2633
outputFileName := fmt.Sprintf("%s %s.msi", applicationName, version)
27-
cmdConvert := exec.Command("convert", "-resize", "x16", "build/assets/icon.png", "build/assets/icon.ico")
28-
cmdConvert.Dir = tmpPath
29-
cmdConvert.Stdout = os.Stdout
30-
cmdConvert.Stderr = os.Stderr
31-
err := cmdConvert.Run()
34+
iconPngFile, err := os.Open(filepath.Join(tmpPath, "build", "assets", "icon.png"))
3235
if err != nil {
3336
return "", err
3437
}
35-
cmdWixl := exec.Command("wixl", "-v", fmt.Sprintf("%s.wxs", packageName), "-o", outputFileName)
36-
cmdWixl.Dir = tmpPath
37-
cmdWixl.Stdout = os.Stdout
38-
cmdWixl.Stderr = os.Stderr
39-
err = cmdWixl.Run()
38+
pngImage, err := png.Decode(iconPngFile)
4039
if err != nil {
4140
return "", err
4241
}
42+
// We can't defer it, because windows reports that the file is used by another program
43+
err = iconPngFile.Close()
44+
if err != nil {
45+
return "", err
46+
}
47+
iconIcoFile, err := os.Create(filepath.Join(tmpPath, "build", "assets", "icon.ico"))
48+
if err != nil {
49+
return "", err
50+
}
51+
err = ico.Encode(iconIcoFile, pngImage)
52+
if err != nil {
53+
return "", err
54+
}
55+
// We can't defer it, because windows reports that the file is used by another program
56+
err = iconIcoFile.Close()
57+
if err != nil {
58+
return "", err
59+
}
60+
switch runtime.GOOS {
61+
case "windows":
62+
cmdCandle := exec.Command("candle", fmt.Sprintf("%s.wxs", packageName))
63+
cmdCandle.Dir = tmpPath
64+
cmdCandle.Stdout = os.Stdout
65+
cmdCandle.Stderr = os.Stderr
66+
err = cmdCandle.Run()
67+
if err != nil {
68+
return "", err
69+
}
70+
cmdLight := exec.Command("light", fmt.Sprintf("%s.wixobj", packageName), "-sval")
71+
cmdLight.Dir = tmpPath
72+
cmdLight.Stdout = os.Stdout
73+
cmdLight.Stderr = os.Stderr
74+
err = cmdLight.Run()
75+
if err != nil {
76+
return "", err
77+
}
78+
err = os.Rename(filepath.Join(tmpPath, fmt.Sprintf("%s.msi", packageName)), filepath.Join(tmpPath, outputFileName))
79+
if err != nil {
80+
return "", err
81+
}
82+
case "linux":
83+
cmdWixl := exec.Command("wixl", "-v", fmt.Sprintf("%s.wxs", packageName), "-o", outputFileName)
84+
cmdWixl.Dir = tmpPath
85+
cmdWixl.Stdout = os.Stdout
86+
cmdWixl.Stderr = os.Stderr
87+
err = cmdWixl.Run()
88+
if err != nil {
89+
return "", err
90+
}
91+
default:
92+
panic("should be unreachable")
93+
}
4394
return outputFileName, nil
4495
},
4596
requiredTools: map[string][]string{
46-
"linux": {"convert", "wixl"},
97+
"windows": {"candle", "light"},
98+
"linux": {"wixl"},
99+
},
100+
generateInitFiles: func(packageName, path string) {
101+
b := make([]byte, 16)
102+
_, err := rand.Read(b)
103+
if err != nil {
104+
log.Errorf("Failed to generate GUID: %v", err)
105+
os.Exit(1)
106+
}
107+
upgradeCode := strings.ToUpper(fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]))
108+
err = ioutil.WriteFile(filepath.Join(path, "upgrade-code.txt"), []byte(fmt.Sprintf("%s\n# This GUID is your upgrade code and ensures that you can properly update your app.\n# Don't change it.", upgradeCode)), 0755)
109+
if err != nil {
110+
log.Errorf("Failed to create `upgrade-code.txt` file: %v", err)
111+
os.Exit(1)
112+
}
113+
},
114+
extraTemplateData: func(packageName, path string) map[string]string {
115+
data, err := ioutil.ReadFile(filepath.Join(path, "upgrade-code.txt"))
116+
if err != nil {
117+
log.Errorf("Failed to read `go/packaging/windows-msi/upgrade-code.txt`: %v", err)
118+
if os.IsNotExist(err) {
119+
log.Errorf("Please re-init windows-msi to generate the `go/packaging/windows-msi/upgrade-code.txt`")
120+
log.Errorf("or put a GUID from https://www.guidgen.com/ into a new `go/packaging/windows-msi/upgrade-code.txt` file.")
121+
}
122+
os.Exit(1)
123+
}
124+
guid := strings.Split(string(data), "\n")[0]
125+
return map[string]string{
126+
"upgradeCode": guid,
127+
"pathSeparator": string(os.PathSeparator),
128+
}
47129
},
48130
generateBuildFiles: func(packageName, tmpPath string) {
49131
directoriesFilePath, err := filepath.Abs(filepath.Join(tmpPath, "directories.wxi"))
@@ -76,13 +158,13 @@ var WindowsMsiTask = &packagingTask{
76158
log.Errorf("Failed to create component_refs.wxi file %s: %v", packageName, err)
77159
os.Exit(1)
78160
}
79-
directoriesFileContent = append(directoriesFileContent, `<Include>`)
80-
directoryRefsFileContent = append(directoryRefsFileContent, `<Include>`)
81-
componentRefsFileContent = append(componentRefsFileContent, `<Include>`)
161+
directoriesFileContent = append(directoriesFileContent, "<Include>")
162+
directoryRefsFileContent = append(directoryRefsFileContent, "<Include>")
163+
componentRefsFileContent = append(componentRefsFileContent, "<Include>")
82164
windowsMsiProcessFiles(filepath.Join(tmpPath, "build", "flutter_assets"))
83-
directoriesFileContent = append(directoriesFileContent, `</Include>`)
84-
directoryRefsFileContent = append(directoryRefsFileContent, `</Include>`)
85-
componentRefsFileContent = append(componentRefsFileContent, `</Include>`)
165+
directoriesFileContent = append(directoriesFileContent, "</Include>")
166+
directoryRefsFileContent = append(directoryRefsFileContent, "</Include>")
167+
componentRefsFileContent = append(componentRefsFileContent, "</Include>")
86168

87169
for _, line := range directoriesFileContent {
88170
if _, err := directoriesFile.WriteString(line + "\n"); err != nil {
@@ -121,6 +203,7 @@ var WindowsMsiTask = &packagingTask{
121203
}
122204

123205
func windowsMsiProcessFiles(path string) {
206+
pathSeparator := string(os.PathSeparator)
124207
files, err := ioutil.ReadDir(path)
125208
if err != nil {
126209
log.Errorf("Failed to read directory %s: %v", path, err)
@@ -129,34 +212,43 @@ func windowsMsiProcessFiles(path string) {
129212

130213
for _, f := range files {
131214
p := filepath.Join(path, f.Name())
132-
relativePath := strings.Split(strings.Split(p, "flutter_assets"+string(filepath.Separator))[1], string(filepath.Separator))
215+
relativePath := strings.Split(strings.Split(p, "flutter_assets"+pathSeparator)[1], pathSeparator)
216+
id := hashSha1(strings.Join(relativePath, ""))
133217
if f.IsDir() {
134218
directoriesFileContent = append(directoriesFileContent,
135-
`<Directory Id="FLUTTERASSETSDIRECTORY_`+strings.Join(relativePath, "_")+`" Name="`+f.Name()+`">`,
219+
fmt.Sprintf(`<Directory Id="FLUTTERASSETSDIRECTORY_%s" Name="%s">`, id, f.Name()),
136220
)
137221
windowsMsiProcessFiles(p)
138222
directoriesFileContent = append(directoriesFileContent,
139-
`</Directory>`,
223+
"</Directory>",
140224
)
141225
} else {
142226
if len(relativePath) > 1 {
143227
directoryRefsFileContent = append(directoryRefsFileContent,
144-
`<DirectoryRef Id="FLUTTERASSETSDIRECTORY_`+strings.Join(relativePath[:len(relativePath)-1], "_")+`">`,
228+
fmt.Sprintf(`<DirectoryRef Id="FLUTTERASSETSDIRECTORY_%s">`, hashSha1(strings.Join(relativePath[:len(relativePath)-1], ""))),
145229
)
146230
} else {
147231
directoryRefsFileContent = append(directoryRefsFileContent,
148232
`<DirectoryRef Id="FLUTTERASSETSDIRECTORY">`,
149233
)
150234
}
235+
fileSource := filepath.Join("build", "flutter_assets", strings.Join(relativePath, pathSeparator))
151236
directoryRefsFileContent = append(directoryRefsFileContent,
152-
`<Component Id="flutter_assets_`+strings.Join(relativePath, "_")+`" Guid="*">`,
153-
`<File Id="flutter_assets_`+strings.Join(relativePath, "_")+`" Source="build/flutter_assets/`+strings.Join(relativePath, "/")+`" KeyPath="yes"/>`,
154-
`</Component>`,
155-
`</DirectoryRef>`,
237+
fmt.Sprintf(`<Component Id="flutter_assets_%s" Guid="*">`, id),
238+
fmt.Sprintf(`<File Id="flutter_assets_%s" Source="%s" KeyPath="yes"/>`, id, fileSource),
239+
"</Component>",
240+
"</DirectoryRef>",
156241
)
157242
componentRefsFileContent = append(componentRefsFileContent,
158-
`<ComponentRef Id="flutter_assets_`+strings.Join(relativePath, "_")+`"/>`,
243+
fmt.Sprintf(`<ComponentRef Id="flutter_assets_%s"/>`, id),
159244
)
160245
}
161246
}
162247
}
248+
249+
func hashSha1(content string) string {
250+
h := sha1.New()
251+
h.Write([]byte(content))
252+
sha := h.Sum(nil)
253+
return hex.EncodeToString(sha)
254+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.13
44

55
require (
66
github.com/GeertJohan/go.rice v1.0.0
7+
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9
78
github.com/daaku/go.zipexe v1.0.1 // indirect
89
github.com/google/go-github v17.0.0+incompatible // indirect
910
github.com/google/go-querystring v1.0.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
33
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
44
github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voiMLQ=
55
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
6+
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9 h1:1ltqoej5GtaWF8jaiA49HwsZD459jqm9YFz9ZtMFpQA=
7+
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
68
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
79
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
810
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=

0 commit comments

Comments
 (0)