Update profman to support artd use cases.

1. Return 100 instead of 1 for usage error. 1 is already used to
   represent kCompile.
2. Move profman return codes to a separate header file so that artd can
   reference them.
3. Allow running profman with `--reference-profile-file(-fd)` but no
   `--profile-file(-fd)`. In this case, profman returns either
   `kSkipCompilationSmallDelta`, `kSkipCompilationEmptyProfiles`, or
   errors. It is essentially a profile checker that checks whether the
   reference profile is usable or not.
4. Add a new return code kCopyAndUpdateNoUpdate for the case where
   profman is run with `--copy-and-update-profile-key` and the copy
   succeeds but nothing has been updated.
5. Use positive values for `--copy-and-update-profile-key` errors
   instead of negative values because the exit code range is 0~255.

- Change #1 is safe because there is no one checking the exit code
  against 1 for usage error. (If there were, the check would be wrong
  anyway.)
- Change #4 and #5 are safe because only installd calls profman with
  `--copy-and-update-profile-key`, and installd doesn't check the exit
  code.

Bug: 229268202
Test: m test-art-host-gtest-art_profman_tests
Change-Id: I8e82c14955519244852166bc7753df4a2bd102ef
Merged-In: I8e82c14955519244852166bc7753df4a2bd102ef
(cherry picked from commit e3d589c26d6f97c4031986132afd4eb1ef00fe4c)
diff --git a/libprofile/profile/profile_compilation_info.cc b/libprofile/profile/profile_compilation_info.cc
index f135805..83db0a0 100644
--- a/libprofile/profile/profile_compilation_info.cc
+++ b/libprofile/profile/profile_compilation_info.cc
@@ -2387,7 +2387,8 @@
 }
 
 bool ProfileCompilationInfo::UpdateProfileKeys(
-      const std::vector<std::unique_ptr<const DexFile>>& dex_files) {
+    const std::vector<std::unique_ptr<const DexFile>>& dex_files, /*out*/ bool* updated) {
+  *updated = false;
   for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
     for (const std::unique_ptr<DexFileData>& dex_data : info_) {
       if (dex_data->checksum == dex_file->GetLocationChecksum() &&
@@ -2407,6 +2408,7 @@
           // form the old key.
           dex_data->profile_key = MigrateAnnotationInfo(new_profile_key, dex_data->profile_key);
           profile_key_map_.Put(dex_data->profile_key, dex_data->profile_index);
+          *updated = true;
         }
       }
     }
diff --git a/libprofile/profile/profile_compilation_info.h b/libprofile/profile/profile_compilation_info.h
index 4366078..76cbf9a 100644
--- a/libprofile/profile/profile_compilation_info.h
+++ b/libprofile/profile/profile_compilation_info.h
@@ -645,7 +645,10 @@
   //
   // If the new profile key would collide with an existing key (for a different dex)
   // the method returns false. Otherwise it returns true.
-  bool UpdateProfileKeys(const std::vector<std::unique_ptr<const DexFile>>& dex_files);
+  //
+  // `updated` is set to true if any profile key has been updated by this method.
+  bool UpdateProfileKeys(const std::vector<std::unique_ptr<const DexFile>>& dex_files,
+                         /*out*/ bool* updated);
 
   // Checks if the profile is empty.
   bool IsEmpty() const;
diff --git a/libprofile/profile/profile_compilation_info_test.cc b/libprofile/profile/profile_compilation_info_test.cc
index 39e8b4f..2ee34f2 100644
--- a/libprofile/profile/profile_compilation_info_test.cc
+++ b/libprofile/profile/profile_compilation_info_test.cc
@@ -956,7 +956,9 @@
   AddMethod(&info, dex2, /*method_idx=*/ 0);
 
   // Update the profile keys based on the original dex files
-  ASSERT_TRUE(info.UpdateProfileKeys(dex_files));
+  bool updated = false;
+  ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &updated));
+  ASSERT_TRUE(updated);
 
   // Verify that we find the methods when searched with the original dex files.
   for (const std::unique_ptr<const DexFile>& dex : dex_files) {
@@ -982,7 +984,9 @@
   AddMethod(&info, dex2, /*method_idx=*/ 0, Hotness::kFlagHot, annotation);
 
   // Update the profile keys based on the original dex files
-  ASSERT_TRUE(info.UpdateProfileKeys(dex_files));
+  bool updated = false;
+  ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &updated));
+  ASSERT_TRUE(updated);
 
   // Verify that we find the methods when searched with the original dex files.
   for (const std::unique_ptr<const DexFile>& dex : dex_files) {
@@ -1005,7 +1009,9 @@
   AddMethod(&info, dex2, /*method_idx=*/ 0);
 
   // Update the profile keys based on the original dex files.
-  ASSERT_TRUE(info.UpdateProfileKeys(dex_files));
+  bool updated = false;
+  ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &updated));
+  ASSERT_FALSE(updated);
 
   // Verify that we did not perform any update and that we cannot find anything with the new
   // location.
@@ -1037,7 +1043,9 @@
   // This will cause the rename to fail because an existing entry would already have that name.
   AddMethod(&info, dex1_renamed, /*method_idx=*/ 0);
 
