cmake: use assembly language support

This commit moves the asm libraries to object libraries through CMake's
ASM and ASM_NASM support. It brings the following benefits:

- we no longer need to duplicate between static and shared libraries
- Nasm objects can now be fully compiled in parallel (as they're not
  custom targets anymore)
- there's no longer the need for dummy C files to determine the linker

The ASM language support is currently unused, but it's been added since
AS_EXECUTABLE was used for both Nasm and GAS files.

For Xcode, the existing approach has to be preserved due to the
following issues:

- the Xcode generator does not recognise ASM_NASM as a valid language,
  so it does not output build rules
- even if this was fixed (with explicitFileType set to
  "sourcecode.nasm"), the default build rule in Xcode does not carry
  any of the ASM_NASM flags, nor the selected object format

These issues were already detected in [1], and I asked about them in
the CMake Discourse forum [2], to no avail yet.

Since this change relies on CMake's CheckLanguage module and ASM_NASM
language support, the minimum version has been increased to 3.9.

[1]: https://cmake.org/pipermail/cmake/2018-October/068332.html
[2]: https://discourse.cmake.org/t/building-nasm-files-with-xcode/7934

Related: aomedia:3401
Change-Id: Id0d4c8208afa403df30c0b8ab9b0809e6d67d6d8
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2e38010..50c4efd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,7 +11,7 @@
 if(CONFIG_TFLITE)
   cmake_minimum_required(VERSION 3.11)
 else()
-  cmake_minimum_required(VERSION 3.7)
+  cmake_minimum_required(VERSION 3.9)
 endif()
 
 set(AOM_ROOT "${CMAKE_CURRENT_SOURCE_DIR}")
diff --git a/aom_ports/aom_ports.cmake b/aom_ports/aom_ports.cmake
index 92d3030..e3b67e4 100644
--- a/aom_ports/aom_ports.cmake
+++ b/aom_ports/aom_ports.cmake
@@ -49,9 +49,13 @@
 #
 # * The libaom target must exist before this function is called.
 function(setup_aom_ports_targets)
-  if(WIN32 AND "${AOM_TARGET_CPU}" STREQUAL "x86_64")
+  if(XCODE AND "${AOM_TARGET_CPU}" STREQUAL "x86_64")
     add_asm_library("aom_ports" "AOM_PORTS_ASM_X86")
-    set(aom_ports_asm_lib 1)
+    # Xcode is the only one
+    set(aom_ports_is_embedded 1)
+    set(aom_ports_has_symbols 1)
+  elseif(WIN32 AND "${AOM_TARGET_CPU}" STREQUAL "x86_64")
+    add_asm_library("aom_ports" "AOM_PORTS_ASM_X86")
     set(aom_ports_has_symbols 1)
   elseif("${AOM_TARGET_CPU}" MATCHES "arm")
     add_library(aom_ports OBJECT ${AOM_PORTS_SOURCES_ARM})
@@ -74,14 +78,7 @@
   # libaom_srcs.*; if it becomes necessary for a particular generator another
   # method should be used.
   if(aom_ports_has_symbols)
-    if(aom_ports_asm_lib)
-      # When aom_ports is an asm library its name changes based on build
-      # configuration. This handles adding sources to the correct target(s).
-      target_sources(aom_ports_static PRIVATE ${AOM_PORTS_INCLUDES})
-      if(BUILD_SHARED_LIBS)
-        target_sources(aom_ports_shared PRIVATE ${AOM_PORTS_INCLUDES})
-      endif()
-    else()
+    if(NOT aom_ports_is_embedded)
       target_sources(aom_ports PRIVATE ${AOM_PORTS_INCLUDES})
     endif()
     set(AOM_LIB_TARGETS ${AOM_LIB_TARGETS} PARENT_SCOPE)
