Merge pull request #1866 from jhasse/unique-edge-ids

Add unique IDs to edges
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..18ffaac
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,15 @@
+---
+Checks: '
+  ,readability-avoid-const-params-in-decls,
+  ,readability-inconsistent-declaration-parameter-name,
+  ,readability-non-const-parameter,
+  ,readability-redundant-string-cstr,
+  ,readability-redundant-string-init,
+'
+WarningsAsErrors: '
+  ,readability-avoid-const-params-in-decls,
+  ,readability-inconsistent-declaration-parameter-name,
+  ,readability-non-const-parameter,
+  ,readability-redundant-string-cstr,
+  ,readability-redundant-string-init,
+'
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 511b92b..9062d98 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -70,3 +70,56 @@
         asset_path: ./artifact/ninja-linux.zip
         asset_name: ninja-linux.zip
         asset_content_type: application/zip
+
+  test:
+    runs-on: [ubuntu-latest]
+    container:
+      image: ubuntu:20.04
+    steps:
+    - uses: actions/checkout@v2
+    - name: Install dependencies
+      run: |
+        apt update
+        apt install -y python3-pytest ninja-build clang-tidy python3-pip clang
+        pip3 install cmake==3.17.*
+    - name: Configure (GCC)
+      run: cmake -Bbuild-gcc -DCMAKE_BUILD_TYPE=Debug -G'Ninja Multi-Config'
+
+    - name: Build (GCC, Debug)
+      run: cmake --build build-gcc --config Debug
+    - name: Unit tests (GCC, Debug)
+      run: ./build-gcc/Debug/ninja_test
+    - name: Python tests (GCC, Debug)
+      run: pytest-3 --color=yes ../..
+      working-directory: build-gcc/Debug
+
+    - name: Build (GCC, Release)
+      run: cmake --build build-gcc --config Release
+    - name: Unit tests (GCC, Release)
+      run: ./build-gcc/Release/ninja_test
+    - name: Python tests (GCC, Release)
+      run: pytest-3 --color=yes ../..
+      working-directory: build-gcc/Release
+
+    - name: Configure (Clang)
+      run: CC=clang CXX=clang++ cmake -Bbuild-clang -DCMAKE_BUILD_TYPE=Debug -G'Ninja Multi-Config' -DCMAKE_EXPORT_COMPILE_COMMANDS=1
+
+    - name: Build (Clang, Debug)
+      run: cmake --build build-clang --config Debug
+    - name: Unit tests (Clang, Debug)
+      run: ./build-clang/Debug/ninja_test
+    - name: Python tests (Clang, Debug)
+      run: pytest-3 --color=yes ../..
+      working-directory: build-clang/Debug
+
+    - name: Build (Clang, Release)
+      run: cmake --build build-clang --config Release
+    - name: Unit tests (Clang, Release)
+      run: ./build-clang/Release/ninja_test
+    - name: Python tests (Clang, Release)
+      run: pytest-3 --color=yes ../..
+      working-directory: build-clang/Release
+
+    - name: clang-tidy
+      run: /usr/lib/llvm-10/share/clang/run-clang-tidy.py -header-filter=src
+      working-directory: build-clang
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index 411cfe1..4ea958f 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -8,7 +8,7 @@
 
 jobs:
   build:
-    runs-on: macOS-latest
+    runs-on: macos-11.0
 
     steps:
     - uses: actions/checkout@v2
@@ -21,8 +21,9 @@
       env:
         MACOSX_DEPLOYMENT_TARGET: 10.12
       run: |
-        cmake -DCMAKE_BUILD_TYPE=Release -B build
-        cmake --build build --parallel --config Release
+        sudo xcode-select -s /Applications/Xcode_12.2.app
+        cmake -Bbuild -GXcode '-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64'
+        cmake --build build --config Release
 
     - name: Test ninja
       run: ctest -vv
