-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This adds the creation of a .dmg files when building packages on MacOS (the build host must be darwin with XCode installed). The .dmg file contains an installer (.pkg) and a rudimentary Uninstall.app. When code signing is enabled every part of the .dmg is signed. This is the structure of the package. - beat.dmg - beat.pkg - internal-beat.pkg - BeatPrefPane.pkg - Uninstall.app The following environment variables control the code signing behavior. - APPLE_SIGNING_ENABLED - Must be set to true to enable signing. Defaults to false. - APPLE_SIGNING_IDENTITY_INSTALLER - filter for selecting the signing identity for installers. It's effectively used as `security find-identity -v | grep $APPLE_SIGNING_IDENTITY_INSTALLER` to select a single certificate. - APPLE_SIGNING_IDENTITY_APP - filter for selecting the signing identity for apps. Co-authored-by: Adrian Serrano <adrisr83@gmail.com>
- Loading branch information
1 parent
94fe58a
commit 2a58850
Showing
35 changed files
with
1,330 additions
and
255 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,286 @@ | ||
// Licensed to Elasticsearch B.V. under one or more contributor | ||
// license agreements. See the NOTICE file distributed with | ||
// this work for additional information regarding copyright | ||
// ownership. Elasticsearch B.V. licenses this file to you under | ||
// the Apache License, Version 2.0 (the "License"); you may | ||
// not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, | ||
// software distributed under the License is distributed on an | ||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
// KIND, either express or implied. See the License for the | ||
// specific language governing permissions and limitations | ||
// under the License. | ||
|
||
package mage | ||
|
||
import ( | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/magefile/mage/mg" | ||
"github.com/magefile/mage/sh" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type dmgBuilder struct { | ||
PackageSpec | ||
|
||
SigningInfo *AppleSigningInfo | ||
Identifier string | ||
PreferencePaneDir string | ||
PreferencePanePkgFile string | ||
InternalBeatPkg string | ||
BeatPkg string | ||
|
||
beatsDir string | ||
dmgDir string | ||
|
||
// Build tools. | ||
pkgbuild func(args ...string) error | ||
productbuild func(args ...string) error | ||
spctl func(args ...string) error | ||
codesign func(args ...string) error | ||
hdiutil func(args ...string) error | ||
} | ||
|
||
func newDMGBuilder(spec PackageSpec) (*dmgBuilder, error) { | ||
for _, cmd := range []string{"pkgbuild", "productbuild", "spctl", "codesign", "hdiutil"} { | ||
if _, err := exec.LookPath(cmd); err != nil { | ||
return nil, errors.Wrapf(err, "required tool '%v' for DMG packaging not found on PATH", cmd) | ||
} | ||
} | ||
|
||
beatsDir, err := ElasticBeatsDir() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
preferencePaneDir := filepath.Join(beatsDir, "dev-tools/packer/platforms/darwin/preference-pane") | ||
preferencePanePkgFile := filepath.Join(preferencePaneDir, "build/BeatsPrefPane.pkg") | ||
beatIdentifier, ok := spec.evalContext["identifier"].(string) | ||
if !ok { | ||
return nil, errors.Errorf("identifier not specified for DMG packaging") | ||
} | ||
|
||
spec.OutputFile, err = spec.Expand(defaultBinaryName) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
info, err := GetAppleSigningInfo() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &dmgBuilder{ | ||
PackageSpec: spec, | ||
SigningInfo: info, | ||
Identifier: beatIdentifier, | ||
PreferencePaneDir: preferencePaneDir, | ||
PreferencePanePkgFile: preferencePanePkgFile, | ||
|
||
beatsDir: beatsDir, | ||
dmgDir: filepath.Join(spec.packageDir, "dmg"), | ||
|
||
pkgbuild: sh.RunCmd("pkgbuild"), | ||
productbuild: sh.RunCmd("productbuild"), | ||
spctl: sh.RunCmd("spctl", "-a", "-t"), | ||
codesign: sh.RunCmd("codesign"), | ||
hdiutil: sh.RunCmd("hdiutil"), | ||
}, nil | ||
} | ||
|
||
// Create .pkg for preference pane. | ||
func (b *dmgBuilder) buildPreferencePane() error { | ||
return errors.Wrap(Mage(b.PreferencePaneDir), "failed to build Beats preference pane") | ||
} | ||
|
||
func (b *dmgBuilder) buildBeatPkg() error { | ||
beatPkgRoot := filepath.Join(b.packageDir, "beat-pkg-root") | ||
if err := os.RemoveAll(beatPkgRoot); err != nil { | ||
return errors.Wrap(err, "failed to clean beat-pkg-root") | ||
} | ||
|
||
// Copy files into the packaging root and set their mode. | ||
for _, f := range b.Files { | ||
target := filepath.Join(beatPkgRoot, f.Target) | ||
if err := Copy(f.Source, target); err != nil { | ||
return err | ||
} | ||
|
||
info, err := os.Stat(target) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if info.Mode().IsRegular() && info.Mode().Perm() != f.Mode { | ||
if err = os.Chmod(target, f.Mode); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
|
||
b.InternalBeatPkg = filepath.Join(b.packageDir, "pkgs", "internal-"+b.OutputFile+".pkg") | ||
|
||
args := []string{ | ||
"--root", beatPkgRoot, | ||
"--scripts", filepath.Join(b.packageDir, "scripts"), | ||
"--identifier", b.Identifier, | ||
"--version", b.MustExpand("{{.Version}}{{if .Snapshot}}-SNAPSHOT{{end}}"), | ||
} | ||
if b.SigningInfo.Sign { | ||
args = append(args, "--sign", b.SigningInfo.Installer.ID, "--timestamp") | ||
} | ||
args = append(args, createDir(b.InternalBeatPkg)) | ||
if err := b.pkgbuild(args...); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (b *dmgBuilder) buildProductPkg() error { | ||
var ( | ||
distributionPlist = filepath.Join(b.packageDir, "distributions.plist") | ||
resourcesDir = filepath.Join(b.packageDir, "resources") | ||
) | ||
|
||
b.MustExpandFile( | ||
filepath.Join(b.beatsDir, "dev-tools/mage/templates/darwin/distribution.plist.tmpl"), | ||
distributionPlist) | ||
b.MustExpandFile( | ||
filepath.Join(b.beatsDir, "dev-tools/mage/templates/darwin/README.html.tmpl"), | ||
filepath.Join(resourcesDir, "README.html")) | ||
for t, pf := range b.Files { | ||
if strings.HasSuffix(t, "LICENSE.txt") { | ||
Copy(pf.Source, filepath.Join(resourcesDir, "LICENSE.txt")) | ||
break | ||
} | ||
} | ||
b.MustExpandFile( | ||
filepath.Join(b.beatsDir, "dev-tools/mage/templates/darwin/README.html.tmpl"), | ||
filepath.Join(resourcesDir, "README.html")) | ||
|
||
if err := os.RemoveAll(b.dmgDir); err != nil { | ||
return err | ||
} | ||
b.BeatPkg = filepath.Join(b.dmgDir, b.OutputFile+".pkg") | ||
|
||
// Create .pkg containing the previous two .pkg files. | ||
args := []string{ | ||
"--distribution", distributionPlist, | ||
"--resources", resourcesDir, | ||
"--package-path", filepath.Dir(b.InternalBeatPkg), | ||
"--package-path", filepath.Dir(b.PreferencePanePkgFile), | ||
"--component-compression", "auto", | ||
} | ||
if b.SigningInfo.Sign { | ||
args = append(args, "--sign", b.SigningInfo.Installer.ID, "--timestamp") | ||
} | ||
args = append(args, createDir(b.BeatPkg)) | ||
if err := b.productbuild(args...); err != nil { | ||
return err | ||
} | ||
|
||
if b.SigningInfo.Sign { | ||
if err := b.spctl("install", b.BeatPkg); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (b *dmgBuilder) buildUninstallApp() error { | ||
const ( | ||
uninstallerIcons = "Uninstall.app/Contents/Resources/uninstaller.icns" | ||
uninstallScript = "Uninstall.app/Contents/MacOS/uninstall.sh" | ||
infoPlist = "Uninstall.app/Contents/Info.plist" | ||
) | ||
|
||
inputDir := filepath.Join(b.beatsDir, "dev-tools/mage/templates/darwin/dmg") | ||
|
||
Copy( | ||
filepath.Join(inputDir, uninstallerIcons), | ||
filepath.Join(b.dmgDir, uninstallerIcons), | ||
) | ||
b.MustExpandFile( | ||
filepath.Join(inputDir, infoPlist+".tmpl"), | ||
filepath.Join(b.dmgDir, infoPlist), | ||
) | ||
b.MustExpandFile( | ||
filepath.Join(inputDir, uninstallScript+".tmpl"), | ||
filepath.Join(b.dmgDir, uninstallScript), | ||
) | ||
if err := os.Chmod(filepath.Join(b.dmgDir, uninstallScript), 0755); err != nil { | ||
return err | ||
} | ||
|
||
if b.SigningInfo.Sign { | ||
uninstallApp := filepath.Join(b.dmgDir, "Uninstall.app") | ||
if err := b.codesign("-s", b.SigningInfo.App.ID, "--timestamp", uninstallApp); err != nil { | ||
return err | ||
} | ||
|
||
if err := b.spctl("exec", uninstallApp); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Create a .dmg file containing both the Uninstall.app and .pkg file. | ||
func (b *dmgBuilder) buildDMG() error { | ||
dmgFile := filepath.Join(distributionsDir, DMG.AddFileExtension(b.OutputFile)) | ||
|
||
args := []string{ | ||
"create", | ||
"-volname", b.MustExpand("{{.BeatName | title}} {{.Version}}{{if .Snapshot}}-SNAPSHOT{{end}}"), | ||
"-srcfolder", b.dmgDir, | ||
"-ov", | ||
createDir(dmgFile), | ||
} | ||
if err := b.hdiutil(args...); err != nil { | ||
return err | ||
} | ||
|
||
// Sign the .dmg. | ||
if b.SigningInfo.Sign { | ||
if err := b.codesign("-s", b.SigningInfo.App.ID, "--timestamp", dmgFile); err != nil { | ||
return err | ||
} | ||
|
||
if err := b.spctl("install", dmgFile); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return errors.Wrap(CreateSHA512File(dmgFile), "failed to create .sha512 file") | ||
} | ||
|
||
func (b *dmgBuilder) Build() error { | ||
// Mark this function as a dep so that is is only invoked once. | ||
mg.Deps(b.buildPreferencePane) | ||
|
||
var err error | ||
if err = b.buildBeatPkg(); err != nil { | ||
return errors.Wrap(err, "failed to build internal beat pkg") | ||
} | ||
if err = b.buildProductPkg(); err != nil { | ||
return errors.Wrap(err, "failed to build beat product pkg (pref pane + beat)") | ||
} | ||
if err = b.buildUninstallApp(); err != nil { | ||
return errors.Wrap(err, "failed to build Uninstall.app") | ||
} | ||
if err = b.buildDMG(); err != nil { | ||
return errors.Wrap(err, "failed to build beat dmg") | ||
} | ||
return nil | ||
} |
Oops, something went wrong.