-  ASSERT_FALSE(info.UpdateProfileKeys(dex_files));
+  bool updated = false;
+  ASSERT_FALSE(info.UpdateProfileKeys(dex_files, &updated));
+  ASSERT_FALSE(updated);
 
   // Release the ownership as this is held by the test class;
   for (std::unique_ptr<const DexFile>& dex : dex_files) {
diff --git a/profman/Android.bp b/profman/Android.bp
index b231499..8e76bc2 100644
--- a/profman/Android.bp
+++ b/profman/Android.bp
@@ -32,6 +32,7 @@
         "profman.cc",
         "profile_assistant.cc",
     ],
+    header_libs: ["profman_headers"],
 
     target: {
         android: {
@@ -160,11 +161,31 @@
     },
 }
 
+cc_library_headers {
+    name: "profman_headers",
+    defaults: ["art_defaults"],
+    export_include_dirs: ["include"],
+    host_supported: true,
+    target: {
+        darwin: {
+            enabled: false,
+        },
+        windows: {
+            enabled: true,
+        },
+    },
+    apex_available: [
+        "com.android.art",
+        "com.android.art.debug",
+    ],
+}
+
 art_cc_defaults {
     name: "art_profman_tests_defaults",
     data: [
         ":art-gtest-jars-ProfileTestMultiDex",
     ],
+    header_libs: ["profman_headers"],
     tidy_timeout_srcs: ["profile_assistant_test.cc"],
     srcs: ["profile_assistant_test.cc"],
 }
diff --git a/profman/include/profman/profman_result.h b/profman/include/profman/profman_result.h
new file mode 100644
index 0000000..2e962e8
--- /dev/null
+++ b/profman/include/profman/profman_result.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_PROFMAN_INCLUDE_PROFMAN_PROFMAN_RESULT_H_
+#define ART_PROFMAN_INCLUDE_PROFMAN_PROFMAN_RESULT_H_
+
+namespace art {
+
+class ProfmanResult {
+ public:
+  static constexpr int kErrorUsage = 100;
+
+  // The return codes of processing profiles (running profman in normal mode).
+  //
+  // Note that installd consumes the returns codes with its own copy of these values
+  // (frameworks/native/cmds/installd/dexopt.cpp).
+  enum ProcessingResult {
+    kSuccess = 0,  // Generic success code for non-analysis runs.
+    kCompile = 1,
+    kSkipCompilationSmallDelta = 2,
+    kErrorBadProfiles = 3,
+    kErrorIO = 4,
+    kErrorCannotLock = 5,
+    kErrorDifferentVersions = 6,
+    kSkipCompilationEmptyProfiles = 7,
+  };
+
+  // The return codes of running profman with `--copy-and-update-profile-key`.
+  enum CopyAndUpdateResult {
+    kCopyAndUpdateSuccess = 0,
+    kCopyAndUpdateNoUpdate = 21,
+    kCopyAndUpdateErrorFailedToUpdateProfile = 22,
+    kCopyAndUpdateErrorFailedToSaveProfile = 23,
+    kCopyAndUpdateErrorFailedToLoadProfile = 24,
+  };
+};
+
+}  // namespace art
+
+#endif  // ART_PROFMAN_INCLUDE_PROFMAN_PROFMAN_RESULT_H_
diff --git a/profman/profile_assistant.cc b/profman/profile_assistant.cc
index d098738..abbde2d 100644
--- a/profman/profile_assistant.cc
+++ b/profman/profile_assistant.cc
@@ -18,6 +18,7 @@
 
 #include "base/os.h"
 #include "base/unix_file/fd_file.h"
