Add the beginnings of scripts to produce SDKs.

R=vardhan@google.com

Review URL: https://codereview.chromium.org/1701323003 .
diff --git a/sdk_build/README.md b/sdk_build/README.md
new file mode 100644
index 0000000..f8cc65a
--- /dev/null
+++ b/sdk_build/README.md
@@ -0,0 +1,4 @@
+# Tools/scripts/data for building SDKs
+
+This directory contains (or will contain) tools, scripts, and data for building
+SDKs (for various languages and platforms).
diff --git a/sdk_build/build_sdk.py b/sdk_build/build_sdk.py
new file mode 100755
index 0000000..e5a7638
--- /dev/null
+++ b/sdk_build/build_sdk.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+# Copyright 2016 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.
+
+"""Builds an SDK (in a specified target directory) from the (current) source
+repository (which should not be dirty) and a given "specification" file."""
+
+
+import argparse
+from pylib.errors import FatalError
+from pylib.git import Git, IsGitTreeDirty, SanityCheckGit, GitLsFiles
+import os
+import shutil
+import sys
+
+
+def _CopyFiles(source_path, dest_path, **kwargs):
+  """Copies files from the source git repository (the current working directory
+  should be the root of this repository) to the destination path. |source_path|
+  and the keyword arguments are as for the arguments of |GitLsFiles|.
+  |dest_path| should be the "root" destination path. Note that a file such as
+  <source_path>/foo/bar/baz.quux is copied to <dest_path>/foo/bar/baz.quux."""
+
+  # Normalize the source path. Note that this strips any trailing '/'.
+  source_path = os.path.normpath(source_path)
+  source_files = GitLsFiles(source_path, **kwargs)
+  for source_file in source_files:
+    rel_path = source_file[len(source_path) + 1:]
+    dest_file = os.path.join(dest_path, rel_path)
+    try:
+      os.makedirs(os.path.dirname(dest_file))
+    except OSError:
+      pass
+    shutil.copyfile(source_file, dest_file)
+
+
+def main():
+  parser = argparse.ArgumentParser(
+      description="Constructs an SDK from a specification")
+  parser.add_argument("--allow-dirty-tree", dest="allow_dirty_tree",
+                      action="store_true",
+                      help="proceed even if the source tree is dirty")
+  parser.add_argument("sdk_spec_file", metavar="sdk_spec_file.sdk",
+                      type=argparse.FileType("rb"),
+                      help="spec file for the SDK to build")
+  parser.add_argument("target_dir",
+                      help="target directory (must not already exist)")
+  args = parser.parse_args()
+
+  target_dir = os.path.abspath(args.target_dir)
+
+  # CD to the "src" directory (we should currently be in src/sdk_build).
+  src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
+  os.chdir(src_dir)
+
+  SanityCheckGit()
+
+  if not args.allow_dirty_tree and IsGitTreeDirty():
+    FatalError("tree appears to be dirty")
+
+  try:
+    os.mkdir(target_dir)
+  except OSError:
+    FatalError("failed to create target directory %s" % target_dir)
+
+  def CopyFilesToTargetDir(source_path, rel_dest_path, **kwargs):
+    return _CopyFiles(source_path, os.path.join(target_dir, rel_dest_path),
+                      **kwargs)
+
+  execution_globals = {"CopyFiles": CopyFilesToTargetDir,
+                       "FatalError": FatalError,
+                       "GitLsFiles": GitLsFiles}
+  exec args.sdk_spec_file in execution_globals
+
+  return 0
+
+
+if __name__ == "__main__":
+  sys.exit(main())
diff --git a/sdk_build/data/cpp/cpp.sdk b/sdk_build/data/cpp/cpp.sdk
new file mode 100644
index 0000000..f6f4cec
--- /dev/null
+++ b/sdk_build/data/cpp/cpp.sdk
@@ -0,0 +1,20 @@
+# Copyright 2016 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.
+
+# This file contains steps for "building" a C/C++ SDK. It is processed by
+# //mojo/sdk_build/build_sdk.py.
+# TODO(vtl): This isn't done yet.
+
+EXCLUDE_FILES=[".*", "*.gn", "*.gni", "PRESUBMIT.py", "*_win.*"]
+EXCLUDE_PATHS=["*/tests/"]
+
+CopyFiles("mojo/public/c", "third_party/mojo/public/c", recursive=True,
+          exclude_file_patterns=EXCLUDE_FILES,
+          exclude_path_patterns=EXCLUDE_PATHS)
+CopyFiles("mojo/public/cpp", "third_party/mojo/public/cpp", recursive=True,
+          exclude_file_patterns=EXCLUDE_FILES,
+          exclude_path_patterns=EXCLUDE_PATHS+
+              ["mojo/public/cpp/test_support/*"])
+CopyFiles("mojo/public", "third_party/mojo/public", recursive=False,
+          exclude_file_patterns=EXCLUDE_FILES)
diff --git a/sdk_build/pylib/__init__.py b/sdk_build/pylib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sdk_build/pylib/__init__.py
diff --git a/sdk_build/pylib/errors.py b/sdk_build/pylib/errors.py
new file mode 100644
index 0000000..f90abfd
--- /dev/null
+++ b/sdk_build/pylib/errors.py
@@ -0,0 +1,15 @@
+# Copyright 2016 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.
+
+"""Error-handling support functions."""
+
+
+import os.path
+import sys
+
+
+def FatalError(message):
+  print >> sys.stderr, "%s: fatal error: %s" % (os.path.basename(sys.argv[0]),
+                                                message)
+  sys.exit(1)
diff --git a/sdk_build/pylib/git.py b/sdk_build/pylib/git.py
new file mode 100644
index 0000000..fa09880
--- /dev/null
+++ b/sdk_build/pylib/git.py
@@ -0,0 +1,69 @@
+# Copyright 2016 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.
+
+"""Git support functions."""
+
+
+from errors import FatalError
+import fnmatch
+import os.path
+import subprocess
+
+
+def Git(subcommand, *args):
+  """Runs "git <subcommand> <args...>" and returns the output, including
+  standard error. Raises |OSError| if it fails to run git at all and
+  |subprocess.CalledProcessError| if the command returns failure (nonzero)."""
+
+  return subprocess.check_output(
+      ('git', subcommand) + args, stderr=subprocess.STDOUT)
+
+
+def SanityCheckGit():
+  """Does a sanity check that git is available and the current working directory
+  is in a git repository (exits with an error message on failure)."""
+
+  try:
+    Git("status")
+  except OSError:
+    FatalError("failed to run git -- is it in your PATH?")
+  except subprocess.CalledProcessError:
+    FatalError("\"git status\" failed -- is %s in a git repository?" %
+        os.getcwd())
+
+
+def IsGitTreeDirty():
+  """Checks if the tree looks dirty (returning true if it is)."""
+
+  try:
+    Git("diff-index", "--quiet", "HEAD")
+  except subprocess.CalledProcessError:
+    return True
+  return False
+
+
+def GitLsFiles(path, recursive=True, exclude_file_patterns=None,
+              exclude_path_patterns=None):
+  """Returns a list of files under the given path (the filenames will include
+  the full path). If |recursive| is true (the default), then this list will
+  include all files (recursively); if not, it will only include the files "at"
+  the exact path. |exclude_file_patterns| may be a list of shell-style wildcards
+  of filenames to exclude. Similarly, |exclude_path_patterns| may be a list of
+  shell-style wildcards of paths to exclude (note: to make it easier to match
+  subtrees, a trailing '/' is added to the path for matching purposes)."""
+
+  # Files are "null-terminated". This results in an extra empty string at the
+  # end. "Luckily", in the empty case, we also get an empty string.
+  result = Git("ls-files", "-z", path).split("\0")[:-1]
+  if not recursive:
+    result = [f for f in result if os.path.dirname(f) == path]
+  if exclude_file_patterns:
+    for p in exclude_file_patterns:
+      result = [f for f in result
+                if not fnmatch.fnmatch(os.path.basename(f), p)]
+  if exclude_path_patterns:
+    for p in exclude_path_patterns:
+      result = [f for f in result
+                if not fnmatch.fnmatch(os.path.dirname(f) + "/", p)]
+  return result