diff --git a/helper/helper.c b/helper/helper.c deleted file mode 100644 index 5eddd2f8811746a4a2fa0ed9005a944936800dbc..0000000000000000000000000000000000000000 --- a/helper/helper.c +++ /dev/null @@ -1,9 +0,0 @@ -#include -#include "include/capi/cef_app_capi.h" - -int main(int argc, char **argv) { - cef_main_args_t *args = (cef_main_args_t *)calloc(1, sizeof(cef_main_args_t)); - args->argc = argc; - args->argv = argv; - return cef_execute_process(args, NULL, NULL); -} diff --git a/internal/cmd/dist.go b/internal/cmd/dist.go new file mode 100644 index 0000000000000000000000000000000000000000..8b37123e94f2dce81843c44c089cc7e7ab8cd980 --- /dev/null +++ b/internal/cmd/dist.go @@ -0,0 +1,214 @@ +package cmd + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path" + "runtime" + "strconv" + "time" + + "github.com/richardwilkes/toolbox/atexit" + "github.com/richardwilkes/toolbox/cmdline" + "github.com/richardwilkes/toolbox/xio/fs" +) + +type dist struct { + root string + displayName string + bundleName string + bundleID string + exeName string + icon string + version string + shortVersion string + copyrightYears string + copyrightOwner string +} + +// NewDist returns the dist command. +func NewDist() cmdline.Cmd { + d := &dist{ + root: "dist", + displayName: "Example", + bundleName: "Example", + bundleID: "com.example", + exeName: "example", + version: "1.0.0", + shortVersion: "1.0", + copyrightYears: strconv.Itoa(time.Now().Year()), + copyrightOwner: "Unknown", + } + switch runtime.GOOS { + case "darwin": + d.root = path.Join(d.root, "macos") + d.icon = "AppIcon.icns" + default: + d.root = path.Join(d.root, runtime.GOOS) + } + return d +} + +func (d *dist) Name() string { + return "dist" +} + +func (d *dist) Usage() string { + return "Creates a distribution tree, adding the necessary CEF libraries." +} + +func (d *dist) Run(cl *cmdline.CmdLine, args []string) error { + cl.NewStringOption(&d.root).SetSingle('d').SetName("dir").SetUsage("Set the root distribution directory") + cl.NewStringOption(&d.displayName).SetSingle('n').SetName("name").SetUsage("Set the display name (macOS-only)") + cl.NewStringOption(&d.bundleName).SetSingle('b').SetName("bundle").SetUsage("Set the bundle name (macOS-only)") + cl.NewStringOption(&d.bundleID).SetSingle('B').SetName("id").SetUsage("Set the bundle ID (macOS-only)") + cl.NewStringOption(&d.exeName).SetSingle('e').SetName("executable").SetUsage("Set the executable name (macOS-only)") + cl.NewStringOption(&d.icon).SetSingle('i').SetName("icon").SetUsage("Set the icon (macOS-only)") + cl.NewStringOption(&d.version).SetSingle('r').SetName("release").SetUsage("Set the release version (macOS-only)") + cl.NewStringOption(&d.shortVersion).SetSingle('s').SetName("short-release").SetUsage("Set the short release version (macOS-only)") + cl.NewStringOption(&d.copyrightYears).SetSingle('y').SetName("year").SetUsage("Set the copyright year(s) (macOS-only)") + cl.NewStringOption(&d.copyrightOwner).SetSingle('o').SetName("owner").SetUsage("Set the copyright owner (macOS-only)") + cl.Parse(args) + checkPlatform() + if err := os.RemoveAll(d.root); err != nil { + fmt.Printf("Unable to remove old dist at %s\n", d.root) + fmt.Println(err) + atexit.Exit(1) + } + createDir(d.root, 0755) + switch runtime.GOOS { + case "darwin": + d.distMacOS() + case "windows": + d.distWindows() + default: + return fmt.Errorf("Unhandled OS: %s", runtime.GOOS) + } + return nil +} + +func (d *dist) distMacOS() { + appBundleContentsDir := path.Join(d.root, d.displayName+".app", "Contents") + createDir(path.Join(appBundleContentsDir, "MacOS"), 0755) + appBundleResourcesDir := path.Join(appBundleContentsDir, "Resources") + createDir(appBundleResourcesDir, 0755) + appFrameworksDir := path.Join(appBundleContentsDir, "Frameworks") + helperAppBundleContentsDir := path.Join(appFrameworksDir, d.exeName+" Helper.app", "Contents") + helperAppBundleMacOSDir := path.Join(helperAppBundleContentsDir, "MacOS") + createDir(helperAppBundleMacOSDir, 0755) + createDir(path.Join(helperAppBundleContentsDir, "Frameworks"), 0755) + releaseDir := path.Join(installPrefix, "Release") + cc := exec.Command("cc", "-I", installPrefix, path.Join(installPrefix, "helper", "helper.c"), "-F", releaseDir, "-framework", "Chromium Embedded Framework", "-o", path.Join(helperAppBundleMacOSDir, d.exeName+" Helper")) + if result, err := cc.CombinedOutput(); err != nil { + fmt.Println("Failed to compile the helper.") + fmt.Println(err) + fmt.Println(string(result)) + atexit.Exit(1) + } + if err := fs.Copy(path.Join(releaseDir, "Chromium Embedded Framework.framework"), path.Join(appFrameworksDir, "Chromium Embedded Framework.framework")); err != nil { + fmt.Println(err) + atexit.Exit(1) + } + if err := os.Symlink("../../../Chromium Embedded Framework.framework", path.Join(helperAppBundleContentsDir, "Frameworks", "Chromium Embedded Framework.framework")); err != nil { + fmt.Println(err) + atexit.Exit(1) + } + if err := fs.Copy(d.icon, path.Join(appBundleResourcesDir, "AppIcon.icns")); err != nil { + fmt.Println(err) + atexit.Exit(1) + } + + plist := path.Join(appBundleContentsDir, "Info.plist") + f, err := os.Create(plist) + checkFileError(err, "create", plist) + _, err = fmt.Fprintf(f, ` + + + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleDisplayName + %s + CFBundleName + %s + CFBundleExecutable + %s + CFBundleIconFile + AppIcon.icns + CFBundleIdentifier + %s + CFBundlePackageType + APPL + CFBundleVersion + %s + CFBundleShortVersionString + %s + NSHumanReadableCopyright + © %s by %s. All rights reserved. + NSHighResolutionCapable + + NSSupportsAutomaticGraphicsSwitching + + + +`, d.displayName, d.bundleName, d.exeName, d.bundleID, d.version, d.shortVersion, d.copyrightYears, d.copyrightOwner) + checkFileError(err, "write", plist) + checkFileError(f.Close(), "write", plist) + + plist = path.Join(helperAppBundleContentsDir, "Info.plist") + f, err = os.Create(plist) + checkFileError(err, "create", plist) + _, err = fmt.Fprintf(f, ` + + + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleDisplayName + %s Helper + CFBundleName + %s Helper + CFBundleExecutable + %s Helper + CFBundleIdentifier + %s.helper + CFBundlePackageType + APPL + CFBundleVersion + %s + CFBundleShortVersionString + %s + NSHumanReadableCopyright + © %s by %s. All rights reserved. + NSHighResolutionCapable + + NSSupportsAutomaticGraphicsSwitching + + + +`, d.displayName, d.bundleName, d.exeName, d.bundleID, d.version, d.shortVersion, d.copyrightYears, d.copyrightOwner) + checkFileError(err, "write", plist) + checkFileError(f.Close(), "write", plist) +} + +func (d *dist) distWindows() { + copyDirContents(path.Join(installPrefix, "Release"), d.root) + copyDirContents(path.Join(installPrefix, "Resources"), d.root) +} + +func copyDirContents(srcdir, dstdir string) { + list, err := ioutil.ReadDir(srcdir) + if err != nil { + fmt.Println(err) + atexit.Exit(1) + } + for _, one := range list { + name := one.Name() + if err := fs.Copy(path.Join(srcdir, name), path.Join(dstdir, name)); err != nil { + fmt.Println(err) + atexit.Exit(1) + } + } +} diff --git a/internal/cmd/helpers.go b/internal/cmd/helpers.go new file mode 100644 index 0000000000000000000000000000000000000000..9a90d8109748e1d61ac7b34065189270d5134df5 --- /dev/null +++ b/internal/cmd/helpers.go @@ -0,0 +1,24 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/richardwilkes/toolbox/atexit" +) + +func createDir(dir string, mode os.FileMode) { + if err := os.MkdirAll(dir, mode); err != nil { + fmt.Println(err) + fmt.Println("You may need to run this as root.") + atexit.Exit(1) + } +} + +func checkFileError(err error, op, name string) { + if err != nil { + fmt.Printf("Unable to %s file %s\n", op, name) + fmt.Println(err) + atexit.Exit(1) + } +} diff --git a/internal/cmd/install.go b/internal/cmd/install.go new file mode 100644 index 0000000000000000000000000000000000000000..4f609b3cb258b955665c29f64ae9726482a7e24c --- /dev/null +++ b/internal/cmd/install.go @@ -0,0 +1,173 @@ +package cmd + +import ( + "archive/tar" + "bufio" + "bytes" + "compress/bzip2" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "path" + "regexp" + "runtime" + "strings" + "time" + + "github.com/richardwilkes/toolbox/atexit" + "github.com/richardwilkes/toolbox/cmdline" + "github.com/richardwilkes/toolbox/xio" +) + +var cefVersionRegex = regexp.MustCompile(`^\s*#define\s+CEF_VERSION\s+"(\d+\.\d+\.\d+\.\w+)"\s*$`) + +// Install holds the install command. +type Install struct { + Version string +} + +// Name returns the name of the command as it needs to be entered on the command line. +func (c *Install) Name() string { + return "install" +} + +// Usage returns a description of what the command does. +func (c *Install) Usage() string { + return "Downloads and installs the headers and libraries necessary use the github.com/richardwilkes/cef/cef package." +} + +// Run the command. +func (c *Install) Run(cl *cmdline.CmdLine, args []string) error { + var needInstall bool + cl.NewBoolOption(&needInstall).SetSingle('f').SetName("force").SetUsage("Force an install") + cl.Parse(args) + checkPlatform() + + if !needInstall { + var existingCEFVersion string + if f, err := os.Open(path.Join(installPrefix, "include/cef_version.h")); err == nil { + s := bufio.NewScanner(f) + for s.Scan() { + line := s.Text() + if result := cefVersionRegex.FindStringSubmatch(line); len(result) == 2 { + existingCEFVersion = result[1] + break + } + } + xio.CloseIgnoringErrors(f) + } + needInstall = existingCEFVersion != c.Version + } + + if needInstall { + fmt.Printf("Installing into %s...\n", installPrefix) + if err := os.RemoveAll(installPrefix); err != nil { + fmt.Printf("Unable to remove old installation at %s\n", installPrefix) + fmt.Println(err) + fmt.Println("You may need to run this as root.") + atexit.Exit(1) + } + createDir(path.Join(installPrefix, "helper"), 0755) + name := path.Join(installPrefix, "helper", "helper.c") + checkFileError(ioutil.WriteFile(name, []byte(`#include +#include "include/capi/cef_app_capi.h" + +int main(int argc, char **argv) { + cef_main_args_t *args = (cef_main_args_t *)calloc(1, sizeof(cef_main_args_t)); + args->argc = argc; + args->argv = argv; + return cef_execute_process(args, NULL, NULL); +} +`), 0644), "write", name) + c.untar(bytes.NewBuffer(c.downloadAndUncompressArchive())) + if runtime.GOOS == "windows" { + dir := path.Join(path.Dir(os.Getenv("MINGW_PREFIX")), "lib/pkgconfig") + createDir(dir, 0755) + name = path.Join(dir, "cef.pc") + f, err := os.Create(name) + checkFileError(err, "create", name) + _, err = fmt.Fprintf(f, `Name: cef +Description: Chromium Embedded Framework +Version: %[1]s + +Requires: +Libs: -L%[2]s/Release -lcef +Cflags: -I%[2]s +`, c.Version, installPrefix) + checkFileError(err, "write", name) + checkFileError(f.Close(), "write", name) + } + } + + return nil +} + +func (c *Install) archiveName() string { + return fmt.Sprintf("cef_binary_%s_%s_minimal", c.Version, cefPlatform) +} + +func (c *Install) downloadAndUncompressArchive() []byte { + client := http.Client{Timeout: 10 * time.Minute} + url := fmt.Sprintf("http://opensource.spotify.com/cefbuilds/%s.tar.bz2", c.archiveName()) + fmt.Println(" Downloading...") + resp, err := client.Get(url) + if err != nil { + fmt.Printf("Unable to download CEF archive at %s\n", url) + fmt.Println(err) + atexit.Exit(1) + } + buffer, err := ioutil.ReadAll(resp.Body) + xio.CloseIgnoringErrors(resp.Body) + if err != nil { + fmt.Printf("Unable to download CEF archive at %s\n", url) + fmt.Println(err) + atexit.Exit(1) + } + if resp.StatusCode > 299 { + fmt.Printf("Unable to download CEF archive at %s\n", url) + fmt.Printf("Status: %s\n", resp.Status) + atexit.Exit(1) + } + fmt.Println(" Uncompressing...") + buffer, err = ioutil.ReadAll(bzip2.NewReader(bytes.NewReader(buffer))) + if err != nil { + fmt.Printf("Unable to uncompress CEF archive from %s\n", url) + fmt.Println(err) + atexit.Exit(1) + } + return buffer +} + +func (c *Install) untar(in io.Reader) { + prefix := c.archiveName() + fmt.Println(" Unarchiving...") + r := tar.NewReader(in) + for { + h, err := r.Next() + if err != nil { + if err == io.EOF { + break + } + fmt.Println("Unable to read tar entry from archive") + fmt.Println(err) + atexit.Exit(1) + } + name := strings.Trim(strings.TrimPrefix(h.Name, prefix), "/") + if name != "" && !strings.Contains(name, "..") { + name = path.Join(installPrefix, name) + switch h.Typeflag { + case tar.TypeDir: + createDir(name, os.FileMode(h.Mode|0555)) + case tar.TypeReg: + buffer, err := ioutil.ReadAll(r) + checkFileError(err, "read archive data for", name) + checkFileError(ioutil.WriteFile(name, buffer, os.FileMode(h.Mode|0444)), "write", name) + default: + fmt.Printf("Unexpected type flag: %d\n", h.Typeflag) + atexit.Exit(1) + } + } + } +} diff --git a/internal/cmd/platform.go b/internal/cmd/platform.go new file mode 100644 index 0000000000000000000000000000000000000000..d7fb1202584b4fdf31aad559beb249e9e8fb0e10 --- /dev/null +++ b/internal/cmd/platform.go @@ -0,0 +1,32 @@ +package cmd + +import ( + "fmt" + "os" + "path" + "runtime" + + "github.com/richardwilkes/toolbox/atexit" +) + +var ( + installPrefix = "/usr/local/cef" + cefPlatform string +) + +func checkPlatform() { + switch runtime.GOOS { + case "darwin": + cefPlatform = "macosx64" + case "windows": + if os.Getenv("MSYSTEM") != "MINGW64" { + fmt.Println("Windows is only supported through the use of MINGW64") + atexit.Exit(1) + } + cefPlatform = "windows64" + installPrefix = path.Join(path.Dir(os.Getenv("MINGW_PREFIX")), installPrefix) + default: + fmt.Println("Unsupported OS: ", runtime.GOOS) + atexit.Exit(1) + } +} diff --git a/main.go b/main.go index b551255abd5f56844d089dc055c103e81f16f983..e32c40a76a4ab0355fb9370409836d4af1df7b46 100644 --- a/main.go +++ b/main.go @@ -1,187 +1,26 @@ package main import ( - "archive/tar" - "bufio" - "bytes" - "compress/bzip2" "fmt" - "io" - "io/ioutil" - "net/http" "os" - "path" - "regexp" - "runtime" - "strings" - "time" + "github.com/richardwilkes/cef/internal/cmd" "github.com/richardwilkes/toolbox/atexit" "github.com/richardwilkes/toolbox/cmdline" - "github.com/richardwilkes/toolbox/xio" ) const desiredCEFVersion = "3.3538.1852.gcb937fc" -var ( - cefVersionRegex = regexp.MustCompile(`^\s*#define\s+CEF_VERSION\s+"(\d+\.\d+\.\d+\.\w+)"\s*$`) - installPrefix = "/usr/local/cef" - cefPlatform string -) - func main() { cmdline.CopyrightYears = "2018" cmdline.CopyrightHolder = "Richard A. Wilkes" cmdline.AppIdentifier = "com.trollworks.cef" cl := cmdline.New(true) - cl.Description = "Downloads and installs the headers and libraries necessary use the github.com/richardwilkes/cef/cef package." - var needInstall bool - cl.NewBoolOption(&needInstall).SetSingle('f').SetName("force").SetUsage("Force an install") - cl.Parse(os.Args[1:]) - - switch runtime.GOOS { - case "darwin": - cefPlatform = "macosx64" - case "windows": - if os.Getenv("MSYSTEM") != "MINGW64" { - fmt.Println("Windows is only supported through the use of MINGW64") - atexit.Exit(1) - } - cefPlatform = "windows64" - installPrefix = path.Join(path.Dir(os.Getenv("MINGW_PREFIX")), installPrefix) - default: - fmt.Println("Unsupported OS: ", runtime.GOOS) - atexit.Exit(1) - } - - if !needInstall { - var existingCEFVersion string - if f, err := os.Open(path.Join(installPrefix, "include/cef_version.h")); err == nil { - s := bufio.NewScanner(f) - for s.Scan() { - line := s.Text() - if result := cefVersionRegex.FindStringSubmatch(line); len(result) == 2 { - existingCEFVersion = result[1] - break - } - } - xio.CloseIgnoringErrors(f) - } - needInstall = existingCEFVersion != desiredCEFVersion - } - - if needInstall { - fmt.Printf("Installing into %s...\n", installPrefix) - if err := os.RemoveAll(installPrefix); err != nil { - fmt.Printf("Unable to remove old installation at %s\n", installPrefix) - fmt.Println(err) - fmt.Println("You may need to run this as root.") - atexit.Exit(1) - } - createDir(installPrefix, 0755) - untar(bytes.NewBuffer(downloadAndUncompressArchive())) - if runtime.GOOS == "windows" { - dir := path.Join(path.Dir(os.Getenv("MINGW_PREFIX")), "lib/pkgconfig") - createDir(dir, 0755) - name := path.Join(dir, "cef.pc") - f, err := os.Create(name) - checkFileError(err, "create", name) - _, err = fmt.Fprintf(f, `Name: cef -Description: Chromium Embedded Framework -Version: %[1]s - -Requires: -Libs: -L%[2]s/Release -lcef -Cflags: -I%[2]s -`, desiredCEFVersion, installPrefix) - checkFileError(err, "write", name) - checkFileError(f.Close(), "write", name) - } - } - - atexit.Exit(0) -} - -func createDir(dir string, mode os.FileMode) { - if err := os.MkdirAll(dir, mode); err != nil { - fmt.Println(err) - fmt.Println("You may need to run this as root.") - atexit.Exit(1) - } -} - -func checkFileError(err error, op, name string) { - if err != nil { - fmt.Printf("Unable to %s file %s\n", op, name) - fmt.Println(err) - atexit.Exit(1) - } -} - -func archiveName() string { - return fmt.Sprintf("cef_binary_%s_%s_minimal", desiredCEFVersion, cefPlatform) -} - -func downloadAndUncompressArchive() []byte { - client := http.Client{Timeout: 10 * time.Minute} - url := fmt.Sprintf("http://opensource.spotify.com/cefbuilds/%s.tar.bz2", archiveName()) - fmt.Println(" Downloading...") - resp, err := client.Get(url) - if err != nil { - fmt.Printf("Unable to download CEF archive at %s\n", url) - fmt.Println(err) - atexit.Exit(1) - } - buffer, err := ioutil.ReadAll(resp.Body) - xio.CloseIgnoringErrors(resp.Body) - if err != nil { - fmt.Printf("Unable to download CEF archive at %s\n", url) - fmt.Println(err) - atexit.Exit(1) - } - if resp.StatusCode > 299 { - fmt.Printf("Unable to download CEF archive at %s\n", url) - fmt.Printf("Status: %s\n", resp.Status) - atexit.Exit(1) - } - fmt.Println(" Uncompressing...") - buffer, err = ioutil.ReadAll(bzip2.NewReader(bytes.NewReader(buffer))) - if err != nil { - fmt.Printf("Unable to uncompress CEF archive from %s\n", url) - fmt.Println(err) + cl.Description = "Utilities for managing setup of the Chromium Embedded Framework." + cl.AddCommand(&cmd.Install{Version: desiredCEFVersion}) + cl.AddCommand(cmd.NewDist()) + if err := cl.RunCommand(cl.Parse(os.Args[1:])); err != nil { + fmt.Fprintln(os.Stderr, err) atexit.Exit(1) } - return buffer -} - -func untar(in io.Reader) { - prefix := archiveName() - fmt.Println(" Unarchiving...") - r := tar.NewReader(in) - for { - h, err := r.Next() - if err != nil { - if err == io.EOF { - break - } - fmt.Println("Unable to read tar entry from archive") - fmt.Println(err) - atexit.Exit(1) - } - name := strings.Trim(strings.TrimPrefix(h.Name, prefix), "/") - if name != "" && !strings.Contains(name, "..") { - name = path.Join(installPrefix, name) - switch h.Typeflag { - case tar.TypeDir: - createDir(name, os.FileMode(h.Mode|0555)) - case tar.TypeReg: - buffer, err := ioutil.ReadAll(r) - checkFileError(err, "read archive data for", name) - checkFileError(ioutil.WriteFile(name, buffer, os.FileMode(h.Mode|0444)), "write", name) - default: - fmt.Printf("Unexpected type flag: %d\n", h.Typeflag) - atexit.Exit(1) - } - } - } }