+#include "profman/profman_result.h"
 
 namespace art {
 
@@ -26,25 +27,22 @@
 static constexpr const uint32_t kMinNewMethodsForCompilation = 100;
 static constexpr const uint32_t kMinNewClassesForCompilation = 50;
 
-
-ProfileAssistant::ProcessingResult ProfileAssistant::ProcessProfilesInternal(
-        const std::vector<ScopedFlock>& profile_files,
-        const ScopedFlock& reference_profile_file,
-        const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn,
-        const Options& options) {
-  DCHECK(!profile_files.empty());
-
+ProfmanResult::ProcessingResult ProfileAssistant::ProcessProfilesInternal(
+    const std::vector<ScopedFlock>& profile_files,
+    const ScopedFlock& reference_profile_file,
+    const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn,
+    const Options& options) {
   ProfileCompilationInfo info(options.IsBootImageMerge());
 
   // Load the reference profile.
   if (!info.Load(reference_profile_file->Fd(), /*merge_classes=*/ true, filter_fn)) {
     LOG(WARNING) << "Could not load reference profile file";
-    return kErrorBadProfiles;
+    return ProfmanResult::kErrorBadProfiles;
   }
 
   if (options.IsBootImageMerge() && !info.IsForBootImage()) {
     LOG(WARNING) << "Requested merge for boot image profile but the reference profile is regular.";
-    return kErrorBadProfiles;
+    return ProfmanResult::kErrorBadProfiles;
   }
 
   // Store the current state of the reference profile before merging with the current profiles.
@@ -65,21 +63,21 @@
       // TODO: Do we really need to use a different error code for version mismatch?
       ProfileCompilationInfo wrong_info(!options.IsBootImageMerge());
       if (wrong_info.Load(profile_files[i]->Fd(), /*merge_classes=*/ true, filter_fn)) {
-        return kErrorDifferentVersions;
+        return ProfmanResult::kErrorDifferentVersions;
       }
-      return kErrorBadProfiles;
+      return ProfmanResult::kErrorBadProfiles;
     }
 
     if (!info.MergeWith(cur_info)) {
       LOG(WARNING) << "Could not merge profile file at index " << i;
-      return kErrorBadProfiles;
+      return ProfmanResult::kErrorBadProfiles;
     }
   }
 
   // If we perform a forced merge do not analyze the difference between profiles.
   if (!options.IsForceMerge()) {
     if (info.IsEmpty()) {
-      return kSkipCompilationEmptyProfiles;
+      return ProfmanResult::kSkipCompilationEmptyProfiles;
     }
     uint32_t min_change_in_methods_for_compilation = std::max(
         (options.GetMinNewMethodsPercentChangeForCompilation() * number_of_methods) / 100,
@@ -91,21 +89,21 @@
     if (((info.GetNumberOfMethods() - number_of_methods) < min_change_in_methods_for_compilation) &&
         ((info.GetNumberOfResolvedClasses() - number_of_classes)
             < min_change_in_classes_for_compilation)) {
-      return kSkipCompilationSmallDelta;
+      return ProfmanResult::kSkipCompilationSmallDelta;
     }
   }
 
   // We were successful in merging all profile information. Update the reference profile.
   if (!reference_profile_file->ClearContent()) {
     PLOG(WARNING) << "Could not clear reference profile file";
-    return kErrorIO;
+    return ProfmanResult::kErrorIO;
   }
   if (!info.Save(reference_profile_file->Fd())) {
     LOG(WARNING) << "Could not save reference profile file";
-    return kErrorIO;
+    return ProfmanResult::kErrorIO;
   }
 
-  return options.IsForceMerge() ? kSuccess : kCompile;
+  return options.IsForceMerge() ? ProfmanResult::kSuccess : ProfmanResult::kCompile;
 }
 
 class ScopedFlockList {
@@ -144,7 +142,7 @@
   std::vector<ScopedFlock> flocks_;
 };
 
-ProfileAssistant::ProcessingResult ProfileAssistant::ProcessProfiles(
+ProfmanResult::ProcessingResult ProfileAssistant::ProcessProfiles(
         const std::vector<int>& profile_files_fd,
         int reference_profile_file_fd,
         const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn,
@@ -155,7 +153,7 @@
   ScopedFlockList profile_files(profile_files_fd.size());
   if (!profile_files.Init(profile_files_fd, &error)) {
     LOG(WARNING) << "Could not lock profile files: " << error;
-    return kErrorCannotLock;
+    return ProfmanResult::kErrorCannotLock;
   }
 
   // The reference_profile_file is opened in read/write mode because it's
@@ -166,7 +164,7 @@
                                                          &error);
   if (reference_profile_file.get() == nullptr) {
     LOG(WARNING) << "Could not lock reference profiled files: " << error;
-    return kErrorCannotLock;
+    return ProfmanResult::kErrorCannotLock;
   }
 
   return ProcessProfilesInternal(profile_files.Get(),
@@ -175,7 +173,7 @@
                                  options);
 }
 
-ProfileAssistant::ProcessingResult ProfileAssistant::ProcessProfiles(
+ProfmanResult::ProcessingResult ProfileAssistant::ProcessProfiles(
         const std::vector<std::string>& profile_files,
         const std::string& reference_profile_file,
         const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn,
@@ -185,14 +183,14 @@
   ScopedFlockList profile_files_list(profile_files.size());
   if (!profile_files_list.Init(profile_files, &error)) {
     LOG(WARNING) << "Could not lock profile files: " << error;
-    return kErrorCannotLock;
+    return ProfmanResult::kErrorCannotLock;
   }
 
   ScopedFlock locked_reference_profile_file = LockedFile::Open(
       reference_profile_file.c_str(), O_RDWR, /* block= */ true, &error);
   if (locked_reference_profile_file.get() == nullptr) {
     LOG(WARNING) << "Could not lock reference profile files: " << error;
-    return kErrorCannotLock;
+    return ProfmanResult::kErrorCannotLock;
   }
 
   return ProcessProfilesInternal(profile_files_list.Get(),
diff --git a/profman/profile_assistant.h b/profman/profile_assistant.h
index 0ef4f88..6b7a7a6 100644
--- a/profman/profile_assistant.h
+++ b/profman/profile_assistant.h
@@ -22,24 +22,12 @@
 
 #include "base/scoped_flock.h"
 #include "profile/profile_compilation_info.h"
+#include "profman/profman_result.h"
 
 namespace art {
 
 class ProfileAssistant {
  public:
-  // These also serve as return codes of profman and are processed by installd
-  // (frameworks/native/cmds/installd/commands.cpp)
-  enum ProcessingResult {
-    kSuccess = 0,  // Generic success code for non-analysis runs.
-    kCompile = 1,
-    kSkipCompilationSmallDelta = 2,
-    kErrorBadProfiles = 3,
-    kErrorIO = 4,
-    kErrorCannotLock = 5,
-    kErrorDifferentVersions = 6,
-    kSkipCompilationEmptyProfiles = 7,
-  };
-
   class Options {
    public:
     static constexpr bool kForceMergeDefault = false;
@@ -101,14 +89,14 @@
   // this case no file will be updated. A variation of this code is
   // kSkipCompilationEmptyProfiles which indicates that all the profiles are empty.
   // This allow the caller to make fine grain decisions on the compilation strategy.
-  static ProcessingResult ProcessProfiles(
+  static ProfmanResult::ProcessingResult ProcessProfiles(
       const std::vector<std::string>& profile_files,
       const std::string& reference_profile_file,
       const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn
           = ProfileCompilationInfo::ProfileFilterFnAcceptAll,
       const Options& options = Options());
 
-  static ProcessingResult ProcessProfiles(
+  static ProfmanResult::ProcessingResult ProcessProfiles(
       const std::vector<int>& profile_files_fd_,
       int reference_profile_file_fd,
       const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn
@@ -116,7 +104,7 @@
       const Options& options = Options());
 
  private:
-  static ProcessingResult ProcessProfilesInternal(
+  static ProfmanResult::ProcessingResult ProcessProfilesInternal(
       const std::vector<ScopedFlock>& profile_files,
       const ScopedFlock& reference_profile_file,
       const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn,
diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc
index d337419..01b385a 100644
--- a/profman/profile_assistant_test.cc
+++ b/profman/profile_assistant_test.cc
@@ -14,7 +14,8 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
+#include "profile_assistant.h"
+
 #include <sstream>
 #include <string>
 
@@ -31,12 +32,13 @@
 #include "dex/dex_instruction_iterator.h"
 #include "dex/type_reference.h"
 #include "exec_utils.h"
+#include "gtest/gtest.h"
 #include "linear_alloc.h"
 #include "mirror/class-inl.h"
 #include "obj_ptr-inl.h"
 #include "profile/profile_compilation_info.h"
 #include "profile/profile_test_helper.h"
-#include "profile_assistant.h"
+#include "profman/profman_result.h"
 #include "scoped_thread_state_change-inl.h"
 
 namespace art {
@@ -492,8 +494,7 @@
   SetupProfile(dex3, dex4, kNumberOfMethodsToEnableCompilation, 0, profile2, &info2);
 
   // We should advise compilation.
-  ASSERT_EQ(ProfileAssistant::kCompile,
-            ProcessProfiles(profile_fds, reference_profile_fd));
+  ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd));
   // The resulting compilation info must be equal to the merge of the inputs.
   ProfileCompilationInfo result;
   ASSERT_TRUE(result.Load(reference_profile_fd));
@@ -533,8 +534,7 @@
   SetupProfile(dex1_100, dex2_100, 0, kNumberOfClassesToEnableCompilation, profile1, &info1);
 
   // We should advise compilation.
-  ASSERT_EQ(ProfileAssistant::kCompile,
-            ProcessProfiles(profile_fds, reference_profile_fd));
+  ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd));
   // The resulting compilation info must be equal to the merge of the inputs.
   ProfileCompilationInfo result;
   ASSERT_TRUE(result.Load(reference_profile_fd));
@@ -572,8 +572,7 @@
       &reference_info, kNumberOfMethodsToEnableCompilation / 2);
 
   // We should advise compilation.
-  ASSERT_EQ(ProfileAssistant::kCompile,
-            ProcessProfiles(profile_fds, reference_profile_fd));
+  ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd));
 
   // The resulting compilation info must be equal to the merge of the inputs
   ProfileCompilationInfo result;
@@ -606,7 +605,7 @@
   SetupProfile(dex3, dex4, /*number_of_methods=*/ 0, /*number_of_classes*/ 0, profile2, &info2);
 
   // We should not advise compilation.
-  ASSERT_EQ(ProfileAssistant::kSkipCompilationEmptyProfiles,
+  ASSERT_EQ(ProfmanResult::kSkipCompilationEmptyProfiles,
             ProcessProfiles(profile_fds, reference_profile_fd));
 
   // The information from profiles must remain the same.
@@ -643,7 +642,7 @@
   SetupProfile(dex3, dex4, kNumberOfMethodsToSkipCompilation, 0, profile2, &info2);
 
   // We should not advise compilation.
-  ASSERT_EQ(ProfileAssistant::kSkipCompilationSmallDelta,
+  ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
             ProcessProfiles(profile_fds, reference_profile_fd));
 
   // The information from profiles must remain the same.
@@ -669,10 +668,9 @@
   std::vector<const std::string> extra_args({"--min-new-methods-percent-change=2"});
 
   // We should not advise compilation.
-  ASSERT_EQ(ProfileAssistant::kSkipCompilationSmallDelta,
-            CheckCompilationMethodPercentChange(kNumberOfMethodsInCurProfile,
-                                                kNumberOfMethodsInRefProfile,
-                                                extra_args));
+  ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
+            CheckCompilationMethodPercentChange(
+                kNumberOfMethodsInCurProfile, kNumberOfMethodsInRefProfile, extra_args));
 }
 
 TEST_F(ProfileAssistantTest, ShouldAdviseCompilationMethodPercentage) {
@@ -681,10 +679,9 @@
   std::vector<const std::string> extra_args({"--min-new-methods-percent-change=2"});
 
   // We should advise compilation.
-  ASSERT_EQ(ProfileAssistant::kCompile,
-            CheckCompilationMethodPercentChange(kNumberOfMethodsInCurProfile,
-                                                kNumberOfMethodsInRefProfile,
-                                                extra_args));
+  ASSERT_EQ(ProfmanResult::kCompile,
+            CheckCompilationMethodPercentChange(
+                kNumberOfMethodsInCurProfile, kNumberOfMethodsInRefProfile, extra_args));
 }
 
 TEST_F(ProfileAssistantTest, DoNotAdviseCompilationMethodPercentageWithNewMin) {
@@ -692,7 +689,7 @@
   const uint16_t kNumberOfMethodsInCurProfile = 6200;  // Threshold is 20%.
 
   // We should not advise compilation.
-  ASSERT_EQ(ProfileAssistant::kSkipCompilationSmallDelta,
+  ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
             CheckCompilationMethodPercentChange(kNumberOfMethodsInCurProfile,
                                                 kNumberOfMethodsInRefProfile));
 }
@@ -703,10 +700,9 @@
   std::vector<const std::string> extra_args({"--min-new-classes-percent-change=2"});
 
   // We should not advise compilation.
-  ASSERT_EQ(ProfileAssistant::kSkipCompilationSmallDelta,
-            CheckCompilationClassPercentChange(kNumberOfClassesInCurProfile,
-                                               kNumberOfClassesInRefProfile,
-                                               extra_args));
+  ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
+            CheckCompilationClassPercentChange(
+                kNumberOfClassesInCurProfile, kNumberOfClassesInRefProfile, extra_args));
 }
 
 TEST_F(ProfileAssistantTest, ShouldAdviseCompilationClassPercentage) {
@@ -715,10 +711,9 @@
   std::vector<const std::string> extra_args({"--min-new-classes-percent-change=2"});
 
   // We should advise compilation.
-  ASSERT_EQ(ProfileAssistant::kCompile,
-            CheckCompilationClassPercentChange(kNumberOfClassesInCurProfile,
-                                               kNumberOfClassesInRefProfile,
-                                               extra_args));
+  ASSERT_EQ(ProfmanResult::kCompile,
+            CheckCompilationClassPercentChange(
+                kNumberOfClassesInCurProfile, kNumberOfClassesInRefProfile, extra_args));
 }
 
 TEST_F(ProfileAssistantTest, DoNotAdviseCompilationClassPercentageWithNewMin) {
@@ -726,7 +721,7 @@
   const uint16_t kNumberOfClassesInCurProfile = 6200;  // Threshold is 20%.
 
   // We should not advise compilation.
-  ASSERT_EQ(ProfileAssistant::kSkipCompilationSmallDelta,
+  ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
             CheckCompilationClassPercentChange(kNumberOfClassesInCurProfile,
                                                kNumberOfClassesInRefProfile));
 }
@@ -750,8 +745,7 @@
       dex1_checksum_missmatch, dex2, kNumberOfMethodsToEnableCompilation, 0, profile2, &info2);
 
   // We should fail processing.
-  ASSERT_EQ(ProfileAssistant::kErrorBadProfiles,
-            ProcessProfiles(profile_fds, reference_profile_fd));
+  ASSERT_EQ(ProfmanResult::kErrorBadProfiles, ProcessProfiles(profile_fds, reference_profile_fd));
 
   // The information from profiles must remain the same.
   CheckProfileInfo(profile1, info1);
@@ -782,8 +776,7 @@
                &reference_info);
 
   // We should not advise compilation.
