Improve Windows GN build setup.

Properly setup a new output build directory on Windows (previously you had to manually copy some files into it). It now creates the environment block and the gyp-win-tool.

The include and library paths are moved to the command line and removed from the environment which is cleaner and more explicit.

BUG=
R=scottmg@chromium.org

Review URL: https://codereview.chromium.org/34603009

Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 12e400a10af6a47105c2294bb7b20084108eebe3
diff --git a/tools/gn/build_settings.cc b/tools/gn/build_settings.cc
index 2ac7389..af81467 100644
--- a/tools/gn/build_settings.cc
+++ b/tools/gn/build_settings.cc
@@ -4,6 +4,7 @@
 
 #include "tools/gn/build_settings.h"
 
+#include "base/file_util.h"
 #include "tools/gn/filesystem_utils.h"
 
 BuildSettings::BuildSettings()
@@ -45,6 +46,9 @@
 void BuildSettings::SetBuildDir(const SourceDir& d) {
   build_dir_ = d;
   build_to_source_dir_string_ = InvertDir(d);
+
+  DCHECK(!root_path_.empty());
+  file_util::CreateDirectory(build_dir_.Resolve(root_path_));
 }
 
 base::FilePath BuildSettings::GetFullPath(const SourceFile& file) const {
diff --git a/tools/gn/build_settings.h b/tools/gn/build_settings.h
index fc175df..231d040 100644
--- a/tools/gn/build_settings.h
+++ b/tools/gn/build_settings.h
@@ -60,7 +60,8 @@
 
   // The build directory is the root of all output files. The default toolchain
   // files go into here, and non-default toolchains will have separate
-  // toolchain-specific root directories inside this.
+  // toolchain-specific root directories inside this. This should be set after
+  // the root path for the proper directory to be created.
   const SourceDir& build_dir() const { return build_dir_; }
   void SetBuildDir(const SourceDir& dir);
 
diff --git a/tools/gn/ninja_binary_target_writer.cc b/tools/gn/ninja_binary_target_writer.cc
index aefb513..85dd0cd 100644
--- a/tools/gn/ninja_binary_target_writer.cc
+++ b/tools/gn/ninja_binary_target_writer.cc
@@ -95,8 +95,6 @@
 }
 
 void NinjaBinaryTargetWriter::Run() {
-  WriteEnvironment();
-
   WriteCompilerVars();
 
   std::vector<OutputFile> obj_files;
diff --git a/tools/gn/ninja_script_target_writer.cc b/tools/gn/ninja_script_target_writer.cc
index 4678ba0..b362976 100644
--- a/tools/gn/ninja_script_target_writer.cc
+++ b/tools/gn/ninja_script_target_writer.cc
@@ -22,8 +22,6 @@
 }
 
 void NinjaScriptTargetWriter::Run() {
-  WriteEnvironment();
-
   FileTemplate args_template(target_->script_values().args());
   std::string custom_rule_name = WriteRuleDefinition(args_template);
   std::string implicit_deps = GetSourcesImplicitDeps();
diff --git a/tools/gn/ninja_target_writer.cc b/tools/gn/ninja_target_writer.cc
index 4bbc1f5..a7bd6cc 100644
--- a/tools/gn/ninja_target_writer.cc
+++ b/tools/gn/ninja_target_writer.cc
@@ -85,12 +85,6 @@
                        static_cast<int>(contents.size()));
 }
 
-void NinjaTargetWriter::WriteEnvironment() {
-  // TODO(brettw) have a better way to do the environment setup on Windows.
-  if (target_->settings()->IsWin())
-    out_ << "arch = environment.x86\n";
-}
-
 const Toolchain* NinjaTargetWriter::GetToolchain() const {
   return target_->settings()->toolchain();
 }
diff --git a/tools/gn/ninja_target_writer.h b/tools/gn/ninja_target_writer.h
index 9e6f240..c9c72a7 100644
--- a/tools/gn/ninja_target_writer.h
+++ b/tools/gn/ninja_target_writer.h
@@ -27,8 +27,6 @@
   virtual void Run() = 0;
 
  protected:
-  void WriteEnvironment();
-
   // Returns the toolchain associated with the target.
   const Toolchain* GetToolchain() const;
 