diff --git a/build/cmake/aom_configure.cmake b/build/cmake/aom_configure.cmake
index d5fa169..33def6c 100644
--- a/build/cmake/aom_configure.cmake
+++ b/build/cmake/aom_configure.cmake
@@ -155,49 +155,61 @@
 endif()
 
 if(AOM_TARGET_CPU STREQUAL "x86" OR AOM_TARGET_CPU STREQUAL "x86_64")
-  find_program(AS_EXECUTABLE yasm $ENV{YASM_PATH})
-  if(NOT AS_EXECUTABLE OR ENABLE_NASM)
-    unset(AS_EXECUTABLE CACHE)
-    find_program(AS_EXECUTABLE nasm $ENV{NASM_PATH})
-    if(AS_EXECUTABLE)
-      test_nasm()
-    endif()
+  find_program(CMAKE_ASM_NASM_COMPILER yasm $ENV{YASM_PATH})
+  if(NOT CMAKE_ASM_NASM_COMPILER OR ENABLE_NASM)
+    unset(CMAKE_ASM_NASM_COMPILER CACHE)
+    find_program(CMAKE_ASM_NASM_COMPILER nasm $ENV{NASM_PATH})
   endif()
 
-  if(NOT AS_EXECUTABLE)
+  include(CheckLanguage)
+  check_language(ASM_NASM)
+  if(CMAKE_ASM_NASM_COMPILER)
+    get_asm_obj_format("objformat")
+    unset(CMAKE_ASM_NASM_OBJECT_FORMAT)
+    set(CMAKE_ASM_NASM_OBJECT_FORMAT ${objformat})
+    enable_language(ASM_NASM)
+    if(CMAKE_ASM_NASM_COMPILER_ID STREQUAL "NASM")
+      test_nasm()
+    endif()
+    # Xcode requires building the objects manually, so pass the object format
+    # flag.
+    if(XCODE)
+      set(AOM_AS_FLAGS -f ${objformat} ${AOM_AS_FLAGS})
+    endif()
+  else()
     message(
       FATAL_ERROR
         "Unable to find assembler. Install 'yasm' or 'nasm.' "
         "To build without optimizations, add -DAOM_TARGET_CPU=generic to "
         "your cmake command line.")
   endif()
-  get_asm_obj_format("objformat")
-  set(AOM_AS_FLAGS -f ${objformat} ${AOM_AS_FLAGS})
   string(STRIP "${AOM_AS_FLAGS}" AOM_AS_FLAGS)
 elseif(AOM_TARGET_CPU MATCHES "arm")
   if(AOM_TARGET_SYSTEM STREQUAL "Darwin")
-    set(AS_EXECUTABLE as)
+    set(CMAKE_ASM_COMPILER as)
     set(AOM_AS_FLAGS -arch ${AOM_TARGET_CPU} -isysroot ${CMAKE_OSX_SYSROOT})
   elseif(AOM_TARGET_SYSTEM STREQUAL "Windows")
-    if(NOT AS_EXECUTABLE)
-      set(AS_EXECUTABLE ${CMAKE_C_COMPILER} -c -mimplicit-it=always)
+    if(NOT CMAKE_ASM_COMPILER)
+      set(CMAKE_ASM_COMPILER ${CMAKE_C_COMPILER} -c -mimplicit-it=always)
     endif()
   else()
-    if(NOT AS_EXECUTABLE)
-      set(AS_EXECUTABLE as)
+    if(NOT CMAKE_ASM_COMPILER)
+      set(CMAKE_ASM_COMPILER as)
     endif()
   endif()
-  find_program(as_executable_found ${AS_EXECUTABLE})
-  if(NOT as_executable_found)
+  include(CheckLanguage)
+  check_language(ASM)
+  if(NOT CMAKE_ASM_COMPILER)
     message(
       FATAL_ERROR
         "Unable to find assembler and optimizations are enabled."
-        "Searched for ${AS_EXECUTABLE}. Install it, add it to your path, or "
-        "set the assembler directly by adding -DAS_EXECUTABLE=<assembler path> "
-        "to your CMake command line."
+        "Searched for ${CMAKE_ASM_COMPILER}. Install it, add it to your path,"
+        "or set the assembler directly by adding "
+        "-DCMAKE_ASM_COMPILER=<assembler path> to your CMake command line."
         "To build without optimizations, add -DAOM_TARGET_CPU=generic to your "
         "cmake command line.")
   endif()
