Browse Source

refactor: remove js2asar.py and port logic to JS in more readable / GN-style way (#16718)

* refactor: remove js2asar.py and port logic to JS in more readable / GN-style way

* refactor: further clean up ASAR impl, add new node_action GN template
Samuel Attard 6 years ago
parent
commit
b202ad1e24
9 changed files with 168 additions and 73 deletions
  1. 45 8
      BUILD.gn
  2. 22 12
      build/asar.gni
  3. 21 0
      build/node.gni
  4. 14 0
      build/run-node.py
  5. 1 1
      default_app/index.html
  6. 1 1
      default_app/renderer.js
  7. 3 0
      filenames.gni
  8. 61 0
      script/gn-asar.js
  9. 0 51
      tools/js2asar.py

+ 45 - 8
BUILD.gn

@@ -137,7 +137,12 @@ action("atom_js2c") {
          rebase_path(sources, root_build_dir)
 }
 
-asar("js2asar") {
+target_gen_electron_js = "$target_gen_dir/js/electron"
+target_gen_default_app_js = "$target_gen_dir/js/default_app"
+
+# TODO(MarshallOfSound)
+# This copy will be replaced by a call to tsc in the future
+copy("lib_js") {
   sources = filenames.js_sources
   if (enable_desktop_capturer) {
     sources += [
@@ -156,18 +161,50 @@ asar("js2asar") {
       "lib/browser/api/views/text-field.js",
     ]
   }
+
+  outputs = [
+    "$target_gen_electron_js/{{source}}",
+  ]
+}
+
+asar("electron_asar") {
+  deps = [
+    ":lib_js",
+  ]
+
+  root = "$target_gen_electron_js/electron/lib"
+  sources = get_target_outputs(":lib_js")
   outputs = [
     "$root_out_dir/resources/electron.asar",
   ]
-  root = "lib"
 }
 
-asar("app2asar") {
+copy("default_app_js") {
   sources = filenames.default_app_sources
+  outputs = [
+    "$target_gen_default_app_js/{{source}}",
+  ]
+}
+
+copy("default_app_octicon_deps") {
+  sources = filenames.default_app_octicon_sources
+  outputs = [
+    "$target_gen_default_app_js/electron/default_app/octicon/{{source_file_part}}",
+  ]
+}
+
+asar("default_app_asar") {
+  deps = [
+    ":default_app_js",
+    ":default_app_octicon_deps",
+  ]
+
+  root = "$target_gen_default_app_js/electron/default_app"
+  sources = get_target_outputs(":default_app_js") +
+            get_target_outputs(":default_app_octicon_deps")
   outputs = [
     "$root_out_dir/resources/default_app.asar",
   ]
-  root = "default_app"
 }
 
 grit("resources") {
@@ -712,9 +749,9 @@ if (is_mac) {
 
   bundle_data("electron_app_resources") {
     public_deps = [
-      ":app2asar",
+      ":default_app_asar",
       ":electron_app_strings_bundle_data",
-      ":js2asar",
+      ":electron_asar",
     ]
     sources = [
       "$root_out_dir/resources/default_app.asar",
@@ -760,10 +797,10 @@ if (is_mac) {
     sources = filenames.app_sources
     include_dirs = [ "." ]
     deps = [
-      ":app2asar",
+      ":default_app_asar",
       ":electron_app_manifest",
+      ":electron_asar",
       ":electron_lib",
-      ":js2asar",
       ":packed_resources",
       "//content:sandbox_helper_win",
       "//ui/strings",

+ 22 - 12
build/asar.gni

@@ -1,5 +1,6 @@
-import("npm.gni")
+import("node.gni")
 
+# TODO(MarshallOfSound): Move to electron/node, this is the only place it is used now
 # Run an action with a given working directory. Behaves identically to the
 # action() target type, with the exception that it changes directory before
 # running the script.
@@ -32,19 +33,28 @@ template("chdir_action") {
 
 template("asar") {
   assert(defined(invoker.sources),
-         "Need sources in $target_name listing the JS files.")
+         "Need sources in $target_name listing the source files")
   assert(defined(invoker.outputs),
          "Need asar name (as 1-element array, e.g. \$root_out_dir/foo.asar)")
-  assert(defined(invoker.root), "Need asar root directory")
-  asar_root = invoker.root
+  assert(defined(invoker.root), "Need the base dir for generating the ASAR")
 
-  # js2asar.py expects relative paths to its inputs, so we must run it in a
-  # working directory in which those relative paths make sense.
-  chdir_action(target_name) {
-    sources = invoker.sources
-    outputs = invoker.outputs
-    script = "//electron/tools/js2asar.py"
-    cwd = rebase_path(get_path_info(".", "abspath"))
-    args = rebase_path(outputs, cwd) + [ asar_root ] + rebase_path(sources, ".")
+  node_action(target_name) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "script",
+                             "args",
+                           ])
+
+    script = "//electron/script/gn-asar.js"
+    args = [
+             "--base",
+             rebase_path(root),
+             "--files",
+           ] + rebase_path(sources) +
+           [
+             "--out",
+             rebase_path(outputs[0]),
+           ]
   }
 }

+ 21 - 0
build/node.gni

@@ -0,0 +1,21 @@
+template("node_action") {
+  assert(defined(invoker.script), "Need script path to run")
+  assert(defined(invoker.args), "Need script argumets")
+
+  action(target_name) {
+    forward_variables_from(invoker,
+                           [
+                             "deps",
+                             "public_deps",
+                             "sources",
+                             "inputs",
+                             "outputs",
+                           ])
+    if (!defined(inputs)) {
+      inputs = []
+    }
+    inputs += [ invoker.script ]
+    script = "//electron/build/run-node.py"
+    args = [ rebase_path(invoker.script) ] + invoker.args
+  }
+}

+ 14 - 0
build/run-node.py

@@ -0,0 +1,14 @@
+import os
+import subprocess
+import sys
+
+
+SOURCE_ROOT = os.path.dirname(os.path.dirname(__file__))
+
+def main():
+  # Proxy all args to node script
+  script = os.path.join(SOURCE_ROOT, sys.argv[1])
+  subprocess.check_call(['node', script] + [str(x) for x in sys.argv[2:]])
+
+if __name__ == '__main__':
+  sys.exit(main())

+ 1 - 1
default_app/index.html

@@ -4,7 +4,7 @@
   <title>Electron</title>
   <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'" />
   <link href="./styles.css" type="text/css" rel="stylesheet" />
-  <link href="./node_modules/octicons/build/build.css" type="text/css" rel="stylesheet" />
+  <link href="./octicon/build.css" type="text/css" rel="stylesheet" />
 </head>
 
 <body>

+ 1 - 1
default_app/renderer.js

@@ -35,7 +35,7 @@ function initialize () {
   document.querySelector('.command-example').innerText = `${electronPath} path-to-app`
 
   function getOcticonSvg (name) {
-    const octiconPath = path.resolve(__dirname, 'node_modules', 'octicons', 'build', 'svg', `${name}.svg`)
+    const octiconPath = path.resolve(__dirname, 'octicon', `${name}.svg`)
     if (fs.existsSync(octiconPath)) {
       const content = fs.readFileSync(octiconPath, 'utf8')
       const div = document.createElement('div')

+ 3 - 0
filenames.gni

@@ -99,6 +99,9 @@ filenames = {
     "default_app/package.json",
     "default_app/renderer.js",
     "default_app/styles.css",
+  ]
+
+  default_app_octicon_sources = [
     "node_modules/octicons/build/build.css",
     "node_modules/octicons/build/svg/gist.svg",
     "node_modules/octicons/build/svg/mark-github.svg",

+ 61 - 0
script/gn-asar.js

@@ -0,0 +1,61 @@
+const asar = require('asar')
+const assert = require('assert')
+const fs = require('fs-extra')
+const os = require('os')
+const path = require('path')
+
+const getArgGroup = (name) => {
+  const group = []
+  let inGroup = false
+  for (const arg of process.argv) {
+    // At the next flag we stop being in the current group
+    if (arg.startsWith('--')) inGroup = false
+    // Push all args in the group
+    if (inGroup) group.push(arg)
+    // If we find the start flag, start pushing
+    if (arg === `--${name}`) inGroup = true
+  }
+
+  return group
+}
+
+const base = getArgGroup('base')
+const files = getArgGroup('files')
+const out = getArgGroup('out')
+
+assert(base.length === 1, 'should have a single base dir')
+assert(files.length >= 1, 'should have at least one input file')
+assert(out.length === 1, 'should have a single out path')
+
+// Ensure all files are inside the base dir
+for (const file of files) {
+  if (!file.startsWith(base[0])) {
+    console.error(`Expected all files to be inside the base dir but "${file}" was not in "${base[0]}"`)
+    process.exit(1)
+  }
+}
+
+const tmpPath = fs.mkdtempSync(path.resolve(os.tmpdir(), 'electron-gn-asar-'))
+
+try {
+  // Copy all files to a tmp dir to avoid including scrap files in the ASAR
+  for (const file of files) {
+    const newLocation = path.resolve(tmpPath, path.relative(base[0], file))
+    fs.mkdirsSync(path.dirname(newLocation))
+    fs.writeFileSync(newLocation, fs.readFileSync(file))
+  }
+} catch (err) {
+  console.error('Unexpected error while generating ASAR', err)
+  fs.removeSync(tmpPath)
+  process.exit(1)
+}
+
+// Create the ASAR archive
+asar.createPackageWithOptions(tmpPath, out[0], {}, (err) => {
+  fs.removeSync(tmpPath)
+
+  if (err) {
+    console.error('Unexpected error while generating ASAR', err)
+    process.exit(1)
+  }
+})

+ 0 - 51
tools/js2asar.py

@@ -1,51 +0,0 @@
-#!/usr/bin/env python
-
-import errno
-import os
-import shutil
-import subprocess
-import sys
-import tempfile
-
-SOURCE_ROOT = os.path.dirname(os.path.dirname(__file__))
-
-
-def main():
-  archive = sys.argv[1]
-  folder_name = sys.argv[2]
-  source_files = sys.argv[3:]
-
-  output_dir = tempfile.mkdtemp()
-  copy_files(source_files, output_dir, folder_name)
-  call_asar(archive, os.path.join(output_dir, folder_name))
-  shutil.rmtree(output_dir)
-
-
-def copy_files(source_files, output_dir, folder_name):
-  for source_file in source_files:
-    output_path = os.path.join(output_dir, source_file)
-    # Files that aren't in the default_app folder need to be put inside
-    # the temp one we are making so they end up in the ASAR
-    if not os.path.normpath(source_file).startswith(folder_name + os.sep):
-      output_path = os.path.join(output_dir, folder_name, source_file)
-    safe_mkdir(os.path.dirname(output_path))
-    shutil.copy2(source_file, output_path)
-
-
-def call_asar(archive, output_dir):
-  asar = os.path.join(SOURCE_ROOT, 'node_modules', '.bin', 'asar')
-  if sys.platform in ['win32', 'cygwin']:
-    asar += '.cmd'
-  subprocess.check_call([asar, 'pack', output_dir, archive])
-
-
-def safe_mkdir(path):
-  try:
-    os.makedirs(path)
-  except OSError as e:
-    if e.errno != errno.EEXIST:
-      raise
-
-
-if __name__ == '__main__':
-  sys.exit(main())