-  ASSERT_EQ(ProfileAssistant::kErrorBadProfiles,
-            ProcessProfiles(profile_fds, reference_profile_fd));
+  ASSERT_EQ(ProfmanResult::kErrorBadProfiles, ProcessProfiles(profile_fds, reference_profile_fd));
 
   // The information from profiles must remain the same.
   CheckProfileInfo(profile1, info1);
@@ -1531,8 +1524,7 @@
       &reference_info, kNumberOfMethodsToEnableCompilation / 2, /*reverse_dex_write_order=*/true);
 
   // We should advise compilation.
-  ASSERT_EQ(ProfileAssistant::kCompile,
-            ProcessProfiles(profile_fds, reference_profile_fd));
+  ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd));
 
   // The resulting compilation info must be equal to the merge of the inputs.
   ProfileCompilationInfo result;
@@ -1793,7 +1785,7 @@
   argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
   std::string error;
 
-  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfileAssistant::kCompile) << error;
+  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kCompile) << error;
 
   // Verify that we can load the result.
 
@@ -1826,6 +1818,165 @@
   ASSERT_TRUE(expected.Equals(result));
 }
 
+TEST_F(ProfileAssistantTest, MergeProfilesNoProfile) {
+  ScratchFile reference_profile;
+
+  // Use a real dex file to generate profile test data.
+  std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("ProfileTestMultiDex");
+  const DexFile& d1 = *dex_files[0];
+  const DexFile& d2 = *dex_files[0];
+
+  // The reference profile info will contain the methods with indices 0-100.
+  ProfileCompilationInfo reference_info;
+  SetupProfile(&d1,
+               &d2,
+               /*number_of_methods=*/ 100,
+               /*number_of_classes=*/ 0,
+               reference_profile,
+               &reference_info);
+
+  std::string content_before;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_before));
+
+  // Run profman and pass the dex file with --apk-fd.
+  android::base::unique_fd apk_fd(
+      open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
+  ASSERT_GE(apk_fd.get(), 0);
+
+  std::string profman_cmd = GetProfmanCmd();
+  std::vector<std::string> argv_str;
+  argv_str.push_back(profman_cmd);
+  argv_str.push_back("--reference-profile-file-fd=" + std::to_string(reference_profile.GetFd()));
+  argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
+
+  // Must return kSkipCompilationSmallDelta.
+  std::string error;
+  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kSkipCompilationSmallDelta)
+      << error;
+
+  // Verify that the content has not changed.
+  std::string content_after;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
+  EXPECT_EQ(content_before, content_after);
+}
+
+TEST_F(ProfileAssistantTest, MergeProfilesNoProfilePassByFilename) {
+  ScratchFile reference_profile;
+
+  // Use a real dex file to generate profile test data.
+  std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("ProfileTestMultiDex");
+  const DexFile& d1 = *dex_files[0];
+  const DexFile& d2 = *dex_files[0];
+
+  // The reference profile info will contain the methods with indices 0-100.
+  ProfileCompilationInfo reference_info;
+  SetupProfile(&d1,
+               &d2,
+               /*number_of_methods=*/100,
+               /*number_of_classes=*/0,
+               reference_profile,
+               &reference_info);
+
+  std::string content_before;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_before));
+
+  // Run profman and pass the dex file with --apk-fd.
+  android::base::unique_fd apk_fd(
+      open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
+  ASSERT_GE(apk_fd.get(), 0);
+
+  std::string profman_cmd = GetProfmanCmd();
+  std::vector<std::string> argv_str;
+  argv_str.push_back(profman_cmd);
+  argv_str.push_back("--reference-profile-file=" + reference_profile.GetFilename());
+  argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
+
+  // Must return kSkipCompilationSmallDelta.
+  std::string error;
+  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kSkipCompilationSmallDelta)
+      << error;
+
+  // Verify that the content has not changed.
+  std::string content_after;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
+  EXPECT_EQ(content_before, content_after);
+}
+
+TEST_F(ProfileAssistantTest, MergeProfilesNoProfileEmptyReferenceProfile) {
+  ScratchFile reference_profile;
+
+  // The reference profile info will only contain the header.
+  ProfileCompilationInfo reference_info;
+  SetupProfile(/*dex_file1=*/ nullptr,
+               /*dex_file2=*/ nullptr,
+               /*number_of_methods=*/ 0,
+               /*number_of_classes=*/ 0,
+               reference_profile,
+               &reference_info);
+
+  std::string content_before;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_before));
+
+  // Run profman and pass the dex file with --apk-fd.
+  android::base::unique_fd apk_fd(
+      open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
+  ASSERT_GE(apk_fd.get(), 0);
+
+  std::string profman_cmd = GetProfmanCmd();
+  std::vector<std::string> argv_str;
+  argv_str.push_back(profman_cmd);
+  argv_str.push_back("--reference-profile-file-fd=" + std::to_string(reference_profile.GetFd()));
+  argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
+
+  // Must return kSkipCompilationEmptyProfiles.
+  std::string error;
+  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kSkipCompilationEmptyProfiles)
+      << error;
+
+  // Verify that the content has not changed.
+  std::string content_after;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
+  EXPECT_EQ(content_before, content_after);
+}
+
+TEST_F(ProfileAssistantTest, MergeProfilesNoProfileEmptyReferenceProfileAfterFiltering) {
+  ScratchFile reference_profile;
+
+  // Use fake dex files to generate profile test data.
+  // All the methods will be filtered out during the profman invocation.
+  ProfileCompilationInfo reference_info;
+  SetupProfile(dex1,
+               dex2,
+               /*number_of_methods=*/ 100,
+               /*number_of_classes=*/ 0,
+               reference_profile,
+               &reference_info);
+
+  std::string content_before;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_before));
+
+  // Run profman and pass the real dex file with --apk-fd.
+  android::base::unique_fd apk_fd(
+      open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
+  ASSERT_GE(apk_fd.get(), 0);
+
+  std::string profman_cmd = GetProfmanCmd();
+  std::vector<std::string> argv_str;
+  argv_str.push_back(profman_cmd);
+  argv_str.push_back("--reference-profile-file-fd=" + std::to_string(reference_profile.GetFd()));
+  argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
+
+  // Must return kSkipCompilationEmptyProfiles.
+  std::string error;
+  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kSkipCompilationEmptyProfiles)
+      << error;
+
+  // Verify that the content has not changed.
+  std::string content_after;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
+  EXPECT_EQ(content_before, content_after);
+}
+
 TEST_F(ProfileAssistantTest, CopyAndUpdateProfileKey) {
   ScratchFile profile1;
   ScratchFile reference_profile;
@@ -1864,7 +2015,8 @@
   argv_str.push_back("--copy-and-update-profile-key");
   std::string error;
 
-  ASSERT_EQ(ExecAndReturnCode(argv_str, &error), 0) << error;
+  // Must return kCopyAndUpdateSuccess.
+  ASSERT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kCopyAndUpdateSuccess) << error;
 
   // Verify that we can load the result.
   ProfileCompilationInfo result;