diff --git a/tools/gn/secondary/build/config/win/BUILD.gn b/tools/gn/secondary/build/config/win/BUILD.gn
index efdc5ab..e101c2b 100644
--- a/tools/gn/secondary/build/config/win/BUILD.gn
+++ b/tools/gn/secondary/build/config/win/BUILD.gn
@@ -3,7 +3,12 @@
 # found in the LICENSE file.
 
 declare_args() {
+  # Full path to the Windows SDK, not including a backslash at the end.
   windows_sdk_path = "C:\Program Files (x86)\Windows Kits\8.0"
+
+  # Full path to the Visual Studio installation, not including a backslash
+  # at the end.
+  visual_studio_path = "C:\Program Files (x86)\Microsoft Visual Studio 10.0"
 }
 
 # Compiler setup for the Windows SDK. Applied to all targets.
@@ -25,10 +30,15 @@
     "WINVER=0x0602",
   ]
 
+  # The Windows SDK include directories must be first. They both have a sal.h,
+  # and the SDK one is newer and the SDK uses some newer features from it not
+  # present in the Visual Studio one.
   include_dirs = [
     "$windows_sdk_path\Include\shared",
     "$windows_sdk_path\Include\um",
     "$windows_sdk_path\Include\winrt",
+    "$visual_studio_path\VC\include",
+    "$visual_studio_path\VC\atlmfc\include",
   ]
 }
 
