Shell: Make separate binary for child processes.

To do (separately): Slim down the deps for the child process.

For comparison, on an Android Debug build, the stripped binary for the main process is ~2.4 MB whereas the child is ~850 kB (as seen in MojoShell.apk).

R=davemoore@chromium.org

Review URL: https://codereview.chromium.org/1061413002
diff --git a/shell/BUILD.gn b/shell/BUILD.gn
index 4dcf578..b84cba0 100644
--- a/shell/BUILD.gn
+++ b/shell/BUILD.gn
@@ -44,9 +44,7 @@
 
 if (!mojo_use_prebuilt_mojo_shell) {
   executable("mojo_shell") {
-    sources = [
-      "desktop/main.cc",
-    ]
+    sources = []
 
     deps = [
       ":init",
@@ -58,7 +56,11 @@
       "//mojo/environment:chromium",
     ]
 
-    if (is_android) {
+    data_deps = [ ":mojo_shell_child" ]
+
+    if (!is_android) {
+      sources += [ "desktop/main.cc" ]
+    } else {
       sources += [
         "android/library_loader.cc",
         "android/main.cc",
@@ -77,6 +79,26 @@
       ]
     }
   }
+
+  executable("mojo_shell_child") {
+    sources = [
+      "child_main.cc",
+    ]
+
+    deps = [
+      # TODO(vtl): Reduce these dependencies (probably mostly in :lib).
+      ":child_controller_bindings",
+      ":init",
+      ":lib",
+      ":native_application_support",
+      "//base",
+      "//base/allocator",
+      "//build/config/sanitizers:deps",
+      "//mojo/common",
+      "//mojo/edk/system",
+      "//mojo/environment:chromium",
+    ]
+  }
 }  # !mojo_use_prebuilt_mojo_shell
 
 source_set("init") {
@@ -108,8 +130,6 @@
 
 source_set("lib") {
   sources = [
-    "child_main.cc",
-    "child_main.h",
     "child_process_host.cc",
     "child_process_host.h",
     "command_line_util.cc",
@@ -292,6 +312,7 @@
     clear_dir = true
     dest = mojo_shell_assets_dir
     sources = [
+      "$root_out_dir/exe.stripped/mojo_shell_child",
       "$root_out_dir/lib.stripped/libbootstrap.so",
       "$root_out_dir/network_service.mojo",
       "$root_out_dir/obj/shell/bootstrap_java.dex.jar",
@@ -389,7 +410,8 @@
     "//shell/application_manager",
   ]
 
-  datadeps = [
+  data_deps = [
+    ":mojo_shell_child",
     "//services/test_service:test_app",
     "//services/test_service:test_request_tracker_app",
   ]
diff --git a/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java b/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java
index 1f55fa2..7ed3656 100644
--- a/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java
+++ b/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java
@@ -27,9 +27,12 @@
     private static final String LOCAL_APP_DIRECTORY = "local_apps";
     // Individual applications bundled with the shell as assets.
     private static final String NETWORK_LIBRARY_APP = "network_service.mojo";
-    // The mojo_shell library is also an executable run in forked processes when running
-    // multi-process.
+    // Not really an executable, but what we'll use for "argv[0]" (along with the path).
     private static final String MOJO_SHELL_EXECUTABLE = "libmojo_shell.so";
+    // Directory where the child executable will be extracted.
+    private static final String CHILD_DIRECTORY = "child";
+    // Name of the child executable.
+    private static final String MOJO_SHELL_CHILD_EXECUTABLE = "mojo_shell_child";
 
     /**
      * A guard flag for calling nativeInit() only once.
@@ -46,6 +49,12 @@
                     getLocalAppsDir(applicationContext), false);
             File mojoShell = new File(applicationContext.getApplicationInfo().nativeLibraryDir,
                     MOJO_SHELL_EXECUTABLE);
+            FileHelper.extractFromAssets(applicationContext, MOJO_SHELL_CHILD_EXECUTABLE,
+                    getChildDir(applicationContext), false);
+            File mojoShellChild =
+                    new File(getChildDir(applicationContext), MOJO_SHELL_CHILD_EXECUTABLE);
+            // The shell child executable needs to be ... executable.
+            mojoShellChild.setExecutable(true, true);
 
             List<String> parametersList = new ArrayList<String>();
             // Program name.
@@ -54,6 +63,7 @@
             }
 
             nativeInit(applicationContext, mojoShell.getAbsolutePath(),
+                    mojoShellChild.getAbsolutePath(),
                     parametersList.toArray(new String[parametersList.size()]),
                     getLocalAppsDir(applicationContext).getAbsolutePath(),
                     getTmpDir(applicationContext).getAbsolutePath());
@@ -84,6 +94,10 @@
         return context.getDir(LOCAL_APP_DIRECTORY, Context.MODE_PRIVATE);
     }
 
+    private static File getChildDir(Context context) {
+        return context.getDir(CHILD_DIRECTORY, Context.MODE_PRIVATE);
+    }
+
     private static File getTmpDir(Context context) {
         return new File(context.getCacheDir(), "tmp");
     }
@@ -97,7 +111,8 @@
      * Initializes the native system. This API should be called only once per process.
      **/
     private static native void nativeInit(Context context, String mojoShellPath,
-            String[] parameters, String bundledAppsDirectory, String tmpDir);
+            String mojoShellChildPath, String[] parameters, String bundledAppsDirectory,
+            String tmpDir);
 
     private static native boolean nativeStart();
 
diff --git a/shell/android/main.cc b/shell/android/main.cc
index aa26a13..221a5d0 100644
--- a/shell/android/main.cc
+++ b/shell/android/main.cc
@@ -46,14 +46,20 @@
 
 class MojoShellRunner : public base::DelegateSimpleThread::Delegate {
  public:
-  MojoShellRunner(const std::vector<std::string>& parameters)
-      : parameters_(parameters) {}
+  MojoShellRunner(const base::FilePath& mojo_shell_path,
+                  const base::FilePath& mojo_shell_child_path,
+                  const std::vector<std::string>& parameters)
+      : mojo_shell_path_(mojo_shell_path),
+        mojo_shell_child_path_(mojo_shell_child_path),
+        parameters_(parameters) {}
   ~MojoShellRunner() override {}
 
  private:
   void Run() override;
 
-  std::vector<std::string> parameters_;
+  const base::FilePath mojo_shell_path_;
+  const base::FilePath mojo_shell_child_path_;
+  const std::vector<std::string> parameters_;
 
   DISALLOW_COPY_AND_ASSIGN(MojoShellRunner);
 };
@@ -105,9 +111,9 @@
   base::MessageLoop loop(common::MessagePumpMojo::Create());
   Context* context = g_context.Pointer()->get();
   ConfigureAndroidServices(context);
-  context->Init();
+  context->InitWithPaths(mojo_shell_path_, mojo_shell_child_path_);
 
-  for (auto& args : parameters_)
+  for (const auto& args : parameters_)
     ApplyApplicationArgs(context, args);
 
   RunCommandLineApps(context);
@@ -142,6 +148,7 @@
                  jclass clazz,
                  jobject activity,
                  jstring mojo_shell_path,
+                 jstring mojo_shell_child_path,
                  jobjectArray jparameters,
                  jstring j_local_apps_directory,
                  jstring j_tmp_dir) {
@@ -164,7 +171,12 @@
                                                      &parameters);
   base::CommandLine::Init(0, nullptr);
   base::CommandLine::ForCurrentProcess()->InitFromArgv(parameters);
-  g_shell_runner.Get().reset(new MojoShellRunner(parameters));
+  g_shell_runner.Get().reset(new MojoShellRunner(
+      base::FilePath(
+          base::android::ConvertJavaStringToUTF8(env, mojo_shell_path)),
+      base::FilePath(
+          base::android::ConvertJavaStringToUTF8(env, mojo_shell_child_path)),
+      parameters));
 
   InitializeLogging();
 
@@ -214,3 +226,9 @@
 
 }  // namespace shell
 }  // namespace mojo
+
+// TODO(vtl): We need a main(), even though it should never be called.
+int main(int argc, char** argv) {
+  NOTREACHED();
+  return 1;
+}
diff --git a/shell/child_main.cc b/shell/child_main.cc
index b3964a0..a420096 100644
--- a/shell/child_main.cc
+++ b/shell/child_main.cc
@@ -2,10 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "shell/child_main.h"
-
 #include <unistd.h>
 
+#include "base/at_exit.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/command_line.h"
@@ -28,7 +27,9 @@
 #include "mojo/edk/embedder/simple_platform_support.h"
 #include "mojo/public/cpp/system/core.h"
 #include "shell/child_controller.mojom.h"
+#include "shell/init.h"
 #include "shell/native_application_support.h"
+#include "shell/switches.h"
 
 namespace mojo {
 namespace shell {
@@ -276,25 +277,32 @@
 
 }  // namespace
 
-// ChildMain -------------------------------------------------------------------
+}  // namespace shell
+}  // namespace mojo
 