+  enable_language(ASM)
   string(STRIP "${AOM_AS_FLAGS}" AOM_AS_FLAGS)
 endif()
 
diff --git a/build/cmake/aom_optimization.cmake b/build/cmake/aom_optimization.cmake
index ff0193a..f8eeb82 100644
--- a/build/cmake/aom_optimization.cmake
+++ b/build/cmake/aom_optimization.cmake
@@ -133,7 +133,7 @@
 # language, and that build tools don't complain that an object exposes no
 # symbols.
 #
-# In shared library configs every step described above happens twice, and
+# In Xcode-based builds every step described above happens twice, and
 # directory/target/object names are updated to include _shared and _static
 # suffixes.
 function(add_asm_library lib_name asm_sources)
@@ -141,49 +141,66 @@
     return()
   endif()
 
-  list(APPEND asm_configs "static")
-  if(BUILD_SHARED_LIBS)
-    list(APPEND asm_configs "shared")
-  endif()
-
-  foreach(asm_config ${asm_configs})
-    set(asm_lib_name ${lib_name}_${asm_config})
-    set(asm_lib_obj_dir "${AOM_CONFIG_DIR}/asm_objects/${asm_lib_name}")
-    if(NOT EXISTS "${asm_lib_obj_dir}")
-      file(MAKE_DIRECTORY "${asm_lib_obj_dir}")
+  if(XCODE)
+    # CMake's generator does not output a build rule for Nasm files. Moreover,
+    # it makes Xcode believe Nasm files are of type "sourcecode" instead of
+    # "sourcecode.nasm", which prevents even the default rule from applying.
+    # This default rule is broken, though, because it doesn't apply any of the
+    # flags specified for ASM_NASM. See https://discourse.cmake.org/t/building-
+    # nasm-files-with-xcode/7934
+    list(APPEND asm_configs "static")
+    if(BUILD_SHARED_LIBS)
+      list(APPEND asm_configs "shared")
     endif()
 
-    add_library(${asm_lib_name} STATIC ${${asm_sources}})
-    set_property(TARGET ${asm_lib_name} PROPERTY FOLDER ${AOM_TARGET_CPU})
+    set(as_executable "${CMAKE_ASM_NASM_COMPILER}")
+    if(NOT as_executable)
+      set(as_executable "${CMAKE_ASM_COMPILER}")
+    endif()
 
-    foreach(asm_source ${${asm_sources}})
-      get_filename_component(asm_source_name "${asm_source}" NAME)
-      set(asm_object "${asm_lib_obj_dir}/${asm_source_name}.o")
-      add_custom_command(OUTPUT "${asm_object}"
-                         COMMAND ${AS_EXECUTABLE} ARGS ${AOM_AS_FLAGS}
-                                 -I${AOM_ROOT}/ -I${AOM_CONFIG_DIR}/ -o
-                                 "${asm_object}" "${asm_source}"
-                         DEPENDS "${asm_source}"
-                         COMMENT "Building ASM object ${asm_object}"
-                         WORKING_DIRECTORY "${AOM_CONFIG_DIR}"
-                         VERBATIM)
-      if(BUILD_SHARED_LIBS AND "${asm_config}" STREQUAL "static")
-        target_sources(aom_static PRIVATE "${asm_object}")
-      else()
-        target_sources(aom PRIVATE "${asm_object}")
+    foreach(asm_config ${asm_configs})
+      set(asm_lib_name ${lib_name}_${asm_config})
+      set(asm_lib_obj_dir "${AOM_CONFIG_DIR}/asm_objects/${asm_lib_name}")
+      if(NOT EXISTS "${asm_lib_obj_dir}")
+        file(MAKE_DIRECTORY "${asm_lib_obj_dir}")
       endif()
