Use $HOME/.config/depot_tools on linux for .cfg files

Or $XDG_CONFIG_HOME/.config/depot_tools if set

A followup CL can add support for win/macos.

Bug: b/345092320
Change-Id: I877baa4d7fd912b42cfcd88ad0aa347b700a89f2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5604606
Commit-Queue: Richard Wang <richardwa@google.com>
Reviewed-by: Takuto Ikuta <tikuta@chromium.org>
Reviewed-by: Josip Sokcevic <sokcevic@chromium.org>
diff --git a/.gitignore b/.gitignore
index 22b610a..e2e03d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -84,7 +84,7 @@
 *~
 *.swp
 
-# Ignore the monitoring config. It is unique for each user.
+# Ignore win/mac monitoring config. It is unique for each depot_tools checkout.
 /metrics.cfg
 
 # Ignore the ninjalog upload config.
diff --git a/metrics.py b/metrics.py
index 627db84..0f0083d 100644
--- a/metrics.py
+++ b/metrics.py
@@ -17,9 +17,10 @@
 import gclient_utils
 import metrics_utils
 import subprocess2
+import utils
 
 DEPOT_TOOLS = os.path.dirname(os.path.abspath(__file__))
-CONFIG_FILE = os.path.join(DEPOT_TOOLS, 'metrics.cfg')
+CONFIG_FILE = utils.depot_tools_config_path('metrics.cfg')
 UPLOAD_SCRIPT = os.path.join(DEPOT_TOOLS, 'upload_metrics.py')
 
 DEFAULT_COUNTDOWN = 10
diff --git a/ninjalog_uploader_wrapper.py b/ninjalog_uploader_wrapper.py
index f17a053..563144e 100755
--- a/ninjalog_uploader_wrapper.py
+++ b/ninjalog_uploader_wrapper.py
@@ -11,10 +11,11 @@
 
 import ninjalog_uploader
 import subprocess2
+import utils
 
 THIS_DIR = os.path.dirname(__file__)
 UPLOADER = os.path.join(THIS_DIR, "ninjalog_uploader.py")
-CONFIG = os.path.join(THIS_DIR, "ninjalog.cfg")
+CONFIG = utils.depot_tools_config_path("ninjalog.cfg")
 VERSION = 3
 
 
diff --git a/reclient_metrics.py b/reclient_metrics.py
index 934c45e..6d3771f 100755
--- a/reclient_metrics.py
+++ b/reclient_metrics.py
@@ -10,8 +10,10 @@
 import subprocess
 import sys
 
+import utils
+
 THIS_DIR = os.path.dirname(__file__)
-CONFIG = os.path.join(THIS_DIR, 'reclient_metrics.cfg')
+CONFIG = utils.depot_tools_config_path('reclient_metrics.cfg')
 VERSION = 1
 
 
diff --git a/tests/metrics_test.py b/tests/metrics_test.py
index 9e02628..c011ebc 100644
--- a/tests/metrics_test.py
+++ b/tests/metrics_test.py
@@ -14,6 +14,7 @@
 
 import metrics
 import metrics_utils
+import utils
 
 # TODO: Should fix these warnings.
 # pylint: disable=line-too-long
@@ -30,7 +31,7 @@
 
 class MetricsCollectorTest(unittest.TestCase):
     def setUp(self):
-        self.config_file = os.path.join(ROOT_DIR, 'metrics.cfg')
+        self.config_file = utils.depot_tools_config_path('metrics.cfg')
         self.collector = metrics.MetricsCollector()
 
         # Keep track of the URL requests, file reads/writes and subprocess
diff --git a/tests/utils_test.py b/tests/utils_test.py
index 15cd3fe..5d0ca81 100755
--- a/tests/utils_test.py
+++ b/tests/utils_test.py
@@ -5,7 +5,9 @@
 
 import logging
 import os
+import shutil
 import sys
+import tempfile
 import unittest
 from unittest import mock
 
@@ -41,6 +43,77 @@
         self.assertEqual(version, 'unknown')
 
 