-int ChildMain() {
-  DVLOG(2) << "ChildMain()";
+int main(int argc, char** argv) {
+  base::AtExitManager at_exit;
+  base::CommandLine::Init(argc, argv);
 
-  DCHECK(!base::MessageLoop::current());
+  mojo::shell::InitializeLogging();
 
-  embedder::ScopedPlatformHandle platform_channel =
-      embedder::PlatformChannelPair::PassClientHandleFromParentProcess(
+  // Make sure that we're really meant to be invoked as the child process.
+  CHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kChildProcess));
+
+  mojo::embedder::ScopedPlatformHandle platform_channel =
+      mojo::embedder::PlatformChannelPair::PassClientHandleFromParentProcess(
           *base::CommandLine::ForCurrentProcess());
   CHECK(platform_channel.is_valid());
 
-  AppContext app_context;
+  mojo::shell::AppContext app_context;
   app_context.Init();
 
-  Blocker blocker;
+  mojo::shell::Blocker blocker;
   app_context.controller_runner()->PostTask(
       FROM_HERE,
-      base::Bind(&ChildControllerImpl::Init, base::Unretained(&app_context),
+      base::Bind(&mojo::shell::ChildControllerImpl::Init,
+                 base::Unretained(&app_context),
                  base::Passed(&platform_channel), blocker.GetUnblocker()));
   // This will block, then run whatever the controller wants.
   blocker.Block();
@@ -303,6 +311,3 @@
 
   return 0;
 }
-
-}  // namespace shell
-}  // namespace mojo
diff --git a/shell/child_main.h b/shell/child_main.h
deleted file mode 100644
index 36cb9cd..0000000
--- a/shell/child_main.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SHELL_CHILD_MAIN_H_
-#define SHELL_CHILD_MAIN_H_
-
-namespace mojo {
-namespace shell {
-
-// The "main()" for child processes. This should be called with a
-// the |base::CommandLine| singleton initialized and a |base::AtExitManager|,
-// but without a |base::MessageLoop| for the current thread. Returns the exit
-// code for the process.
-int ChildMain();
-
-}  // namespace shell
-}  // namespace mojo
-
-#endif  // SHELL_CHILD_MAIN_H_
diff --git a/shell/child_process_host.cc b/shell/child_process_host.cc
index cdc8fcd..acab25d 100644
--- a/shell/child_process_host.cc
+++ b/shell/child_process_host.cc
@@ -7,6 +7,7 @@
 #include "base/base_switches.h"
 #include "base/bind.h"
 #include "base/command_line.h"