@@ -1880,6 +2032,45 @@
   }
 }
 
+TEST_F(ProfileAssistantTest, CopyAndUpdateProfileKeyNoUpdate) {
+  ScratchFile profile1;
+  ScratchFile reference_profile;
+
+  // Use fake dex files to generate profile test data.
+  ProfileCompilationInfo info1;
+  SetupProfile(dex1,
+               dex2,
+               /*number_of_methods=*/ 100,
+               /*number_of_classes=*/ 0,
+               profile1,
+               &info1);
+
+  std::string input_content;
+  ASSERT_TRUE(android::base::ReadFileToString(profile1.GetFilename(), &input_content));
+
+  // Run profman and pass the real dex file with --apk-fd. It won't match any entry in the profile.
+  android::base::unique_fd apk_fd(
+      open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
+  ASSERT_GE(apk_fd.get(), 0);
+
+  std::string profman_cmd = GetProfmanCmd();
+  std::vector<std::string> argv_str;
+  argv_str.push_back(profman_cmd);
+  argv_str.push_back("--profile-file-fd=" + std::to_string(profile1.GetFd()));
+  argv_str.push_back("--reference-profile-file-fd=" + std::to_string(reference_profile.GetFd()));
+  argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
+  argv_str.push_back("--copy-and-update-profile-key");
+  std::string error;
+
+  // Must return kCopyAndUpdateNoUpdate.
+  ASSERT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kCopyAndUpdateNoUpdate) << error;
+
+  // Verify that the content is the same.
+  std::string output_content;
+  ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &output_content));
+  EXPECT_EQ(input_content, output_content);
+}
+
 TEST_F(ProfileAssistantTest, BootImageMerge) {
   ScratchFile profile;
   ScratchFile reference_profile;
@@ -1906,7 +2097,7 @@
 
   int return_code = ProcessProfiles(profile_fds, reference_profile_fd, extra_args);
 
-  ASSERT_EQ(return_code, ProfileAssistant::kSuccess);
+  ASSERT_EQ(return_code, ProfmanResult::kSuccess);
 
   // Verify the result: it should be equal to info2 since info1 is a regular profile
   // and should be ignored.
@@ -1948,7 +2139,7 @@
   std::vector<const std::string> extra_args({"--force-merge"});
   int return_code = ProcessProfiles(profile_fds, reference_profile_fd, extra_args);
 
-  ASSERT_EQ(return_code, ProfileAssistant::kSuccess);
+  ASSERT_EQ(return_code, ProfmanResult::kSuccess);
 
   // Check that the result is the aggregation.
   ProfileCompilationInfo result;
@@ -1993,7 +2184,7 @@
   argv_str.push_back("--boot-image-merge");
   std::string error;
 
-  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfileAssistant::kSuccess) << error;
+  EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kSuccess) << error;
 
   // Verify that we can load the result and that it equals to what we saved.
   ProfileCompilationInfo result(/*for_boot_image=*/ true);