-    endforeach()
 
-    # The above created a target containing only ASM sources. CMake needs help
-    # here to determine the linker language. Add a C file with an unused no-op
-    # function to force the linker language to C. We don't bother with setting
-    # the LINKER_LANGUAGE property on the library target because not all
-    # generators obey it (looking at you, Xcode generator).
-    add_no_op_source_file_to_target("${asm_lib_name}" "c")
+      foreach(asm_source ${${asm_sources}})
+        get_filename_component(asm_source_name "${asm_source}" NAME)
+        set(asm_object "${asm_lib_obj_dir}/${asm_source_name}.o")
+        add_custom_command(OUTPUT "${asm_object}"
+                           COMMAND ${as_executable} ARGS ${AOM_AS_FLAGS}
+                                   -I${AOM_ROOT}/ -I${AOM_CONFIG_DIR}/ -o
+                                   "${asm_object}" "${asm_source}"
+                           DEPENDS "${asm_source}"
+                           COMMENT "Building ASM object ${asm_object}"
+                           WORKING_DIRECTORY "${AOM_CONFIG_DIR}"
+                           VERBATIM)
+        if(BUILD_SHARED_LIBS AND "${asm_config}" STREQUAL "static")
+          target_sources(aom_static PRIVATE "${asm_object}")
+        else()
+          target_sources(aom PRIVATE "${asm_object}")
+        endif()
+      endforeach()
+    endforeach()
+  else()
+    # For non-Xcode generators, CMake does not need extra help. The language
+    # support takes care of it.
+    set(asm_lib_name ${lib_name})
+
+    add_library(${asm_lib_name} OBJECT ${${asm_sources}})
+    target_include_directories(${asm_lib_name}
+                               PRIVATE ${AOM_ROOT} ${AOM_CONFIG_DIR})
+    target_compile_options(${asm_lib_name} PRIVATE ${AOM_AS_FLAGS})
+    set_property(TARGET ${asm_lib_name} PROPERTY FOLDER ${AOM_TARGET_CPU})
+    if(BUILD_SHARED_LIBS)
+      target_sources(aom_static PRIVATE "$<TARGET_OBJECTS:${asm_lib_name}>")
+    endif()
+    target_sources(aom PRIVATE "$<TARGET_OBJECTS:${asm_lib_name}>")
 
     # Add the new lib target to the global list of aom library targets.
     list(APPEND AOM_LIB_TARGETS ${asm_lib_name})
-  endforeach()
+  endif()
 
   set(AOM_LIB_TARGETS ${AOM_LIB_TARGETS} PARENT_SCOPE)
 endfunction()
@@ -192,7 +209,8 @@
 # Currently checks only for presence of required object formats and support for
 # the -Ox argument (multipass optimization).
 function(test_nasm)