@@ -32,7 +33,7 @@
       shell: bash
       run: |
         mkdir artifact
-        7z a artifact/ninja-mac.zip ./build/ninja
+        7z a artifact/ninja-mac.zip ./build/Release/ninja
 
     # Upload ninja binary archive as an artifact
     - name: Upload artifact
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b0b2fef..7f03c35 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,20 +1,18 @@
 cmake_minimum_required(VERSION 3.15)
 
 include(CheckIncludeFileCXX)
+include(CheckIPOSupported)
 
 project(ninja)
 
 # --- optional link-time optimization
-if(CMAKE_BUILD_TYPE MATCHES "Release")
-	include(CheckIPOSupported)
-	check_ipo_supported(RESULT lto_supported OUTPUT error)
+check_ipo_supported(RESULT lto_supported OUTPUT error)
 
-	if(lto_supported)
-		message(STATUS "IPO / LTO enabled")
-		set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
-	else()
-		message(STATUS "IPO / LTO not supported: <${error}>")
-	endif()
+if(lto_supported)
+	message(STATUS "IPO / LTO enabled")
+	set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE)
+else()
+	message(STATUS "IPO / LTO not supported: <${error}>")
 endif()
 
 # --- compiler flags
@@ -112,6 +110,14 @@
 	endif()
 else()
 	target_sources(libninja PRIVATE src/subprocess-posix.cc)
+	if(CMAKE_SYSTEM_NAME STREQUAL "OS400" OR CMAKE_SYSTEM_NAME STREQUAL "AIX")
+		target_sources(libninja PRIVATE src/getopt.c)
+	endif()
+
+	# Needed for perfstat_cpu_total
+	if(CMAKE_SYSTEM_NAME STREQUAL "AIX")
+		target_link_libraries(libninja PUBLIC "-lperfstat")
+	endif()
 endif()
 
 #Fixes GetActiveProcessorCount on MinGW
@@ -119,11 +125,10 @@
 target_compile_definitions(libninja PRIVATE _WIN32_WINNT=0x0601 __USE_MINGW_ANSI_STDIO=1)
 endif()
 
-# On IBM i (identified as "OS400" for compatibility reasons), this fixes missing
-# PRId64 (and others) at compile time, and links to libutil for getopt_long
-if(CMAKE_SYSTEM_NAME STREQUAL "OS400")
+# On IBM i (identified as "OS400" for compatibility reasons) and AIX, this fixes missing
+# PRId64 (and others) at compile time in C++ sources
+if(CMAKE_SYSTEM_NAME STREQUAL "OS400" OR CMAKE_SYSTEM_NAME STREQUAL "AIX")
 	string(APPEND CMAKE_CXX_FLAGS " -D__STDC_FORMAT_MACROS")
-	string(APPEND CMAKE_EXE_LINKER_FLAGS " -lutil")
 endif()
 
 # Main executable is library plus main() function.
@@ -138,7 +143,7 @@
 		OUTPUT build/browse_py.h
 		MAIN_DEPENDENCY src/browse.py
 		DEPENDS src/inline.sh
-		COMMAND cmake -E make_directory ${CMAKE_BINARY_DIR}/build
+		COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/build
 		COMMAND src/inline.sh kBrowsePy
 						< src/browse.py
 						> ${CMAKE_BINARY_DIR}/build/browse_py.h
@@ -196,6 +201,12 @@
     target_link_libraries(${perftest} PRIVATE libninja libninja-re2c)
   endforeach()
 
+  if(CMAKE_SYSTEM_NAME STREQUAL "AIX" AND CMAKE_SIZEOF_VOID_P EQUAL 4)
+    # These tests require more memory than will fit in the standard AIX shared stack/heap (256M)
+    target_link_libraries(hash_collision_bench PRIVATE "-Wl,-bmaxdata:0x80000000")
+    target_link_libraries(manifest_parser_perftest PRIVATE "-Wl,-bmaxdata:0x80000000")
+  endif()
+
   add_test(NinjaTest ninja_test)
 endif()
 