@@ -2015,17 +2206,16 @@
   int reference_profile_fd = GetFd(profile2);
   std::vector<const std::string> boot_image_args({"--boot-image-merge"});
   ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd, boot_image_args),
-            ProfileAssistant::kErrorDifferentVersions);
-  ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd),
-            ProfileAssistant::kErrorBadProfiles);
+            ProfmanResult::kErrorDifferentVersions);
+  ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd), ProfmanResult::kErrorBadProfiles);
 
   // Reverse the order of the profiles to verify we get the same behaviour.
   profile_fds[0] = GetFd(profile2);
   reference_profile_fd = GetFd(profile1);
   ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd, boot_image_args),
-            ProfileAssistant::kErrorBadProfiles);
+            ProfmanResult::kErrorBadProfiles);
   ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd),
-            ProfileAssistant::kErrorDifferentVersions);
+            ProfmanResult::kErrorDifferentVersions);
 }
 
 // Under default behaviour we will abort if we cannot load a profile during a merge
@@ -2048,7 +2238,7 @@
   // With force-merge we should merge successfully.
   std::vector<const std::string> extra_args({"--force-merge", "--boot-image-merge"});
   ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd, extra_args),
-            ProfileAssistant::kSuccess);
+            ProfmanResult::kSuccess);
 
   ProfileCompilationInfo result(/*for_boot_image=*/ true);
   ASSERT_TRUE(result.Load(reference_profile_fd));
