Rework CMake rules for building grammar tables.

Use add_custom_command() to generate all .inc files and manage
dependency with set_source_files_properties().
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5c89b22..d6d57f0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -139,41 +139,6 @@
   ${spirv-tools_SOURCE_DIR}
   COMMENT "Update build-version.inc in the Spirv-tools build directory (if necessary).")
 
-function(spvtools_core_tables VERSION)
-  add_custom_target(spirv-tools-build-core-tables-${VERSION}
-    ${PYTHON_EXECUTABLE}
-    ${CMAKE_CURRENT_SOURCE_DIR}/utils/generate_grammar_tables.py
-    --spirv-core-grammar=${spirv-tools_SOURCE_DIR}/source/spirv-${VERSION}.core.grammar.json
-    --core-insts-output=${spirv-tools_BINARY_DIR}/core.insts-${VERSION}.inc
-    --operand-kinds-output=${spirv-tools_BINARY_DIR}/operand.kinds-${VERSION}.inc
-    COMMENT "Generate info tables for SPIR-V v${VERSION} core instructions and operands.")
-endfunction(spvtools_core_tables)
-
-function(spvtools_glsl_tables VERSION)
-  add_custom_target(spirv-tools-build-glsl-tables-${VERSION}
-    ${PYTHON_EXECUTABLE}
-    ${CMAKE_CURRENT_SOURCE_DIR}/utils/generate_grammar_tables.py
-    --spirv-core-grammar=${spirv-tools_SOURCE_DIR}/source/spirv-${VERSION}.core.grammar.json
-    --extinst-glsl-grammar=${spirv-tools_SOURCE_DIR}/source/extinst-${VERSION}.glsl.std.450.grammar.json
-    --glsl-insts-output=${spirv-tools_BINARY_DIR}/glsl.std.450.insts-${VERSION}.inc
-    COMMENT "Generate info tables for GLSL extended instructions and operands v${VERSION}.")
-endfunction(spvtools_glsl_tables)
-
-function(spvtools_opencl_tables VERSION)
-  add_custom_target(spirv-tools-build-opencl-tables-${VERSION}
-    ${PYTHON_EXECUTABLE}
-    ${CMAKE_CURRENT_SOURCE_DIR}/utils/generate_grammar_tables.py
-    --spirv-core-grammar=${spirv-tools_SOURCE_DIR}/source/spirv-${VERSION}.core.grammar.json
-    --extinst-opencl-grammar=${spirv-tools_SOURCE_DIR}/source/extinst-${VERSION}.opencl.std.grammar.json
-    --opencl-insts-output=${spirv-tools_BINARY_DIR}/opencl.std.insts-${VERSION}.inc
-    COMMENT "Generate info tables for OpenCL extended instructions and operands v${VERSION}.")
-endfunction(spvtools_opencl_tables)
-
-spvtools_core_tables("1-0")
-spvtools_core_tables("1-1")
-spvtools_opencl_tables("1-0")
-spvtools_glsl_tables("1-0")
-
 # Defaults to OFF if the user didn't set it.
 option(SPIRV_SKIP_EXECUTABLES
        "Skip building the executable and tests along with the library"
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 781173e..c7c07f5 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -24,6 +24,84 @@
 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 # MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
 
+set(GRAMMAR_PROCESSING_SCRIPT "${spirv-tools_SOURCE_DIR}/utils/generate_grammar_tables.py")
+
+# macro() definitions are used in the following because we need to append .inc
+# file paths into some global lists (*_CPP_DEPENDS). And those global lists are
+# later used by set_source_files_properties() calls.
+# function() definitions are not suitable because they create new scopes.
+macro(spvtools_core_tables VERSION)
+  set(GRAMMAR_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/spirv-${VERSION}.core.grammar.json")
+  set(GRAMMAR_INSTS_INC_FILE "${spirv-tools_BINARY_DIR}/core.insts-${VERSION}.inc")
+  set(GRAMMAR_KINDS_INC_FILE "${spirv-tools_BINARY_DIR}/operand.kinds-${VERSION}.inc")
+  add_custom_command(OUTPUT ${GRAMMAR_INSTS_INC_FILE} ${GRAMMAR_KINDS_INC_FILE}
+    COMMAND ${PYTHON_EXECUTABLE} ${GRAMMAR_PROCESSING_SCRIPT}
+      --spirv-core-grammar=${GRAMMAR_JSON_FILE}
+      --core-insts-output=${GRAMMAR_INSTS_INC_FILE}
+      --operand-kinds-output=${GRAMMAR_KINDS_INC_FILE}
+    DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${GRAMMAR_JSON_FILE}
+    COMMENT "Generate info tables for SPIR-V v${VERSION} core instructions and operands.")
+  list(APPEND OPCODE_CPP_DEPENDS ${GRAMMAR_INSTS_INC_FILE})
+  list(APPEND OPERAND_CPP_DEPENDS ${GRAMMAR_KINDS_INC_FILE})
+endmacro(spvtools_core_tables)
+
+macro(spvtools_glsl_tables VERSION)
+  set(CORE_GRAMMAR_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/spirv-${VERSION}.core.grammar.json")
+  set(GLSL_GRAMMAR_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/extinst-${VERSION}.glsl.std.450.grammar.json")
+  set(GRAMMAR_INC_FILE "${spirv-tools_BINARY_DIR}/glsl.std.450.insts-${VERSION}.inc")
+  add_custom_command(OUTPUT ${GRAMMAR_INC_FILE}
+    COMMAND ${PYTHON_EXECUTABLE} ${GRAMMAR_PROCESSING_SCRIPT}
+      --spirv-core-grammar=${CORE_GRAMMAR_JSON_FILE}
+      --extinst-glsl-grammar=${GLSL_GRAMMAR_JSON_FILE}
+      --glsl-insts-output=${GRAMMAR_INC_FILE}
+    DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${CORE_GRAMMAR_JSON_FILE} ${GLSL_GRAMMAR_JSON_FILE}
+    COMMENT "Generate info tables for GLSL extended instructions and operands v${VERSION}.")
+  list(APPEND EXTINST_CPP_DEPENDS ${GRAMMAR_INC_FILE})
+endmacro(spvtools_glsl_tables)
+
+macro(spvtools_opencl_tables VERSION)
+  set(CORE_GRAMMAR_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/spirv-${VERSION}.core.grammar.json")
+  set(OPENCL_GRAMMAR_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/extinst-${VERSION}.opencl.std.grammar.json")
+  set(GRAMMAR_INC_FILE "${spirv-tools_BINARY_DIR}/opencl.std.insts-${VERSION}.inc")
+  add_custom_command(OUTPUT ${GRAMMAR_INC_FILE}
+    COMMAND ${PYTHON_EXECUTABLE} ${GRAMMAR_PROCESSING_SCRIPT}
+      --spirv-core-grammar=${CORE_GRAMMAR_JSON_FILE}
+      --extinst-opencl-grammar=${OPENCL_GRAMMAR_JSON_FILE}
+      --opencl-insts-output=${GRAMMAR_INC_FILE}
+    DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${CORE_GRAMMAR_JSON_FILE} ${OPENCL_GRAMMAR_JSON_FILE}
+    COMMENT "Generate info tables for OpenCL extended instructions and operands v${VERSION}.")
+  list(APPEND EXTINST_CPP_DEPENDS ${GRAMMAR_INC_FILE})
+endmacro(spvtools_opencl_tables)
+
+spvtools_core_tables("1-0")
+spvtools_core_tables("1-1")
+spvtools_opencl_tables("1-0")
+spvtools_glsl_tables("1-0")
+
+# The following .cpp files include the above generated .inc files.
+# Add those .inc files as their dependencies.
+#
+# Why using such an awkward way?
+# * If we use add_custom_target() to define a target to generate all .inc files
+#   and let ${SPIRV_TOOLS} depend on it, then we need to run ninja twice every
+#   time the grammar is updated: the first time is for generating those .inc
+#   files, and the second time is for rebuilding .cpp files, when ninja finds
+#   out that .inc files are updated.
+# * If we use add_custom_command() with PRE_BUILD, then the grammar processing
+#   script will always run no matter whether the grammar is updated.
+# * add_dependencies() is used to add *target* dependencies to a target.
+# * The following solution only generates .inc files when the script or the
+#   grammar files is updated, and in a single ninja run.
+set_source_files_properties(
+  ${CMAKE_CURRENT_SOURCE_DIR}/opcode.cpp
+  PROPERTIES OBJECT_DEPENDS "${OPCODE_CPP_DEPENDS}")
+set_source_files_properties(
+  ${CMAKE_CURRENT_SOURCE_DIR}/operand.cpp
+  PROPERTIES OBJECT_DEPENDS "${OPERAND_CPP_DEPENDS}")
+set_source_files_properties(
+  ${CMAKE_CURRENT_SOURCE_DIR}/operand.cpp
+  PROPERTIES OBJECT_DEPENDS "${EXTINST_CPP_DEPENDS}")
+
 set(SPIRV_SOURCES
   ${spirv-tools_SOURCE_DIR}/include/spirv-tools/libspirv.h
   ${spirv-tools_SOURCE_DIR}/include/spirv/spirv.h
@@ -76,9 +154,6 @@
   PUBLIC ${spirv-tools_SOURCE_DIR}/include
   PRIVATE ${spirv-tools_BINARY_DIR}
   )
-add_dependencies(${SPIRV_TOOLS}
-  spirv-tools-build-core-tables-1-0 spirv-tools-build-core-tables-1-1
-  spirv-tools-build-glsl-tables-1-0 spirv-tools-build-opencl-tables-1-0)
 
 install(TARGETS ${SPIRV_TOOLS}
   RUNTIME DESTINATION bin