-  execute_process(COMMAND ${AS_EXECUTABLE} -hf OUTPUT_VARIABLE nasm_helptext)
+  execute_process(COMMAND ${CMAKE_ASM_NASM_COMPILER} -hf
+                  OUTPUT_VARIABLE nasm_helptext)
 
   if(NOT "${nasm_helptext}" MATCHES "-Ox")
     message(
diff --git a/build/cmake/toolchains/android.cmake b/build/cmake/toolchains/android.cmake
index f0b9fab..4d38c9a 100644
--- a/build/cmake/toolchains/android.cmake
+++ b/build/cmake/toolchains/android.cmake
@@ -45,11 +45,11 @@
 endif()
 
 if(ANDROID_ABI MATCHES "^arm")
-  set(AS_EXECUTABLE as)
+  set(CMAKE_ASM_COMPILER as)
   # No runtime cpu detect for arm targets.
   set(CONFIG_RUNTIME_CPU_DETECT 0 CACHE STRING "")
 elseif(ANDROID_ABI MATCHES "^x86")
-  set(AS_EXECUTABLE yasm)
+  set(CMAKE_ASM_NASM_COMPILER yasm)
 endif()
 
 set(CMAKE_SYSTEM_NAME "Android")
diff --git a/build/cmake/toolchains/arm64-linux-gcc.cmake b/build/cmake/toolchains/arm64-linux-gcc.cmake
index 64e460b..a510524 100644
--- a/build/cmake/toolchains/arm64-linux-gcc.cmake
+++ b/build/cmake/toolchains/arm64-linux-gcc.cmake
@@ -27,8 +27,8 @@
 if(NOT CMAKE_CXX_COMPILER)
   set(CMAKE_CXX_COMPILER ${CROSS}g++)
 endif()
-if(NOT AS_EXECUTABLE)
-  set(AS_EXECUTABLE ${CROSS}as)
+if(NOT CMAKE_ASM_COMPILER)
+  set(CMAKE_ASM_COMPILER ${CROSS}as)
 endif()
 set(CMAKE_C_FLAGS_INIT "-march=armv8-a")
 set(CMAKE_CXX_FLAGS_INIT "-march=armv8-a")
diff --git a/build/cmake/toolchains/armv7-linux-gcc.cmake b/build/cmake/toolchains/armv7-linux-gcc.cmake
index 1201538..7ce1dd8 100644
--- a/build/cmake/toolchains/armv7-linux-gcc.cmake
+++ b/build/cmake/toolchains/armv7-linux-gcc.cmake
@@ -31,8 +31,8 @@
 if(NOT CMAKE_CXX_COMPILER)
   set(CMAKE_CXX_COMPILER ${CROSS}g++)
 endif()
-if(NOT AS_EXECUTABLE)
-  set(AS_EXECUTABLE ${CROSS}as)
+if(NOT CMAKE_ASM_COMPILER)
+  set(CMAKE_ASM_COMPILER ${CROSS}as)
 endif()
 set(CMAKE_C_FLAGS_INIT "-march=armv7-a -mfpu=vfpv3 \
                           ${AOM_EXTRA_TOOLCHAIN_FLAGS}")
diff --git a/build/cmake/toolchains/ppc-linux-gcc.cmake b/build/cmake/toolchains/ppc-linux-gcc.cmake
index ab0efea..aeb2420 100644
--- a/build/cmake/toolchains/ppc-linux-gcc.cmake
+++ b/build/cmake/toolchains/ppc-linux-gcc.cmake
@@ -27,8 +27,8 @@
 if(NOT CMAKE_CXX_COMPILER)
   set(CMAKE_CXX_COMPILER ${CROSS}g++)
 endif()
-if(NOT AS_EXECUTABLE)
-  set(AS_EXECUTABLE ${CROSS}as)
+if(NOT CMAKE_ASM_COMPILER)
+  set(CMAKE_ASM_COMPILER ${CROSS}as)
 endif()
 set(CMAKE_SYSTEM_PROCESSOR "ppc")
 
diff --git a/build/cmake/toolchains/riscv-linux-gcc.cmake b/build/cmake/toolchains/riscv-linux-gcc.cmake
index 21e7370..65b7c59 100644
--- a/build/cmake/toolchains/riscv-linux-gcc.cmake
+++ b/build/cmake/toolchains/riscv-linux-gcc.cmake
@@ -27,8 +27,8 @@
 if(NOT CMAKE_CXX_COMPILER)
   set(CMAKE_CXX_COMPILER ${CROSS}g++)
 endif()
-if(NOT AS_EXECUTABLE)
-  set(AS_EXECUTABLE ${CROSS}as)
+if(NOT CMAKE_ASM_COMPILER)
+  set(CMAKE_ASM_COMPILER ${CROSS}as)
 endif()
 set(CMAKE_SYSTEM_PROCESSOR "riscv")