@@ -2057,7 +2247,7 @@
   // Without force-merge we should fail.
   std::vector<const std::string> extra_args2({"--boot-image-merge"});
   ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd, extra_args2),
-            ProfileAssistant::kErrorBadProfiles);
+            ProfmanResult::kErrorBadProfiles);
 }
 
 }  // namespace art
diff --git a/profman/profman.cc b/profman/profman.cc
index 1968468..8e2f3b1 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -36,7 +36,6 @@
 #include "android-base/parsebool.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
-
 #include "base/array_ref.h"
 #include "base/dumpable.h"
 #include "base/logging.h"  // For InitLogging.
@@ -64,6 +63,7 @@
 #include "profile/profile_boot_info.h"
 #include "profile/profile_compilation_info.h"
 #include "profile_assistant.h"
+#include "profman/profman_result.h"
 
 namespace art {
 
@@ -118,7 +118,10 @@
   UsageError("");
   UsageError("  --profile-file=<filename>: specify profiler output file to use for compilation.");
   UsageError("      Can be specified multiple time, in which case the data from the different");
-  UsageError("      profiles will be aggregated.");
+  UsageError("      profiles will be aggregated. Can also be specified zero times, in which case");
+  UsageError("      profman will still analyze the reference profile against the given --apk and");
+  UsageError("      return exit code based on whether the reference profile is empty and whether");
+  UsageError("      an error occurs, but no merge will happen.");
   UsageError("");
   UsageError("  --profile-file-fd=<number>: same as --profile-file but accepts a file descriptor.");
   UsageError("      Cannot be used together with --profile-file.");
@@ -126,8 +129,8 @@
   UsageError("  --reference-profile-file=<filename>: specify a reference profile.");
   UsageError("      The data in this file will be compared with the data obtained by merging");
   UsageError("      all the files specified with --profile-file or --profile-file-fd.");
-  UsageError("      If the exit code is EXIT_COMPILE then all --profile-file will be merged into");
-  UsageError("      --reference-profile-file. ");
+  UsageError("      If the exit code is ProfmanResult::kCompile then all --profile-file will be");
+  UsageError("      merged into --reference-profile-file. ");
   UsageError("");
   UsageError("  --reference-profile-file-fd=<number>: same as --reference-profile-file but");
   UsageError("      accepts a file descriptor. Cannot be used together with");
@@ -194,7 +197,7 @@
   UsageError("      the min percent of new classes to trigger a compilation.");
   UsageError("");
 
-  exit(EXIT_FAILURE);
+  exit(ProfmanResult::kErrorUsage);
 }
 
 // Note: make sure you update the Usage if you change these values.
