Refactor generateManifest infra tool
- Changed from using bash/perl to using go.
- Now "if" statements in .gjson files can be stacked, and there can be
conditional "or" statements inside, like |#if defined(A || B)|.
- Go tests were added to make sure that the generateManifest tool
continues to work as expected.
- Updated docs to reflect the fact that the manifest is now generated
with a different tool, and Go is now required in the build phase.
Change-Id: Id866d1a49a7ab4aac2dcf8d188c56bb8254a80de
diff --git a/README.md b/README.md
index d0d6ee2..f5ab3eb 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,8 @@
## Build the extension
A zip file with the contents of the extension, which can be uploaded to the
Chrome Web Store and addons.mozilla.org, can be created with any of the
-following procedures:
+following procedures (make sure to [install Go](https://golang.org) before
+building the extension, as it is needed during the build):
### Using the release.bash script
Run `bash release.bash -h` in order to learn how to use this command. To
@@ -76,12 +77,12 @@
## Testing notes
When testing the extension during development, you don't have to build the
extension each time you want to import an updated version to Chrome/Firefox.
-Instead, run `bash generateManifest.bash {browser}` once, where `{browser}`
-is either `CHROMIUM` or `GECKO`, and this will generate a `manifest.json`
-file for the specified browser in the `src` directory. Now, you can load the
-`src` folder directly in the browser in order to import the extension, which
-removes the need to build it. When the `manifest.gjson` file is modified, you'll
-have to generate the manifest again.
+Instead, run `go run generateManifest.go {browser}` once, where `{browser}` is
+either `CHROMIUM` or `GECKO`, and this will generate a `manifest.json` file for
+the specified browser in the `src` directory. Now, you can load the `src` folder
+directly in the browser in order to import the extension, which removes the need
+to build it. When the `manifest.gjson` file is modified, you'll have to generate
+the manifest again.
To test translations, you might want to set your browser's locale. This section
tells you how to set the locale in
diff --git a/generateManifest.bash b/generateManifest.bash
deleted file mode 100644
index 29839c0..0000000
--- a/generateManifest.bash
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Generates the manifest.json file according to the dependencies passed
-# via CLI arguments
-
-dependencies=("$@")
-
-rm -f src/manifest.json
-cp templates/manifest.gjson src/manifest.json
-
-for dep in "${dependencies[@]}"; do
- perl -0777 -pi -e "s/^#if defined\($dep\)\n([^#]*)#endif\n/\$1/gms" \
- src/manifest.json
-done
-
-perl -0777 -pi -e "s/^#if defined\([^\n#]+\)\n[^#]*#endif\n//gms" \
- src/manifest.json
diff --git a/generateManifest.go b/generateManifest.go
new file mode 100644
index 0000000..1c2e4f0
--- /dev/null
+++ b/generateManifest.go
@@ -0,0 +1,107 @@
+package main
+
+import (
+ "bufio"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "regexp"
+ "strings"
+)
+
+// Generates the manifest.json file according to the dependencies passed
+// via CLI arguments
+
+const (
+ manifestTemplate = "templates/manifest.gjson"
+ manifestSrc = "src/manifest.json"
+)
+
+var beginningOfIfStatement = regexp.MustCompile(`^\s*#if defined\(([^\(\)]*)\)\s*$`)
+var endOfIfStatement = regexp.MustCompile(`^\s*#endif\s*$`)
+
+var (
+ quietMode = flag.Bool("quiet", false, "Quiet mode")
+)
+
+func FindWithCaseFolding(slice []string, val string) bool {
+ for _, item := range slice {
+ if strings.EqualFold(item, val) {
+ return true
+ }
+ }
+ return false
+}
+
+func WriteManifest(template io.Reader, dest io.Writer, dependencies []string) error {
+ level := 0
+ activeLevel := 0
+ scanner := bufio.NewScanner(template)
+ for scanner.Scan() {
+ line := scanner.Text()
+ considerLine := false
+ if m := beginningOfIfStatement.FindStringSubmatch(line); m != nil {
+ if level == activeLevel {
+ statementDeps := m[1]
+ deps := strings.Split(statementDeps, "||")
+ for _, dep := range deps {
+ dep = strings.TrimSpace(dep)
+ if FindWithCaseFolding(dependencies, dep) {
+ activeLevel++
+ break
+ }
+ }
+ }
+ level++
+ } else if m := endOfIfStatement.MatchString(line); m {
+ if activeLevel == level {
+ activeLevel--
+ }
+ level--
+ } else {
+ considerLine = level == activeLevel
+ }
+
+ if considerLine {
+ _, err := io.WriteString(dest, line + "\n")
+ if err != nil {
+ return fmt.Errorf("Can't write manifest: %v", err)
+ }
+ }
+ }
+
+ return nil
+}
+
+func main() {
+ log.SetPrefix("generateManifest: ")
+ log.SetFlags(0)
+
+ flag.Parse()
+ dependencies := flag.Args()
+
+ if len(dependencies) == 0 {
+ log.Fatalf("Pass the dependencies as arguments (for instance, run `go run generateManifest.go CHROMIUM`).")
+ }
+
+ template, err := os.Open(manifestTemplate)
+ if err != nil {
+ log.Fatalf("Couldn't open file %v: %v", manifestTemplate, err)
+ }
+ defer template.Close()
+
+ dest, err := os.Create(manifestSrc)
+ if err != nil {
+ log.Fatalf("Couldn't create file %v: %v", manifestSrc, err)
+ }
+ defer dest.Close()
+
+ err = WriteManifest(template, dest, dependencies)
+ if err != nil {
+ log.Fatalf("%v", err)
+ } else if !*quietMode {
+ log.Println("Manifest has been generated successfully")
+ }
+}
diff --git a/generateManifest_test.go b/generateManifest_test.go
new file mode 100644
index 0000000..57f2f56
--- /dev/null
+++ b/generateManifest_test.go
@@ -0,0 +1,79 @@
+package main
+
+import (
+ "bytes"
+ "crypto/sha256"
+ "encoding/hex"
+ "io"
+ "os"
+ "testing"
+)
+
+type Manifest struct {
+ name string
+ templateFile string
+ expectedFile string
+ dependencies []string
+}
+
+func TestManifestConversions(t *testing.T) {
+ testManifests := []Manifest{
+ {
+ name: "ManifestSmall",
+ templateFile: "testdata/manifest_small1.gjson",
+ expectedFile: "testdata/manifest_small1_expected.json",
+ dependencies: []string{"AAA", "BBB", "D"},
+ },
+ {
+ name: "ManifestFrozenChromium",
+ templateFile: "testdata/manifest_frozen.gjson",
+ expectedFile: "testdata/manifest_frozen_chromium_expected.json",
+ dependencies: []string{"CHROMIUM"},
+ },
+ {
+ name: "ManifestFrozenGecko",
+ templateFile: "testdata/manifest_frozen.gjson",
+ expectedFile: "testdata/manifest_frozen_gecko_expected.json",
+ dependencies: []string{"GECKO"},
+ },
+ }
+
+ for _, m := range testManifests {
+ t.Run(m.name, func(t *testing.T) {
+ template, err := os.Open(m.templateFile)
+ if err != nil {
+ t.Fatalf("Can't open test file: %v", err)
+ }
+ defer template.Close()
+
+ expected, err := os.Open(m.expectedFile)
+ if err != nil {
+ t.Fatalf("Can't open expected file: %v", err)
+ }
+ defer expected.Close()
+
+ dest := bytes.NewBufferString("")
+
+ WriteManifest(template, dest, m.dependencies)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ h1 := sha256.New()
+ if _, err := io.Copy(h1, dest); err != nil {
+ t.Fatalf("Can't prepare sha256 sum of the result: %v", err)
+ }
+
+ h2 := sha256.New()
+ if _, err := io.Copy(h2, expected); err != nil {
+ t.Fatalf("Can't prepare sha256 sum of the expected file: %v", err)
+ }
+
+ sum1 := h1.Sum(nil)
+ sum2 := h2.Sum(nil)
+ if bytes.Compare(sum1, sum2) != 0 {
+ t.Fatalf("The expected manifest file (sha256: %v) differs from what we got (sha256: %v).", hex.EncodeToString(sum2), hex.EncodeToString(sum1))
+ }
+ })
+ }
+}
diff --git a/release.bash b/release.bash
index 75ac4aa..269bd4d 100644
--- a/release.bash
+++ b/release.bash
@@ -64,9 +64,9 @@
# First of all, generate the appropriate manifest.json file for the
# target browser
-dependencies=(${browser^^})
+dependencies=(${browser})
-bash generateManifest.bash "${dependencies[@]}"
+go run generateManifest.go "${dependencies[@]}"
# This is the version name which git gives us
version=$(git describe --always --tags --dirty)
diff --git a/testdata/manifest_frozen.gjson b/testdata/manifest_frozen.gjson
new file mode 100644
index 0000000..8d93b2e
--- /dev/null
+++ b/testdata/manifest_frozen.gjson
@@ -0,0 +1,89 @@
+{
+ "manifest_version": 2,
+ "name": "__MSG_appName__",
+ "version": "0",
+#if defined(CHROMIUM)
+ "version_name": "dirty",
+#endif
+ "description": "__MSG_appDescription__",
+ "icons": {
+ "512": "icons/512.png",
+ "128": "icons/128.png"
+ },
+ "content_scripts": [
+ {
+ "matches": ["https://support.google.com/s/community*"],
+ "js": ["common/content_scripts.js", "content_scripts/console_inject.js"]
+ },
+ {
+ "matches": ["https://support.google.com/s/community*"],
+ "js": ["common/content_scripts.js", "content_scripts/console_inject_start.js"],
+ "css": ["common/console.css"],
+ "run_at": "document_start"
+ },
+ {
+ "matches": ["https://support.google.com/*/threads*"],
+ "js": ["content_scripts/forum_inject.js"]
+ },
+ {
+ "matches": ["https://support.google.com/*/thread/*"],
+ "exclude_matches": ["https://support.google.com/s/community*", "https://support.google.com/*/thread/new*"],
+ "js": ["common/content_scripts.js", "content_scripts/thread_inject.js"],
+ "run_at": "document_end"
+ },
+ {
+ "matches": ["https://support.google.com/s/community*", "https://support.google.com/*/thread/*"],
+ "exclude_matches": ["https://support.google.com/*/thread/new*"],
+ "js": ["common/content_scripts.js", "common/cs_event_listener.js", "content_scripts/profileindicator_inject.js"]
+ },
+ {
+ "matches": ["https://support.google.com/*/profile/*"],
+ "js": ["common/content_scripts.js", "content_scripts/profile_inject.js"],
+ "css": ["common/forum.css"]
+ }
+ ],
+ "permissions": [
+ "https://support.google.com/s/community*",
+ "https://support.google.com/*/threads*",
+ "https://support.google.com/*/thread/*",
+ "storage"
+ ],
+ "web_accessible_resources": [
+ "injections/profileindicator_inject.js",
+ "injections/profileindicator_inject.css",
+ "injections/ccdarktheme.css",
+ "injections/batchlock_inject.js"
+ ],
+ "browser_action": {},
+#if defined(CHROMIUM)
+ "options_page": "options.html",
+#endif
+ "options_ui": {
+ "page": "options.html",
+#if defined(CHROMIUM)
+ "chrome_style": true,
+#endif
+#if defined(GECKO)
+ "browser_style": true,
+#endif
+ "open_in_tab": false
+ },
+ "background": {
+#if defined(CHROMIUM)
+ "persistent": false,
+#endif
+ "scripts": [
+ "common/common.js",
+ "background.js"
+ ]
+ },
+#if defined(GECKO)
+ "browser_specific_settings": {
+ "gecko": {
+ "id": "twpowertools@avm99963.com",
+ "strict_min_version": "57.0"
+ }
+ },
+#endif
+ "default_locale": "en"
+}
diff --git a/testdata/manifest_frozen_chromium_expected.json b/testdata/manifest_frozen_chromium_expected.json
new file mode 100644
index 0000000..2bd184b
--- /dev/null
+++ b/testdata/manifest_frozen_chromium_expected.json
@@ -0,0 +1,70 @@
+{
+ "manifest_version": 2,
+ "name": "__MSG_appName__",
+ "version": "0",
+ "version_name": "dirty",
+ "description": "__MSG_appDescription__",
+ "icons": {
+ "512": "icons/512.png",
+ "128": "icons/128.png"
+ },
+ "content_scripts": [
+ {
+ "matches": ["https://support.google.com/s/community*"],
+ "js": ["common/content_scripts.js", "content_scripts/console_inject.js"]
+ },
+ {
+ "matches": ["https://support.google.com/s/community*"],
+ "js": ["common/content_scripts.js", "content_scripts/console_inject_start.js"],
+ "css": ["common/console.css"],
+ "run_at": "document_start"
+ },
+ {
+ "matches": ["https://support.google.com/*/threads*"],
+ "js": ["content_scripts/forum_inject.js"]
+ },
+ {
+ "matches": ["https://support.google.com/*/thread/*"],
+ "exclude_matches": ["https://support.google.com/s/community*", "https://support.google.com/*/thread/new*"],
+ "js": ["common/content_scripts.js", "content_scripts/thread_inject.js"],
+ "run_at": "document_end"
+ },
+ {
+ "matches": ["https://support.google.com/s/community*", "https://support.google.com/*/thread/*"],
+ "exclude_matches": ["https://support.google.com/*/thread/new*"],
+ "js": ["common/content_scripts.js", "common/cs_event_listener.js", "content_scripts/profileindicator_inject.js"]
+ },
+ {
+ "matches": ["https://support.google.com/*/profile/*"],
+ "js": ["common/content_scripts.js", "content_scripts/profile_inject.js"],
+ "css": ["common/forum.css"]
+ }
+ ],
+ "permissions": [
+ "https://support.google.com/s/community*",
+ "https://support.google.com/*/threads*",
+ "https://support.google.com/*/thread/*",
+ "storage"
+ ],
+ "web_accessible_resources": [
+ "injections/profileindicator_inject.js",
+ "injections/profileindicator_inject.css",
+ "injections/ccdarktheme.css",
+ "injections/batchlock_inject.js"
+ ],
+ "browser_action": {},
+ "options_page": "options.html",
+ "options_ui": {
+ "page": "options.html",
+ "chrome_style": true,
+ "open_in_tab": false
+ },
+ "background": {
+ "persistent": false,
+ "scripts": [
+ "common/common.js",
+ "background.js"
+ ]
+ },
+ "default_locale": "en"
+}
diff --git a/testdata/manifest_frozen_gecko_expected.json b/testdata/manifest_frozen_gecko_expected.json
new file mode 100644
index 0000000..49792d5
--- /dev/null
+++ b/testdata/manifest_frozen_gecko_expected.json
@@ -0,0 +1,73 @@
+{
+ "manifest_version": 2,
+ "name": "__MSG_appName__",
+ "version": "0",
+ "description": "__MSG_appDescription__",
+ "icons": {
+ "512": "icons/512.png",
+ "128": "icons/128.png"
+ },
+ "content_scripts": [
+ {
+ "matches": ["https://support.google.com/s/community*"],
+ "js": ["common/content_scripts.js", "content_scripts/console_inject.js"]
+ },
+ {
+ "matches": ["https://support.google.com/s/community*"],
+ "js": ["common/content_scripts.js", "content_scripts/console_inject_start.js"],
+ "css": ["common/console.css"],
+ "run_at": "document_start"
+ },
+ {
+ "matches": ["https://support.google.com/*/threads*"],
+ "js": ["content_scripts/forum_inject.js"]
+ },
+ {
+ "matches": ["https://support.google.com/*/thread/*"],
+ "exclude_matches": ["https://support.google.com/s/community*", "https://support.google.com/*/thread/new*"],
+ "js": ["common/content_scripts.js", "content_scripts/thread_inject.js"],
+ "run_at": "document_end"
+ },
+ {
+ "matches": ["https://support.google.com/s/community*", "https://support.google.com/*/thread/*"],
+ "exclude_matches": ["https://support.google.com/*/thread/new*"],
+ "js": ["common/content_scripts.js", "common/cs_event_listener.js", "content_scripts/profileindicator_inject.js"]
+ },
+ {
+ "matches": ["https://support.google.com/*/profile/*"],
+ "js": ["common/content_scripts.js", "content_scripts/profile_inject.js"],
+ "css": ["common/forum.css"]
+ }
+ ],
+ "permissions": [
+ "https://support.google.com/s/community*",
+ "https://support.google.com/*/threads*",
+ "https://support.google.com/*/thread/*",
+ "storage"
+ ],
+ "web_accessible_resources": [
+ "injections/profileindicator_inject.js",
+ "injections/profileindicator_inject.css",
+ "injections/ccdarktheme.css",
+ "injections/batchlock_inject.js"
+ ],
+ "browser_action": {},
+ "options_ui": {
+ "page": "options.html",
+ "browser_style": true,
+ "open_in_tab": false
+ },
+ "background": {
+ "scripts": [
+ "common/common.js",
+ "background.js"
+ ]
+ },
+ "browser_specific_settings": {
+ "gecko": {
+ "id": "twpowertools@avm99963.com",
+ "strict_min_version": "57.0"
+ }
+ },
+ "default_locale": "en"
+}
diff --git a/testdata/manifest_small1.gjson b/testdata/manifest_small1.gjson
new file mode 100644
index 0000000..5027087
--- /dev/null
+++ b/testdata/manifest_small1.gjson
@@ -0,0 +1,26 @@
+{
+ "foo": "option",
+#if defined(AAA)
+ "bar": 1,
+ #endif
+#if defined( BBB )
+ "count": 2,
+ #if defined(B || C)
+ "mmm": {},
+#endif
+#if defined(C)
+ #if defined(D)
+ "ok": true,
+#endif
+#endif
+#if defined(D)
+#if defined(C)
+ "ok2": false,
+#endif
+#endif
+#endif
+#if defined(CDE || AAA)
+ "shouldBe": true,
+#endif
+ "so": 3.14
+}
diff --git a/testdata/manifest_small1_expected.json b/testdata/manifest_small1_expected.json
new file mode 100644
index 0000000..0e32581
--- /dev/null
+++ b/testdata/manifest_small1_expected.json
@@ -0,0 +1,7 @@
+{
+ "foo": "option",
+ "bar": 1,
+ "count": 2,
+ "shouldBe": true,
+ "so": 3.14
+}