+#include "base/files/file_path.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/macros.h"
diff --git a/shell/context.cc b/shell/context.cc
index 0ad30a4..49e5fa3 100644
--- a/shell/context.cc
+++ b/shell/context.cc
@@ -235,16 +235,24 @@
 }
 
 bool Context::Init() {
-  TRACE_EVENT0("mojo_shell", "Context::Init");
+  base::FilePath shell_path = base::MakeAbsoluteFilePath(
+      base::CommandLine::ForCurrentProcess()->GetProgram());
+  base::FilePath shell_child_path =
+      shell_path.DirName().AppendASCII("mojo_shell_child");
+  return InitWithPaths(shell_path, shell_child_path);
+}
+
+bool Context::InitWithPaths(const base::FilePath& shell_path,
+                            const base::FilePath& shell_child_path) {
+  TRACE_EVENT0("mojo_shell", "Context::InitWithPaths");
   const base::CommandLine& command_line =
       *base::CommandLine::ForCurrentProcess();
 
   if (command_line.HasSwitch(switches::kWaitForDebugger))
     base::debug::WaitForDebugger(60, true);
 
-  mojo_shell_path_ = base::MakeAbsoluteFilePath(command_line.GetProgram());
-  // TODO(vtl): For the moment, the child is the same as the parent.
-  mojo_shell_child_path_ = mojo_shell_path_;
+  mojo_shell_path_ = shell_path;
+  mojo_shell_child_path_ = shell_child_path;
 
   EnsureEmbedderIsInitialized();
   task_runners_.reset(
diff --git a/shell/context.h b/shell/context.h
index 0b3522e..ec9ab72 100644
--- a/shell/context.h
+++ b/shell/context.h
@@ -50,6 +50,11 @@
   // success.
   bool Init();
 
+  // Like Init(), but specifies values for |mojo_shell_path()| and
+  // |mojo_shell_child_path()| explicitly.
+  bool InitWithPaths(const base::FilePath& shell_path,
+                     const base::FilePath& shell_child_path);
+
   // If Init() was called and succeeded, this must be called before destruction.
   void Shutdown();
 
diff --git a/shell/desktop/main.cc b/shell/desktop/main.cc
index 81d34dd..2fea883 100644
--- a/shell/desktop/main.cc
+++ b/shell/desktop/main.cc
@@ -17,7 +17,6 @@
 #include "base/message_loop/message_loop.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/trace_event/trace_event.h"
-#include "shell/child_main.h"
 #include "shell/command_line_util.h"
 #include "shell/context.h"
 #include "shell/init.h"
@@ -112,85 +111,73 @@
 
   const base::CommandLine& command_line =
       *base::CommandLine::ForCurrentProcess();
+  if (command_line.HasSwitch(switches::kHelp) ||
+      command_line.GetArgs().empty()) {
+    Usage();
+    return 0;
+  }
 
-  // TODO(vtl): Unify parent and child process cases to the extent possible.
-  int exit_code = 0;
-  if (command_line.HasSwitch(switches::kChildProcess)) {
-    exit_code = mojo::shell::ChildMain();
-  } else {
-    // Only check the command line for the main process.
-    const std::set<std::string> all_switches = switches::GetAllSwitches();
-    const base::CommandLine::SwitchMap switches = command_line.GetSwitches();
-    bool found_unknown_switch = false;
-    for (const auto& s : switches) {
-      if (all_switches.find(s.first) == all_switches.end()) {
-        std::cerr << "unknown switch: " << s.first << std::endl;
-        found_unknown_switch = true;
-      }
-    }
-
-    if (found_unknown_switch || command_line.HasSwitch(switches::kHelp) ||
-        command_line.GetArgs().empty()) {
+  const std::set<std::string> all_switches = switches::GetAllSwitches();
+  const base::CommandLine::SwitchMap switches = command_line.GetSwitches();
+  for (const auto& s : switches) {
+    if (all_switches.find(s.first) == all_switches.end()) {
+      std::cerr << "Unknown switch: " << s.first << std::endl;
       Usage();
-      return 0;
-    }
-
-    if (command_line.HasSwitch(switches::kTraceStartup)) {
-      g_tracing = true;
-      base::trace_event::CategoryFilter category_filter(
-          command_line.GetSwitchValueASCII(switches::kTraceStartup));
-      base::trace_event::TraceLog::GetInstance()->SetEnabled(
-          category_filter, base::trace_event::TraceLog::RECORDING_MODE,
-          base::trace_event::TraceOptions(
-              base::trace_event::RECORD_UNTIL_FULL));
-    }
-
-    if (command_line.HasSwitch(switches::kCPUProfile)) {
-#if !defined(NDEBUG) || !defined(ENABLE_PROFILING)
-      LOG(ERROR) << "Profiling requires is_debug=false and "
-                 << "enable_profiling=true. Continuing without profiling.";
-// StartProfiling() and StopProfiling() are a no-op.
-#endif
-      base::debug::StartProfiling("mojo_shell.pprof");
-    }
-
-    // We want the shell::Context to outlive the MessageLoop so that pipes are
-    // all gracefully closed / error-out before we try to shut the Context down.
-    mojo::shell::Context shell_context;
-    {
-      base::MessageLoop message_loop;
-      if (!shell_context.Init()) {
-        Usage();
-        return 0;
-      }
-      if (g_tracing) {
-        message_loop.PostDelayedTask(FROM_HERE,
-                                     base::Bind(StopTracingAndFlushToDisk),
-                                     base::TimeDelta::FromSeconds(5));
-      }
-
-      // The mojo_shell --args-for command-line switch is handled specially
-      // because it can appear more than once. The base::CommandLine class
-      // collapses multiple occurrences of the same switch.
-      for (int i = 1; i < argc; i++) {
-        ApplyApplicationArgs(&shell_context, argv[i]);
-      }
-
-      message_loop.PostTask(
-          FROM_HERE,
-          base::Bind(&mojo::shell::RunCommandLineApps, &shell_context));
-      message_loop.Run();
-
-      // Must be called before |message_loop| is destroyed.
-      shell_context.Shutdown();
-    }
-
-    if (command_line.HasSwitch(switches::kCPUProfile)) {
-      base::debug::StopProfiling();
+      return 1;
     }
   }
 
+  if (command_line.HasSwitch(switches::kTraceStartup)) {
+    g_tracing = true;
+    base::trace_event::CategoryFilter category_filter(
+        command_line.GetSwitchValueASCII(switches::kTraceStartup));
+    base::trace_event::TraceLog::GetInstance()->SetEnabled(
+        category_filter, base::trace_event::TraceLog::RECORDING_MODE,
+        base::trace_event::TraceOptions(base::trace_event::RECORD_UNTIL_FULL));
+  }
+
+  if (command_line.HasSwitch(switches::kCPUProfile)) {
+#if !defined(NDEBUG) || !defined(ENABLE_PROFILING)
+    LOG(ERROR) << "Profiling requires is_debug=false and "
+               << "enable_profiling=true. Continuing without profiling.";
+// StartProfiling() and StopProfiling() are a no-op.
+#endif
+    base::debug::StartProfiling("mojo_shell.pprof");
+  }
+
+  // We want the shell::Context to outlive the MessageLoop so that pipes are all
+  // gracefully closed / error-out before we try to shut the Context down.
+  mojo::shell::Context shell_context;
+  {
+    base::MessageLoop message_loop;
+    if (!shell_context.Init()) {
+      Usage();
+      return 1;
+    }
+    if (g_tracing) {
+      message_loop.PostDelayedTask(FROM_HERE,
+                                   base::Bind(StopTracingAndFlushToDisk),
+                                   base::TimeDelta::FromSeconds(5));
+    }
+
+    // The mojo_shell --args-for command-line switch is handled specially
+    // because it can appear more than once. The base::CommandLine class
+    // collapses multiple occurrences of the same switch.
+    for (int i = 1; i < argc; i++)
+      ApplyApplicationArgs(&shell_context, argv[i]);
+
+    message_loop.PostTask(
+        FROM_HERE,
+        base::Bind(&mojo::shell::RunCommandLineApps, &shell_context));
+    message_loop.Run();
+
+    // Must be called before |message_loop| is destroyed.
+    shell_context.Shutdown();
+  }
+
+  if (command_line.HasSwitch(switches::kCPUProfile))
+    base::debug::StopProfiling();
   if (g_tracing)
     StopTracingAndFlushToDisk();
-  return exit_code;
+  return 0;
 }
diff --git a/shell/shell_test_main.cc b/shell/shell_test_main.cc
index bad2f07..6df2215 100644
--- a/shell/shell_test_main.cc
+++ b/shell/shell_test_main.cc
@@ -8,19 +8,14 @@
 #include "base/logging.h"
 #include "base/test/launcher/unit_test_launcher.h"
 #include "base/test/test_suite.h"
-#include "shell/child_main.h"
 #include "shell/switches.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 int main(int argc, char** argv) {
   base::CommandLine::Init(argc, argv);
-  const base::CommandLine& command_line =
-      *base::CommandLine::ForCurrentProcess();
 
-  if (command_line.HasSwitch(switches::kChildProcess)) {
-    base::AtExitManager at_exit;
-    return mojo::shell::ChildMain();
-  }
+  CHECK(!base::CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kChildProcess));
 
   base::TestSuite test_suite(argc, argv);
   return base::LaunchUnitTests(
diff --git a/shell/switches.cc b/shell/switches.cc
index 0453f8d..518d668 100644
--- a/shell/switches.cc
+++ b/shell/switches.cc
@@ -20,8 +20,7 @@
 // --args-for='mojo:wget http://www.google.com'
 const char kArgsFor[] = "args-for";
 
-// Used internally by the main process to indicate that a new process should be
-// a child process. Not for user use.
+// Used only by the child process. Not for user use.
 const char kChildProcess[] = "child-process";
 
 // Comma separated list like: