Enhance newlib fetch process

Update management of newlib external component to be optimized
for download speed insted of availability.
The updated process is:
  - check if binary is available. If yes configure build to use it
    and stop.
  - if not, check is source is available. If yes, build it and use
    the resulting binary.
  - if not, then download the source using git, compile it and use
    the resulting binary

The following variables can be set on the command line to alter the
behavior of the module:
  - NEWLIB_URL git repo URL to fetch from.
  - NEWLIB_REFSPEC revision to fetch
  - NEWLIB_SOURCE_DIR to specify location of source code in
    local file syetem.
  - NEWLIB_INSTALL_DIR to specify location of binary.

I.e. cmake -S <...> -B <...> -DNEWLIB_INSTALL_DIR=~/newlib
will make the resulting binary installed to ~/newlib. This can be
used later to speed up a clean build an use the prebuilt binary.

Change-Id: Idb4a39721f5739d25382d4749584981cb7e2d88f
Signed-off-by: Gyorgy Szing <Gyorgy.Szing@arm.com>
diff --git a/external/newlib/newlib.cmake b/external/newlib/newlib.cmake
index e04f08c..116c8a6 100644
--- a/external/newlib/newlib.cmake
+++ b/external/newlib/newlib.cmake
@@ -1,18 +1,11 @@
 #-------------------------------------------------------------------------------
-# Copyright (c) 2021, Arm Limited and Contributors. All rights reserved.
+# Copyright (c) 2021-2022, Arm Limited and Contributors. All rights reserved.
 #
 # SPDX-License-Identifier: BSD-3-Clause
 #
 #-------------------------------------------------------------------------------
 
-# Determine the number of processes to run while running parallel builds.
-# Pass -DPROCESSOR_COUNT=<n> to cmake to override.
-if(NOT DEFINED PROCESSOR_COUNT)
-	include(ProcessorCount)
-	ProcessorCount(PROCESSOR_COUNT)
-	set(PROCESSOR_COUNT ${PROCESSOR_COUNT} CACHE STRING "Number of cores to use for parallel builds.")
-endif()
-
+# Add newlib specific porting files to the project.
 if (NOT DEFINED TGT)
 	message(FATAL_ERROR "mandatory parameter TGT is not defined.")
 endif()
@@ -24,100 +17,167 @@
 	"${CMAKE_CURRENT_LIST_DIR}/newlib_sp_heap.c"
 )
 
-set(NEWLIB_URL "https://sourceware.org/git/newlib-cygwin.git" CACHE STRING "newlib repository URL")
-set(NEWLIB_REFSPEC "newlib-4.1.0" CACHE STRING "newlib git refspec")
-set(NEWLIB_INSTALL_PATH "${CMAKE_CURRENT_BINARY_DIR}/newlib_install" CACHE PATH "newlib installation directory")
-
-include(FetchContent)
-
-# Checking git
-find_program(GIT_COMMAND "git")
-if (NOT GIT_COMMAND)
-	message(FATAL_ERROR "Please install git")
-endif()
-
-# Fetching newlib
-FetchContent_Declare(
-	newlib
-	GIT_REPOSITORY ${NEWLIB_URL}
-	GIT_TAG ${NEWLIB_REFSPEC}
-	GIT_SHALLOW TRUE
-	PATCH_COMMAND git stash
-		COMMAND git am ${CMAKE_CURRENT_LIST_DIR}/0001-Allow-aarch64-linux-gcc-to-compile-bare-metal-lib.patch
-		COMMAND git reset HEAD~1
-)
-
-# FetchContent_GetProperties exports newlib_SOURCE_DIR and newlib_BINARY_DIR variables
-FetchContent_GetProperties(newlib)
-if(NOT newlib_POPULATED)
-	message(STATUS "Fetching newlib")
-	FetchContent_Populate(newlib)
-endif()
+# Fetch newlib from external repository
+set(NEWLIB_URL "https://sourceware.org/git/newlib-cygwin.git"
+		CACHE STRING "newlib repository URL")
+set(NEWLIB_REFSPEC "newlib-4.1.0"
+		CACHE STRING "newlib git refspec")
+set(NEWLIB_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/_deps/newlib-src"
+		CACHE PATH "newlib source-code location")
+set(NEWLIB_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/newlib_install"
+		CACHE PATH "newlib installation directory")
 
 # Extracting compiler prefix without the trailing hyphen from the C compiler name
 get_filename_component(COMPILER_PATH ${CMAKE_C_COMPILER} DIRECTORY)
 get_filename_component(COMPILER_NAME ${CMAKE_C_COMPILER} NAME)
 string(REGEX REPLACE "([^-]+-[^-]+-[^-]+).*" "\\1" COMPILER_PREFIX ${COMPILER_NAME})
 