@@ -39,14 +49,21 @@
 
   if (is_64bit) {
     ldflags = [ "/MACHINE:X64" ]
-    libs = [ "$windows_sdk_path\Lib\win8\um\x64" ]
+    lib_dirs = [
+      "$windows_sdk_path\Lib\win8\um\x64",
+      "$visual_studio_path\VC\lib\amd64",
+      "$visual_studio_path\VC\atlmfc\lib\amd64",
+    ]
   } else {
     ldflags = [
       "/MACHINE:X86",
       "/SAFESEH",  # Not compatible with x64 so use only for x86.
     ]
-    lib_dirs = [ "$windows_sdk_path\Lib\win8\um\x86" ]
-
+    lib_dirs = [
+      "$windows_sdk_path\Lib\win8\um\x86",
+      "$visual_studio_path\VC\lib",
+      "$visual_studio_path\VC\atlmfc\lib",
+    ]
     #if (!is_asan) {  TODO(brettw) Address Sanitizer
     #  ldflags += "/largeaddressaware"
     #}
diff --git a/tools/gn/secondary/build/toolchain/win/BUILD.gn b/tools/gn/secondary/build/toolchain/win/BUILD.gn
index 5887b92..95cd947 100644
--- a/tools/gn/secondary/build/toolchain/win/BUILD.gn
+++ b/tools/gn/secondary/build/toolchain/win/BUILD.gn
@@ -13,11 +13,16 @@
 #
 # The list contains the include path as its only element. (I'm expecting to
 # add more so it's currently a list inside a list.)
-msvc_config = [["foo"]]
 #exec_script("get_msvc_config.py",
                      #     [relative_root_output_dir],
                      #     "value")
 
+# This will save the environment block and and copy the gyp-win-tool to the
+# build directory. We pass in the source file of the win tool.
+gyp_win_tool_source =
+  rebase_path("//tools/gyp/pylib/gyp/win_tool.py", ".", root_build_dir)
+exec_script("setup_toolchain.py", [ gyp_win_tool_source ], "value")
+
 # 32-bit toolchain -------------------------------------------------------------
 
 toolchain("32") {
@@ -26,47 +31,47 @@
   lib_dir_prefix="/LIBPATH:"
 
   tool("cc") {
-    command = "ninja -t msvc -e \$arch -- cl.exe /nologo /showIncludes /FC @\$out.rsp /c \$in /Fo\$out /Fd\$pdbname"
+    command = "ninja -t msvc -e environment.x86 -- cl.exe /nologo /showIncludes /FC @\$out.rsp /c \$in /Fo\$out /Fd\$pdbname"
     description = "CC \$out"
     rspfile = "\$out.rsp"
     rspfile_content = "\$defines \$includes \$cflags \$cflags_c"
     deps = "msvc"
   }
   tool("cxx") {
-    command = "ninja -t msvc -e \$arch -- cl.exe /nologo /showIncludes /FC @\$out.rsp /c \$in /Fo\$out /Fd\$pdbname"
+    command = "ninja -t msvc -e environment.x86 -- cl.exe /nologo /showIncludes /FC @\$out.rsp /c \$in /Fo\$out /Fd\$pdbname"
     description = "CXX \$out"
     rspfile = "\$out.rsp"
     rspfile_content = "\$defines \$includes \$cflags \$cflags_cc"
     deps = "msvc"
   }
   #tool("idl") {
-  #  command = $python_path gyp-win-tool midl-wrapper \$arch \$outdir \$tlb \$h \$dlldata \$iid \$
+  #  command = $python_path gyp-win-tool midl-wrapper environment.x86 \$outdir \$tlb \$h \$dlldata \$iid \$
   #      \$proxy \$in \$idlflags
   #  description = IDL \$in
   #}
   tool("rc") {
-    command = "$python_path gyp-win-tool rc-wrapper \$arch rc.exe \$defines \$includes \$rcflags /fo\$out \$in"
+    command = "$python_path gyp-win-tool rc-wrapper environment.x86 rc.exe \$defines \$includes \$rcflags /fo\$out \$in"
     description = "RC \$in"
   }
   #tool("asm") {
-  #  command = $python_path gyp-win-tool asm-wrapper \$arch ml.exe \$defines \$includes /c /Fo \$
+  #  command = $python_path gyp-win-tool asm-wrapper environment.x86 ml.exe \$defines \$includes /c /Fo \$
   #      \$out \$in
   #  description = ASM \$in
   #}
   tool("alink") {
-    command = "$python_path gyp-win-tool link-wrapper \$arch lib.exe /nologo /ignore:4221 /OUT:\$out @\$out.rsp"
+    command = "$python_path gyp-win-tool link-wrapper environment.x86 lib.exe /nologo /ignore:4221 /OUT:\$out @\$out.rsp"
     description = "LIB \$out"
     rspfile = "\$out.rsp"
     rspfile_content = "\$in_newline \$libflags"
   }
   #tool("solink_embed_inc") {
-  #  command = cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo \$implibflag \$
+  #  command = cmd /c $python_path gyp-win-tool link-wrapper environment.x86 link.exe /nologo \$implibflag \$
   #      /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp && $python_path gyp-win-tool \$
-  #      manifest-wrapper \$arch cmd /c if exist \$dll.manifest del \$dll.manifest && \$
-  #      $python_path gyp-win-tool manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests \$
-  #      -out:\$dll.manifest && $python_path gyp-win-tool manifest-to-rc \$arch \$dll.manifest \$
-  #      \$dll.manifest.rc 2 && $python_path gyp-win-tool rc-wrapper \$arch rc.exe \$
-  #      \$dll.manifest.rc && $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo \$
+  #      manifest-wrapper environment.x86 cmd /c if exist \$dll.manifest del \$dll.manifest && \$
+  #      $python_path gyp-win-tool manifest-wrapper environment.x86 mt.exe -nologo -manifest \$manifests \$
+  #      -out:\$dll.manifest && $python_path gyp-win-tool manifest-to-rc environment.x86 \$dll.manifest \$
+  #      \$dll.manifest.rc 2 && $python_path gyp-win-tool rc-wrapper environment.x86 rc.exe \$
+  #      \$dll.manifest.rc && $python_path gyp-win-tool link-wrapper environment.x86 link.exe /nologo \$
   #      \$implibflag /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp \$dll.manifest.res
   #  description = LINK_EMBED_INC(DLL) \$dll
   #  restat = 1
@@ -74,13 +79,13 @@
   #  rspfile_content = \$libs \$in_newline \$ldflags
   #}
   #tool("solink_module_embed_inc") {
-  #  command = cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo \$implibflag \$
+  #  command = cmd /c $python_path gyp-win-tool link-wrapper environment.x86 link.exe /nologo \$implibflag \$
   #      /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp && $python_path gyp-win-tool \$
-  #      manifest-wrapper \$arch cmd /c if exist \$dll.manifest del \$dll.manifest && \$
-  #      $python_path gyp-win-tool manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests \$
-  #      -out:\$dll.manifest && $python_path gyp-win-tool manifest-to-rc \$arch \$dll.manifest \$
-  #      \$dll.manifest.rc 2 && $python_path gyp-win-tool rc-wrapper \$arch rc.exe \$
-  #      \$dll.manifest.rc && $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo \$
+  #      manifest-wrapper environment.x86 cmd /c if exist \$dll.manifest del \$dll.manifest && \$
+  #      $python_path gyp-win-tool manifest-wrapper environment.x86 mt.exe -nologo -manifest \$manifests \$
+  #      -out:\$dll.manifest && $python_path gyp-win-tool manifest-to-rc environment.x86 \$dll.manifest \$
+  #      \$dll.manifest.rc 2 && $python_path gyp-win-tool rc-wrapper environment.x86 rc.exe \$
+  #      \$dll.manifest.rc && $python_path gyp-win-tool link-wrapper environment.x86 link.exe /nologo \$
   #      \$implibflag /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp \$dll.manifest.res
   #  description = LINK_EMBED_INC(DLL) \$dll
   #  restat = 1
@@ -88,54 +93,54 @@
   #  rspfile_content = \$libs \$in_newline \$ldflags
   #}
   #rule link_embed_inc
-  #  command = cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo /OUT:\$out \$
-  #      /PDB:\$out.pdb @\$out.rsp && $python_path gyp-win-tool manifest-wrapper \$arch cmd /c \$
+  #  command = cmd /c $python_path gyp-win-tool link-wrapper environment.x86 link.exe /nologo /OUT:\$out \$
+  #      /PDB:\$out.pdb @\$out.rsp && $python_path gyp-win-tool manifest-wrapper environment.x86 cmd /c \$
   #      if exist \$out.manifest del \$out.manifest && $python_path gyp-win-tool \$
-  #      manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests -out:\$out.manifest && \$
-  #      $python_path gyp-win-tool manifest-to-rc \$arch \$out.manifest \$out.manifest.rc 1 && \$
-  #      $python_path gyp-win-tool rc-wrapper \$arch rc.exe \$out.manifest.rc && \$
-  #      $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo /OUT:\$out /PDB:\$out.pdb \$
+  #      manifest-wrapper environment.x86 mt.exe -nologo -manifest \$manifests -out:\$out.manifest && \$
+  #      $python_path gyp-win-tool manifest-to-rc environment.x86 \$out.manifest \$out.manifest.rc 1 && \$
+  #      $python_path gyp-win-tool rc-wrapper environment.x86 rc.exe \$out.manifest.rc && \$
+  #      $python_path gyp-win-tool link-wrapper environment.x86 link.exe /nologo /OUT:\$out /PDB:\$out.pdb \$
   #      @\$out.rsp \$out.manifest.res
   #  description = LINK_EMBED_INC \$out
   #  rspfile = \$out.rsp
   #  rspfile_content = \$in_newline \$libs \$ldflags
   #rule solink_embed
-  #  command = cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo \$implibflag \$
+  #  command = cmd /c $python_path gyp-win-tool link-wrapper environment.x86 link.exe /nologo \$implibflag \$
   #      /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp && $python_path gyp-win-tool \$
-  #      manifest-wrapper \$arch cmd /c if exist \$dll.manifest del \$dll.manifest && \$
-  #      $python_path gyp-win-tool manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests \$
+  #      manifest-wrapper environment.x86 cmd /c if exist \$dll.manifest del \$dll.manifest && \$
+  #      $python_path gyp-win-tool manifest-wrapper environment.x86 mt.exe -nologo -manifest \$manifests \$
   #      -outputresource:\$dll;2
   #  description = LINK_EMBED(DLL) \$dll
   #  restat = 1
   #  rspfile = \$dll.rsp
   #  rspfile_content = \$libs \$in_newline \$ldflags
   #rule solink_module_embed
-  #  command = cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo \$implibflag \$
+  #  command = cmd /c $python_path gyp-win-tool link-wrapper environment.x86 link.exe /nologo \$implibflag \$
   #      /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp && $python_path gyp-win-tool \$
-  #      manifest-wrapper \$arch cmd /c if exist \$dll.manifest del \$dll.manifest && \$
-  #      $python_path gyp-win-tool manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests \$
+  #      manifest-wrapper environment.x86 cmd /c if exist \$dll.manifest del \$dll.manifest && \$
+  #      $python_path gyp-win-tool manifest-wrapper environment.x86 mt.exe -nologo -manifest \$manifests \$
   #      -outputresource:\$dll;2
   #  description = LINK_EMBED(DLL) \$dll
   #  restat = 1
   #  rspfile = \$dll.rsp
   #  rspfile_content = \$libs \$in_newline \$ldflags
   #rule link_embed
-  #  command = cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo /OUT:\$out \$
-  #      /PDB:\$out.pdb @\$out.rsp && $python_path gyp-win-tool manifest-wrapper \$arch cmd /c \$
+  #  command = cmd /c $python_path gyp-win-tool link-wrapper environment.x86 link.exe /nologo /OUT:\$out \$
+  #      /PDB:\$out.pdb @\$out.rsp && $python_path gyp-win-tool manifest-wrapper environment.x86 cmd /c \$
   #      if exist \$out.manifest del \$out.manifest && $python_path gyp-win-tool \$
-  #      manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests -outputresource:\$out;1
+  #      manifest-wrapper environment.x86 mt.exe -nologo -manifest \$manifests -outputresource:\$out;1
   #  description = LINK_EMBED \$out
   #  rspfile = \$out.rsp
   #  rspfile_content = \$in_newline \$libs \$ldflags
   tool("solink") {
-    command = "cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo \$implibflag /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp && $python_path gyp-win-tool manifest-wrapper \$arch cmd /c if exist \$dll.manifest del \$dll.manifest && $python_path gyp-win-tool manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests -out:\$dll.manifest"
+    command = "cmd /c $python_path gyp-win-tool link-wrapper environment.x86 link.exe /nologo \$implibflag /DLL /OUT:\$dll /PDB:\$dll.pdb @\$dll.rsp && $python_path gyp-win-tool manifest-wrapper environment.x86 cmd /c if exist \$dll.manifest del \$dll.manifest && $python_path gyp-win-tool manifest-wrapper environment.x86 mt.exe -nologo -manifest \$manifests -out:\$dll.manifest"
     description = "LINK(DLL) \$dll"
     restat = "1"
     rspfile = "\$dll.rsp"
     rspfile_content = "\$libs \$in_newline \$ldflags"
   }
   tool("link") {
-    command = "cmd /c $python_path gyp-win-tool link-wrapper \$arch link.exe /nologo /OUT:\$out /PDB:\$out.pdb @\$out.rsp && $python_path gyp-win-tool manifest-wrapper \$arch cmd /c if exist \$out.manifest del \$out.manifest && $python_path gyp-win-tool manifest-wrapper \$arch mt.exe -nologo -manifest \$manifests -out:\$out.manifest"
+    command = "cmd /c $python_path gyp-win-tool link-wrapper environment.x86 link.exe /nologo /OUT:\$out /PDB:\$out.pdb @\$out.rsp && $python_path gyp-win-tool manifest-wrapper environment.x86 cmd /c if exist \$out.manifest del \$out.manifest && $python_path gyp-win-tool manifest-wrapper environment.x86 mt.exe -nologo -manifest \$manifests -out:\$out.manifest"
     description = "LINK \$out"
     rspfile = "\$out.rsp"
     rspfile_content = "\$in_newline \$libs \$ldflags"
diff --git a/tools/gn/secondary/build/toolchain/win/setup_toolchain.py b/tools/gn/secondary/build/toolchain/win/setup_toolchain.py
new file mode 100644
index 0000000..bba18df
--- /dev/null
+++ b/tools/gn/secondary/build/toolchain/win/setup_toolchain.py
@@ -0,0 +1,72 @@
+# Copyright (c) 2013 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.
+
+import os
+import re
+import sys
+
+def ExtractImportantEnvironment():
+  """Extracts environment variables required for the toolchain from the
+  current environment."""
+  envvars_to_save = (
+      'goma_.*', # TODO(scottmg): This is ugly, but needed for goma.
+      'Path',
+      'PATHEXT',
+      'SystemRoot',
+      'TEMP',
+      'TMP',
+      )
+  result = {}
+  for envvar in envvars_to_save:
+    if envvar in os.environ:
+      if envvar == 'Path':
+        # Our own rules (for running gyp-win-tool) and other actions in
+        # Chromium rely on python being in the path. Add the path to this
+        # python here so that if it's not in the path when ninja is run
+        # later, python will still be found.
+        result[envvar.upper()] = os.path.dirname(sys.executable) + \
+            os.pathsep + os.environ[envvar]
+      else:
+        result[envvar.upper()] = os.environ[envvar]
+  for required in ('SYSTEMROOT', 'TEMP', 'TMP'):
+    if required not in result:
+      raise Exception('Environment variable "%s" '
+                      'required to be set to valid path' % required)
+  return result
+
+def FormatAsEnvironmentBlock(envvar_dict):
+  """Format as an 'environment block' directly suitable for CreateProcess.
+  Briefly this is a list of key=value\0, terminated by an additional \0. See
+  CreateProcess documentation for more details."""
+  block = ''
+  nul = '\0'
+  for key, value in envvar_dict.iteritems():
+    block += key + '=' + value + nul
+  block += nul
+  return block
+
+def CopyTool(source_path):
+  """Copies the given tool to the current directory, including a warning not
+  to edit it."""
+  with open(source_path) as source_file:
+    tool_source = source_file.readlines()
+
+  # Add header and write it out to the current directory (which should be the
+  # root build dir).
+  with open("gyp-win-tool", 'w') as tool_file:
+    tool_file.write(''.join([tool_source[0],
+                             '# Generated by setup_toolchain.py do not edit.\n']
+                            + tool_source[1:]))
+
+# Find the tool source, it's the first argument, and copy it.
+if len(sys.argv) != 2:
+  print "Need one argument (win_tool source path)."
+  sys.exit(1)
+CopyTool(sys.argv[1])
+
+# Write the environment file to the current directory.
+environ = FormatAsEnvironmentBlock(ExtractImportantEnvironment())
+with open('environment.x86', 'wb') as env_file:
+  env_file.write(environ)
+