@@ -501,11 +504,10 @@
     }
   };
 
-  ProfileAssistant::ProcessingResult ProcessProfiles() {
-    // Validate that at least one profile file was passed, as well as a reference profile.
-    if (profile_files_.empty() && profile_files_fd_.empty()) {
-      Usage("No profile files specified.");
-    }
+  ProfmanResult::ProcessingResult ProcessProfiles() {
+    // Validate that a reference profile was passed, at the very least. It's okay that profiles are
+    // missing, in which case profman will still analyze the reference profile (to check whether
+    // it's empty), but no merge will happen.
     if (reference_profile_file_.empty() && !FdIsValid(reference_profile_file_fd_)) {
       Usage("No reference profile file specified.");
     }
@@ -518,7 +520,7 @@
     // Check if we have any apks which we should use to filter the profile data.
     std::set<ProfileFilterKey> profile_filter_keys;
     if (!GetProfileFilterKeyFromApks(&profile_filter_keys)) {
-      return ProfileAssistant::kErrorIO;
+      return ProfmanResult::kErrorIO;
     }
 
     // Build the profile filter function. If the set of keys is empty it means we
@@ -536,9 +538,9 @@
             }
         };
 
-    ProfileAssistant::ProcessingResult result;
+    ProfmanResult::ProcessingResult result;
 
-    if (profile_files_.empty()) {
+    if (reference_profile_file_.empty()) {
       // The file doesn't need to be flushed here (ProcessProfiles will do it)
       // so don't check the usage.
       File file(reference_profile_file_fd_, false);
@@ -1854,7 +1856,7 @@
     return copy_and_update_profile_key_;
   }
 
-  int32_t CopyAndUpdateProfileKey() {
+  ProfmanResult::CopyAndUpdateResult CopyAndUpdateProfileKey() {
     // Validate that at least one profile file was passed, as well as a reference profile.
     if (!(profile_files_.size() == 1 ^ profile_files_fd_.size() == 1)) {
       Usage("Only one profile file should be specified.");
@@ -1867,10 +1869,6 @@
       Usage("No apk files specified");
     }
 
-    static constexpr int32_t kErrorFailedToUpdateProfile = -1;
-    static constexpr int32_t kErrorFailedToSaveProfile = -2;
-    static constexpr int32_t kErrorFailedToLoadProfile = -3;
-
     bool use_fds = profile_files_fd_.size() == 1;
 
     ProfileCompilationInfo profile;
@@ -1882,15 +1880,19 @@
       // Open the dex files to look up classes and methods.
       std::vector<std::unique_ptr<const DexFile>> dex_files;
       OpenApkFilesFromLocations(&dex_files);
-      if (!profile.UpdateProfileKeys(dex_files)) {
-        return kErrorFailedToUpdateProfile;
+      bool updated = false;
+      if (!profile.UpdateProfileKeys(dex_files, &updated)) {
+        return ProfmanResult::kCopyAndUpdateErrorFailedToUpdateProfile;
       }
       bool result = use_fds
           ? profile.Save(reference_profile_file_fd_)
           : profile.Save(reference_profile_file_, /*bytes_written=*/ nullptr);
-      return result ? 0 : kErrorFailedToSaveProfile;
+      if (!result) {
+        return ProfmanResult::kCopyAndUpdateErrorFailedToSaveProfile;
+      }
+      return updated ? ProfmanResult::kCopyAndUpdateSuccess : ProfmanResult::kCopyAndUpdateNoUpdate;
     } else {
-      return kErrorFailedToLoadProfile;
+      return ProfmanResult::kCopyAndUpdateErrorFailedToLoadProfile;
     }
   }
 
@@ -1950,7 +1952,7 @@
   return ics.Dump(os);
 }
 
-// See ProfileAssistant::ProcessingResult for return codes.
+// See ProfmanResult for return codes.
 static int profman(int argc, char** argv) {
   ProfMan profman;