-# Newlib configure step
-# CC env var must be unset otherwise configure will assume the cross compiler is the host compiler
-# The configure options are set to minimize code size and memory usage.
-execute_process(COMMAND
-	${CMAKE_COMMAND} -E env --unset=CC PATH=${COMPILER_PATH}:$ENV{PATH} ./configure
-		--target=${COMPILER_PREFIX}
-		--prefix=${NEWLIB_INSTALL_PATH}
-		--enable-newlib-nano-formatted-io
-		--enable-newlib-nano-malloc
-		--enable-lite-exit
-		--enable-newlib-reent-small
-		--enable-newlib-global-atexit
-		--disable-multilib
-		CFLAGS_FOR_TARGET=-fpic
-		LDFLAGS_FOR_TARGET=-fpie
-	WORKING_DIRECTORY
-		${newlib_SOURCE_DIR}
-	RESULT_VARIABLE _newlib_error
+find_library(NEWLIB_LIBC_PATH
+				NAMES libc.a c.a libc.lib c.lib
+				PATHS ${NEWLIB_INSTALL_DIR}
+				PATH_SUFFIXES "${COMPILER_PREFIX}/lib"
+				DOC "Location of newlib::libc library."
+				NO_DEFAULT_PATH
 )
+set(NEWLIB_LIBC_PATH ${NEWLIB_LIBC_PATH})
+unset(NEWLIB_LIBC_PATH CACHE)
 
-if (_newlib_error)
-	message(FATAL_ERROR "Configuration step of newlib failed with ${_newlib_error}.")
+find_library(NEWLIB_LIBNOSYS_PATH
+				NAMES libnosys.a nosys.a nosys.lib nosys.lib
+				PATHS ${NEWLIB_INSTALL_DIR}
+				PATH_SUFFIXES "${COMPILER_PREFIX}/lib"
+				DOC "Location of newlib::libnosys library."
+				NO_DEFAULT_PATH
+)
+set(NEWLIB_LIBNOSYS_PATH ${NEWLIB_LIBC_PATH})
+unset(NEWLIB_LIBNOSYS_PATH CACHE)
+
+if (NOT NEWLIB_LIBC_PATH)
+	# Determine the number of processes to run while running parallel builds.
+	# Pass -DPROCESSOR_COUNT=<n> to cmake to override.
+	if(NOT DEFINED PROCESSOR_COUNT)
+		include(ProcessorCount)
+		ProcessorCount(PROCESSOR_COUNT)
+		set(PROCESSOR_COUNT ${PROCESSOR_COUNT}
+				CACHE STRING "Number of cores to use for parallel builds.")
+	endif()
+
+	# See if the source is available locally
+	find_file(NEWLIB_HEADER_FILE
+		NAMES newlib.h
+		PATHS ${NEWLIB_SOURCE_DIR}
+		PATH_SUFFIXES "newlib/libc/include"
+		NO_DEFAULT_PATH
+	)
+	set(NEWLIB_HEADER_FILE ${NEWLIB_HEADER_FILE})
+	unset(NEWLIB_HEADER_FILE CACHE)
+
+	# Source not found, fetch it.
+	if (NOT NEWLIB_HEADER_FILE)
+		include(FetchContent)
+
+		# Checking git
+		find_program(GIT_COMMAND "git")
+		if (NOT GIT_COMMAND)
+			message(FATAL_ERROR "Please install git")
+		endif()
+
+		# Fetching newlib
+		FetchContent_Declare(
+			newlib
+			SOURCE_DIR ${NEWLIB_SOURCE_DIR}
+			GIT_REPOSITORY ${NEWLIB_URL}
+			GIT_TAG ${NEWLIB_REFSPEC}
+			GIT_SHALLOW TRUE
+			PATCH_COMMAND git stash
+				COMMAND ${GIT_COMMAND} am ${CMAKE_CURRENT_LIST_DIR}/0001-Allow-aarch64-linux-gcc-to-compile-bare-metal-lib.patch
+				COMMAND ${GIT_COMMAND} reset HEAD~1
+		)
+
+		# FetchContent_GetProperties exports newlib_SOURCE_DIR and newlib_BINARY_DIR variables
+		FetchContent_GetProperties(newlib)
+		# FetchContent_Populate will fail if the source directory is removed since it will try to
+		# do an "update" and not a "populate" action. As a workaround, remove the subbuild directory.
+		# Note: this fix assumes, the default subbuild location is used.
+		file(REMOVE_RECURSE "${CMAKE_CURRENT_BINARY_DIR}/_deps/newlib-subbuild")
+
+		if(NOT newlib_POPULATED)
+			message(STATUS "Fetching newlib")
+			FetchContent_Populate(newlib)
+		endif()
+		set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${NEWLIB_SOURCE_DIR})
+	endif()
+
+	# Newlib configure step
+	# CC env var must be unset otherwise configure will assume the cross compiler is the host
+	# compiler.
+	# The configure options are set to minimize code size and memory usage.
+	execute_process(COMMAND
+		${CMAKE_COMMAND} -E env --unset=CC PATH=${COMPILER_PATH}:$ENV{PATH} ./configure
+			--target=${COMPILER_PREFIX}
+			--prefix=${NEWLIB_INSTALL_DIR}
+			--enable-newlib-nano-formatted-io
+			--enable-newlib-nano-malloc
+			--enable-lite-exit
+			--enable-newlib-reent-small
+			--enable-newlib-global-atexit
+			--disable-multilib
+			CFLAGS_FOR_TARGET=-fpic
+			LDFLAGS_FOR_TARGET=-fpie
+		WORKING_DIRECTORY
+			${NEWLIB_SOURCE_DIR}
+		RESULT_VARIABLE _newlib_error
+	)
+
+	if (_newlib_error)
+		message(FATAL_ERROR "Configuration step of newlib failed with ${_newlib_error}.")
+	endif()
+
+	# Newlib build step
+	execute_process(COMMAND
+		${CMAKE_COMMAND} -E env --unset=CC PATH=${COMPILER_PATH}:$ENV{PATH}
+			make -j${PROCESSOR_COUNT}
+		WORKING_DIRECTORY
+			${NEWLIB_SOURCE_DIR}
+		RESULT_VARIABLE _newlib_error
+	)
+
+	if (_newlib_error)
+		message(FATAL_ERROR "Build step of newlib failed with ${_newlib_error}.")
+	endif()
+
+	# Newlib install step
+	execute_process(COMMAND
+		${CMAKE_COMMAND} -E env --unset=CC PATH=${COMPILER_PATH}:$ENV{PATH} make install
+		WORKING_DIRECTORY
+			${NEWLIB_SOURCE_DIR}
+		RESULT_VARIABLE _newlib_error
+	)
+
+	if (_newlib_error)
+		message(FATAL_ERROR "Install step of newlib failed with ${_newlib_error}.")
+	endif()
+
+	set(NEWLIB_LIBC_PATH "${NEWLIB_INSTALL_DIR}/${COMPILER_PREFIX}/lib/libc.a")
+	set(NEWLIB_LIBNOSYS_PATH "${NEWLIB_INSTALL_DIR}/${COMPILER_PREFIX}/lib/libnosys.a")
 endif()
 
-# Newlib build step
-execute_process(COMMAND
-	${CMAKE_COMMAND} -E env --unset=CC PATH=${COMPILER_PATH}:$ENV{PATH} make -j${PROCESSOR_COUNT}
-	WORKING_DIRECTORY
-		${newlib_SOURCE_DIR}
-	RESULT_VARIABLE _newlib_error
-)
-
-if (_newlib_error)
-	message(FATAL_ERROR "Build step of newlib failed with ${_newlib_error}.")
-endif()
-
-# Newlib install step
-execute_process(COMMAND
-	${CMAKE_COMMAND} -E env --unset=CC PATH=${COMPILER_PATH}:$ENV{PATH} make install
-	WORKING_DIRECTORY
-		${newlib_SOURCE_DIR}
-	RESULT_VARIABLE _newlib_error
-)
-
-if (_newlib_error)
-	message(FATAL_ERROR "Install step of newlib failed with ${_newlib_error}.")
-endif()
+set_property(DIRECTORY ${CMAKE_SOURCE_DIR}
+	APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${NEWLIB_LIBC_PATH})
 
 # libc
 add_library(c STATIC IMPORTED)
-set_property(TARGET c PROPERTY IMPORTED_LOCATION "${NEWLIB_INSTALL_PATH}/${COMPILER_PREFIX}/lib/libc.a")
-set_property(TARGET c PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${NEWLIB_INSTALL_PATH}/${COMPILER_PREFIX}/include")
+set_property(TARGET c PROPERTY IMPORTED_LOCATION "${NEWLIB_LIBC_PATH}")
+set_property(TARGET c PROPERTY
+		INTERFACE_INCLUDE_DIRECTORIES "${NEWLIB_INSTALL_DIR}/${COMPILER_PREFIX}/include")
 target_compile_options(c INTERFACE -nostdinc)
 target_link_options(c INTERFACE -nostartfiles -nodefaultlibs)
 
 # libnosys
 add_library(nosys STATIC IMPORTED)
-set_property(TARGET nosys PROPERTY IMPORTED_LOCATION "${NEWLIB_INSTALL_PATH}/${COMPILER_PREFIX}/lib/libnosys.a")
-set_property(TARGET nosys PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${NEWLIB_INSTALL_PATH}/${COMPILER_PREFIX}/include")
+set_property(TARGET nosys PROPERTY IMPORTED_LOCATION "${NEWLIB_LIBNOSYS_PATH}")
+set_property(TARGET nosys PROPERTY
+		INTERFACE_INCLUDE_DIRECTORIES "${NEWLIB_INSTALL_DIR}/${COMPILER_PREFIX}/include")
 target_compile_options(nosys INTERFACE -nostdinc)
 target_link_options(nosys INTERFACE -nostartfiles -nodefaultlibs)
 target_link_libraries(c INTERFACE nosys)