diff --git a/configure.py b/configure.py
index 48c4821..cded265 100755
--- a/configure.py
+++ b/configure.py
@@ -596,6 +596,11 @@
 
 n.comment('Ancillary executables.')
 
+if platform.is_aix() and '-maix64' not in ldflags:
+    # Both hash_collision_bench and manifest_parser_perftest require more
+    # memory than will fit in the standard 32-bit AIX shared stack/heap (256M)
+    libs.append('-Wl,-bmaxdata:0x80000000')
+
 for name in ['build_log_perftest',
              'canon_perftest',
              'depfile_parser_perftest',
diff --git a/src/build.cc b/src/build.cc
index 3c2d0b7..2007d82 100644
--- a/src/build.cc
+++ b/src/build.cc
@@ -318,8 +318,8 @@
   want_.clear();
 }
 
-bool Plan::AddTarget(const Node* node, string* err) {
-  return AddSubTarget(node, NULL, err, NULL);
+bool Plan::AddTarget(const Node* target, string* err) {
+  return AddSubTarget(target, NULL, err, NULL);
 }
 
 bool Plan::AddSubTarget(const Node* node, const Node* dependent, string* err,
@@ -782,16 +782,16 @@
   return node;
 }
 
-bool Builder::AddTarget(Node* node, string* err) {
-  if (!scan_.RecomputeDirty(node, err))
+bool Builder::AddTarget(Node* target, string* err) {
+  if (!scan_.RecomputeDirty(target, err))
     return false;
 
-  if (Edge* in_edge = node->in_edge()) {
+  if (Edge* in_edge = target->in_edge()) {
     if (in_edge->outputs_ready())
       return true;  // Nothing to do.
   }
 
-  if (!plan_.AddTarget(node, err))
+  if (!plan_.AddTarget(target, err))
     return false;
 
   return true;
@@ -828,6 +828,10 @@
     // See if we can start any more commands.
     if (failures_allowed && command_runner_->CanRunMore()) {
       if (Edge* edge = plan_.FindWork()) {
+        if (edge->GetBindingBool("generator")) {
+          scan_.build_log()->Close();
+        }
+
         if (!StartEdge(edge, err)) {
           Cleanup();
           status_->BuildFinished();
diff --git a/src/build.h b/src/build.h
index 2fe71fc..0a68478 100644
--- a/src/build.h
+++ b/src/build.h
@@ -45,7 +45,7 @@
   /// Add a target to our plan (including all its dependencies).
   /// Returns false if we don't need to build this target; may
   /// fill in |err| with an error message if there's a problem.
-  bool AddTarget(const Node* node, std::string* err);
+  bool AddTarget(const Node* target, std::string* err);
 
   // Pop a ready edge off the queue of edges to build.
   // Returns NULL if there's no work to do.
diff --git a/src/build_log.cc b/src/build_log.cc
index 04409c8..4dcd6ce 100644
--- a/src/build_log.cc
+++ b/src/build_log.cc
@@ -183,7 +183,7 @@
 }
 
 bool BuildLog::OpenForWriteIfNeeded() {
-  if (log_file_path_.empty()) {
+  if (log_file_ || log_file_path_.empty()) {
     return true;
   }
   log_file_ = fopen(log_file_path_.c_str(), "ab");
@@ -204,7 +204,6 @@
       return false;
     }
   }
-  log_file_path_.clear();
   return true;
 }
 
diff --git a/src/hash_collision_bench.cc b/src/hash_collision_bench.cc
index 52ff56d..8f37ed0 100644
--- a/src/hash_collision_bench.cc
+++ b/src/hash_collision_bench.cc
@@ -27,9 +27,10 @@
 
 void RandomCommand(char** s) {
   int len = random(5, 100);
-  *s = new char[len];
+  *s = new char[len+1];
   for (int i = 0; i < len; ++i)
     (*s)[i] = (char)random(32, 127);
+  (*s)[len] = '\0';
 }
 
 int main() {
diff --git a/src/manifest_parser_perftest.cc b/src/manifest_parser_perftest.cc
index 92f5e13..853d8e0 100644
--- a/src/manifest_parser_perftest.cc
+++ b/src/manifest_parser_perftest.cc
@@ -25,6 +25,9 @@
 #ifdef _WIN32
 #include "getopt.h"
 #include <direct.h>
+#elif defined(_AIX)
+#include "getopt.h"
+#include <unistd.h>
 #else
 #include <getopt.h>
 #include <unistd.h>
diff --git a/src/string_piece_util_test.cc b/src/string_piece_util_test.cc
index b77ae84..61586dd 100644
--- a/src/string_piece_util_test.cc
+++ b/src/string_piece_util_test.cc
@@ -31,7 +31,7 @@
   }
 
   {
-    string empty("");
+    string empty;
     vector<StringPiece> list = SplitStringPiece(empty, ':');
 
     EXPECT_EQ(list.size(), 1);
@@ -82,7 +82,7 @@
   }
 
   {
-    string empty("");
+    string empty;
     vector<StringPiece> list = SplitStringPiece(empty, ':');
 
     EXPECT_EQ("", JoinStringPiece(list, ':'));
diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc
index 0c5f556..8e78540 100644
--- a/src/subprocess-posix.cc
+++ b/src/subprocess-posix.cc
@@ -154,6 +154,16 @@
     Fatal("waitpid(%d): %s", pid_, strerror(errno));
   pid_ = -1;
 
+#ifdef _AIX
+  if (WIFEXITED(status) && WEXITSTATUS(status) & 0x80) {
+    // Map the shell's exit code used for signal failure (128 + signal) to the
+    // status code expected by AIX WIFSIGNALED and WTERMSIG macros which, unlike
+    // other systems, uses a different bit layout.
+    int signal = WEXITSTATUS(status) & 0x7f;
+    status = (signal << 16) | signal;
+  }
+#endif
+
   if (WIFEXITED(status)) {
     int exit = WEXITSTATUS(status);
     if (exit == 0)
diff --git a/src/util.cc b/src/util.cc
index c76f730..05bdb2d 100644
--- a/src/util.cc
+++ b/src/util.cc
@@ -51,6 +51,10 @@
 #include <sys/sysinfo.h>
 #endif
 
+#if defined(__FreeBSD__)
+#include <sys/cpuset.h>
+#endif
+
 #include "edit_distance.h"
 #include "metrics.h"
 
@@ -485,10 +489,17 @@
 #ifdef _WIN32
   return GetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
 #else
-#ifdef CPU_COUNT
   // The number of exposed processors might not represent the actual number of
   // processors threads can run on. This happens when a CPU set limitation is
   // active, see https://github.com/ninja-build/ninja/issues/1278
+#if defined(__FreeBSD__)
+  cpuset_t mask;
+  CPU_ZERO(&mask);
+  if (cpuset_getaffinity(CPU_LEVEL_WHICH, CPU_WHICH_TID, -1, sizeof(mask),
+    &mask) == 0) {
+    return CPU_COUNT(&mask);
+  }
+#elif defined(CPU_COUNT)
   cpu_set_t set;
   if (sched_getaffinity(getpid(), sizeof(set), &set) == 0) {
     return CPU_COUNT(&set);
diff --git a/src/version.cc b/src/version.cc
index f25a30a..97afa7e 100644
--- a/src/version.cc
+++ b/src/version.cc
@@ -20,7 +20,7 @@
 
 using namespace std;
 
-const char* kNinjaVersion = "1.10.1.git";
+const char* kNinjaVersion = "1.10.2.git";
 
 void ParseVersion(const string& version, int* major, int* minor) {
   size_t end = version.find('.');