+class ConfigDirTest(unittest.TestCase):
+
+    @mock.patch('sys.platform', 'win')
+    def testWin(self):
+        self.assertEqual(DEPOT_TOOLS_ROOT, utils.depot_tools_config_dir())
+
+    @mock.patch('sys.platform', 'darwin')
+    def testMac(self):
+        self.assertEqual(DEPOT_TOOLS_ROOT, utils.depot_tools_config_dir())
+
+    @mock.patch('sys.platform', 'foo')
+    def testOther(self):
+        self.assertEqual(DEPOT_TOOLS_ROOT, utils.depot_tools_config_dir())
+
+    @mock.patch('sys.platform', 'linux')
+    @mock.patch.dict('os.environ', {})
+    def testLinuxDefault(self):
+        self.assertEqual(
+            os.path.join(os.path.expanduser('~/.config'), 'depot_tools'),
+            utils.depot_tools_config_dir())
+
+    @mock.patch('sys.platform', 'linux')
+    @mock.patch.dict('os.environ', {'XDG_CONFIG_HOME': '/my/home'})
+    def testLinuxCustom(self):
+        self.assertEqual(os.path.join('/my/home', 'depot_tools'),
+                         utils.depot_tools_config_dir())
+
+
+class ConfigPathTest(unittest.TestCase):
+
+    def setUp(self):
+        self.temp_dir = tempfile.mkdtemp(prefix='utils_test')
+        self.config_dir = os.path.join(self.temp_dir, 'test_files')
+
+        self.isfile = mock.Mock()
+        self.move = mock.Mock()
+
+        mock.patch('os.path.isfile', self.isfile).start()
+        mock.patch('shutil.move', self.move).start()
+        mock.patch('utils.depot_tools_config_dir',
+                   lambda: self.config_dir).start()
+
+        self.addCleanup(mock.patch.stopall)
+        self.addCleanup(shutil.rmtree, self.temp_dir)
+
+    def testCreatesConfigDir(self):
+        # Ensure "legacy path" doesn't exist so that nothing gets moved.
+        def EnsureLegacyPathNotExists(path):
+            return path != os.path.join(DEPOT_TOOLS_ROOT, 'metrics.cfg')
+
+        self.isfile.side_effect = EnsureLegacyPathNotExists
+
+        self.assertEqual(os.path.join(self.config_dir, 'metrics.cfg'),
+                         utils.depot_tools_config_path('metrics.cfg'))
+        self.assertTrue(os.path.exists(self.config_dir))
+        self.move.assert_not_called()
+
+    def testMovesLegacy(self):
+        # Ensure "legacy path" exists so that it gets moved.
+        def EnsureLegacyPathExists(path):
+            return path == os.path.join(DEPOT_TOOLS_ROOT, 'metrics.cfg')
+
+        self.isfile.side_effect = EnsureLegacyPathExists
+
+        self.assertEqual(os.path.join(self.config_dir, 'metrics.cfg'),
+                         utils.depot_tools_config_path('metrics.cfg'))
+        self.move.assert_called_once_with(
+            os.path.join(DEPOT_TOOLS_ROOT, 'metrics.cfg'),
+            os.path.join(self.config_dir, 'metrics.cfg'))
+
+
 if __name__ == '__main__':
     logging.basicConfig(
         level=logging.DEBUG if '-v' in sys.argv else logging.ERROR)
diff --git a/utils.py b/utils.py
index a85d2f7..5500dbf 100644
--- a/utils.py
+++ b/utils.py
@@ -3,7 +3,12 @@
 # found in the LICENSE file.
 
 import os
+import pathlib
+import shutil
 import subprocess
+import sys
+
+DEPOT_TOOLS_ROOT = os.path.dirname(os.path.abspath(__file__))
 
 
 def depot_tools_version():
@@ -23,3 +28,29 @@
         return 'recipes.cfg-%d' % (mtime)
     except Exception:
         return 'unknown'
+
+
+def depot_tools_config_dir():
+    # Use depot tools path for mac, windows.
+    if not sys.platform.startswith('linux'):
+        return DEPOT_TOOLS_ROOT
+
+    # Use $XDG_CONFIG_HOME/depot_tools or $HOME/.config/depot_tools on linux.
+    config_root = os.getenv('XDG_CONFIG_HOME', os.path.expanduser('~/.config'))
+    return os.path.join(config_root, 'depot_tools')
+
+
+def depot_tools_config_path(file):
+    config_dir = depot_tools_config_dir()
+    expected_path = os.path.join(config_dir, file)
+
+    # Silently create config dir if necessary.
+    pathlib.Path(config_dir).mkdir(parents=True, exist_ok=True)
+
+    # Silently migrate cfg from legacy path if it exists.
+    if not os.path.isfile(expected_path):
+        legacy_path = os.path.join(DEPOT_TOOLS_ROOT, file)
+        if os.path.isfile(legacy_path):
+            shutil.move(legacy_path, expected_path)
+
+    return expected_path