diff --git a/gclient_paths.py b/gclient_paths.py index ef2691087..998a0b39c 100644 --- a/gclient_paths.py +++ b/gclient_paths.py @@ -11,7 +11,9 @@ from __future__ import print_function import gclient_utils +import logging import os +import subprocess2 import sys @@ -25,66 +27,66 @@ def FindGclientRoot(from_dir, filename='.gclient'): return None path = split_path[0] + logging.info('Found gclient root at ' + path) + + if path == real_from_dir: + return path + # If we did not find the file in the current directory, make sure we are in a # sub directory that is controlled by this configuration. - if path != real_from_dir: - entries_filename = os.path.join(path, filename + '_entries') - if not os.path.exists(entries_filename): - # If .gclient_entries does not exist, a previous call to gclient sync - # might have failed. In that case, we cannot verify that the .gclient - # is the one we want to use. In order to not to cause too much trouble, - # just issue a warning and return the path anyway. - print( - "%s missing, %s file in parent directory %s might not be the file " - "you want to use." % (entries_filename, filename, path), - file=sys.stderr) + entries_filename = os.path.join(path, filename + '_entries') + if not os.path.exists(entries_filename): + # If .gclient_entries does not exist, a previous call to gclient sync + # might have failed. In that case, we cannot verify that the .gclient + # is the one we want to use. In order to not to cause too much trouble, + # just issue a warning and return the path anyway. + print( + "%s missing, %s file in parent directory %s might not be the file " + "you want to use." % (entries_filename, filename, path), + file=sys.stderr) + return path + + entries_content = gclient_utils.FileRead(entries_filename) + scope = {} + try: + exec(entries_content, scope) + except (SyntaxError, Exception) as e: + gclient_utils.SyntaxErrorToError(filename, e) + + all_directories = scope['entries'].keys() + path_to_check = os.path.relpath(real_from_dir, path) + while path_to_check: + if path_to_check in all_directories: return path - scope = {} - try: - import io - with io.open(entries_filename, encoding='utf-8') as f: - exec(f.read(), scope) - except SyntaxError as e: - SyntaxErrorToError(filename, e) - all_directories = scope['entries'].keys() - path_to_check = real_from_dir[len(path)+1:] - while path_to_check: - if path_to_check in all_directories: - return path - path_to_check = os.path.dirname(path_to_check) - return None + path_to_check = os.path.dirname(path_to_check) - import logging - logging.info('Found gclient root at ' + path) - return path + return None def GetPrimarySolutionPath(): """Returns the full path to the primary solution. (gclient_root + src)""" gclient_root = FindGclientRoot(os.getcwd()) - if not gclient_root: - # Some projects might not use .gclient. Try to see whether we're in a git - # checkout. - top_dir = [os.getcwd()] - def filter_fn(line): - repo_root_path = os.path.normpath(line.rstrip('\n')) - if os.path.exists(repo_root_path): - top_dir[0] = repo_root_path - try: - gclient_utils.CheckCallAndFilter(["git", "rev-parse", "--show-toplevel"], - print_stdout=False, - filter_fn=filter_fn) - except Exception: - pass - top_dir = top_dir[0] - if os.path.exists(os.path.join(top_dir, 'buildtools')): - return top_dir - return None - - # Some projects' top directory is not named 'src'. - source_dir_name = GetGClientPrimarySolutionName(gclient_root) or 'src' - return os.path.join(gclient_root, source_dir_name) + if gclient_root: + # Some projects' top directory is not named 'src'. + source_dir_name = GetGClientPrimarySolutionName(gclient_root) or 'src' + return os.path.join(gclient_root, source_dir_name) + + # Some projects might not use .gclient. Try to see whether we're in a git + # checkout that contains a 'buildtools' subdir. + top_dir = os.getcwd() + try: + top_dir = subprocess2.check_output( + ['git', 'rev-parse', '--show-toplevel']) + if sys.version_info.major == 3: + top_dir = top_dir.decode('utf-8', 'replace') + top_dir = os.path.normpath(top_dir.strip()) + except subprocess2.CalledProcessError: + pass + + if os.path.exists(os.path.join(top_dir, 'buildtools')): + return top_dir + return None def GetBuildtoolsPath(): @@ -100,12 +102,18 @@ def GetBuildtoolsPath(): primary_solution = GetPrimarySolutionPath() if not primary_solution: return None + buildtools_path = os.path.join(primary_solution, 'buildtools') - if not os.path.exists(buildtools_path): - # Buildtools may be in the gclient root. - gclient_root = FindGclientRoot(os.getcwd()) - buildtools_path = os.path.join(gclient_root, 'buildtools') - return buildtools_path + if os.path.exists(buildtools_path): + return buildtools_path + + # buildtools may be in the gclient root. + gclient_root = FindGclientRoot(os.getcwd()) + buildtools_path = os.path.join(gclient_root, 'buildtools') + if os.path.exists(buildtools_path): + return buildtools_path + + return None def GetBuildtoolsPlatformBinaryPath(): @@ -121,7 +129,7 @@ def GetBuildtoolsPlatformBinaryPath(): elif sys.platform.startswith('linux'): subdir = 'linux64' else: - raise Error('Unknown platform: ' + sys.platform) + raise gclient_utils.Error('Unknown platform: ' + sys.platform) return os.path.join(buildtools_path, subdir) @@ -135,9 +143,9 @@ def GetExeSuffix(): def GetGClientPrimarySolutionName(gclient_root_dir_path): """Returns the name of the primary solution in the .gclient file specified.""" gclient_config_file = os.path.join(gclient_root_dir_path, '.gclient') + gclient_config_contents = gclient_utils.FileRead(gclient_config_file) env = {} - exec(compile(open(gclient_config_file).read(), gclient_config_file, 'exec'), - env) + exec(gclient_config_contents, env) solutions = env.get('solutions', []) if solutions: return solutions[0].get('name') diff --git a/tests/gclient_paths_test.py b/tests/gclient_paths_test.py new file mode 100644 index 000000000..3dd47a7ed --- /dev/null +++ b/tests/gclient_paths_test.py @@ -0,0 +1,255 @@ +#!/usr/bin/env vpython3 +# coding=utf-8 +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os +import sys +import unittest + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +import gclient_paths +import gclient_utils +import subprocess2 + +if sys.version_info.major == 2: + from StringIO import StringIO + import mock +else: + from io import StringIO + from unittest import mock + + +EXCEPTION = subprocess2.CalledProcessError( + 128, ['cmd'], 'cwd', 'stdout', 'stderr') + + +class TestBase(unittest.TestCase): + def setUp(self): + super(TestBase, self).setUp() + self.file_tree = {} + self.root = 'C:\\' if sys.platform == 'win32' else '/' + self.cwd = self.root + mock.patch('gclient_utils.FileRead', self.read).start() + mock.patch('os.environ', {}).start() + mock.patch('os.getcwd', self.getcwd).start() + mock.patch('os.path.exists', self.exists).start() + mock.patch('os.path.realpath', side_effect=lambda path: path).start() + mock.patch('subprocess2.check_output').start() + mock.patch('sys.platform', '').start() + mock.patch('sys.stderr', StringIO()).start() + self.addCleanup(mock.patch.stopall) + + def getcwd(self): + return self.cwd + + def exists(self, path): + return path in self.file_tree + + def read(self, path): + return self.file_tree[path] + + def make_file_tree(self, file_tree): + self.file_tree = { + self.root + path: content + for path, content in file_tree.items() + } + + +class FindGclientRootTest(TestBase): + def testFindGclientRoot(self): + self.make_file_tree({'.gclient': ''}) + self.assertEqual(self.root, gclient_paths.FindGclientRoot(self.root)) + + def testGclientRootInParentDir(self): + self.make_file_tree({ + '.gclient': '', + '.gclient_entries': 'entries = {"foo": "..."}', + }) + self.assertEqual( + self.root, + gclient_paths.FindGclientRoot(os.path.join(self.root, 'foo', 'bar'))) + + def testGclientRootInParentDir_NotInGclientEntries(self): + self.make_file_tree({ + '.gclient': '', + '.gclient_entries': 'entries = {"foo": "..."}', + }) + self.assertIsNone( + gclient_paths.FindGclientRoot(os.path.join(self.root, 'bar', 'baz'))) + + def testGclientRootInParentDir_NoGclientEntriesFile(self): + self.make_file_tree({'.gclient': ''}) + self.assertEqual( + self.root, + gclient_paths.FindGclientRoot(os.path.join(self.root, 'x', 'y', 'z'))) + self.assertEqual( + '%s missing, .gclient file in parent directory %s might not be the ' + 'file you want to use.\n' % ( + os.path.join(self.root, '.gclient_entries'), self.root), + sys.stderr.getvalue()) + + def testGclientRootInParentDir_ErrorWhenParsingEntries(self): + self.make_file_tree({'.gclient': '', '.gclient_entries': ':P'}) + with self.assertRaises(Exception): + gclient_paths.FindGclientRoot(os.path.join(self.root, 'foo', 'bar')) + + def testRootNotFound(self): + self.assertIsNone( + gclient_paths.FindGclientRoot(os.path.join(self.root, 'x', 'y', 'z'))) + + +class GetGClientPrimarySolutionNameTest(TestBase): + def testGetGClientPrimarySolutionName(self): + self.make_file_tree({'.gclient': 'solutions = [{"name": "foo"}]'}) + self.assertEqual( + 'foo', gclient_paths.GetGClientPrimarySolutionName(self.root)) + + def testNoSolutionsInGclientFile(self): + self.make_file_tree({'.gclient': ''}) + self.assertIsNone(gclient_paths.GetGClientPrimarySolutionName(self.root)) + + +class GetPrimarySolutionPathTest(TestBase): + def testGetPrimarySolutionPath(self): + self.make_file_tree({'.gclient': 'solutions = [{"name": "foo"}]'}) + + self.assertEqual( + os.path.join(self.root, 'foo'), gclient_paths.GetPrimarySolutionPath()) + + def testSolutionNameDefaultsToSrc(self): + self.make_file_tree({'.gclient': ''}) + + self.assertEqual( + os.path.join(self.root, 'src'), gclient_paths.GetPrimarySolutionPath()) + + def testGclientRootNotFound_GitRootHasBuildtools(self): + self.make_file_tree({os.path.join('foo', 'buildtools'): ''}) + self.cwd = os.path.join(self.root, 'foo', 'bar') + subprocess2.check_output.return_value = ( + os.path.join(self.root, 'foo').replace(os.sep, '/').encode('utf-8') + + b'\n') + + self.assertEqual( + os.path.join(self.root, 'foo'), gclient_paths.GetPrimarySolutionPath()) + + def testGclientRootNotFound_NoBuildtools(self): + self.cwd = os.path.join(self.root, 'foo', 'bar') + subprocess2.check_output.return_value = b'/foo\n' + + self.assertIsNone(gclient_paths.GetPrimarySolutionPath()) + + def testGclientRootNotFound_NotInAGitRepo_CurrentDirHasBuildtools(self): + self.make_file_tree({os.path.join('foo', 'bar', 'buildtools'): ''}) + self.cwd = os.path.join(self.root, 'foo', 'bar') + subprocess2.check_output.side_effect = EXCEPTION + + self.assertEqual(self.cwd, gclient_paths.GetPrimarySolutionPath()) + + def testGclientRootNotFound_NotInAGitRepo_NoBuildtools(self): + self.cwd = os.path.join(self.root, 'foo') + subprocess2.check_output.side_effect = EXCEPTION + + self.assertIsNone(gclient_paths.GetPrimarySolutionPath()) + + +class GetBuildtoolsPathTest(TestBase): + def testEnvVarOverride(self): + os.environ = {'CHROMIUM_BUILDTOOLS_PATH': 'foo'} + + self.assertEqual('foo', gclient_paths.GetBuildtoolsPath()) + + def testNoSolutionsFound(self): + self.cwd = os.path.join(self.root, 'foo', 'bar') + subprocess2.check_output.side_effect = EXCEPTION + + self.assertIsNone(gclient_paths.GetBuildtoolsPath()) + + def testBuildtoolsInSolution(self): + self.make_file_tree({ + '.gclient': '', + os.path.join('src', 'buildtools'): '', + }) + self.cwd = os.path.join(self.root, 'src', 'foo') + + self.assertEqual( + os.path.join(self.root, 'src', 'buildtools'), + gclient_paths.GetBuildtoolsPath()) + + def testBuildtoolsInGclientRoot(self): + self.make_file_tree({'.gclient': '', 'buildtools': ''}) + self.cwd = os.path.join(self.root, 'src', 'foo') + + self.assertEqual( + os.path.join(self.root, 'buildtools'), + gclient_paths.GetBuildtoolsPath()) + + def testNoBuildtools(self): + self.make_file_tree({'.gclient': ''}) + self.cwd = os.path.join(self.root, 'foo', 'bar') + + self.assertIsNone(gclient_paths.GetBuildtoolsPath()) + + +class GetBuildtoolsPlatformBinaryPath(TestBase): + def testNoBuildtoolsPath(self): + self.make_file_tree({'.gclient': ''}) + self.cwd = os.path.join(self.root, 'foo', 'bar') + self.assertIsNone(gclient_paths.GetBuildtoolsPlatformBinaryPath()) + + def testWin(self): + self.make_file_tree({'.gclient': '', 'buildtools': ''}) + sys.platform = 'win' + + self.assertEqual( + os.path.join(self.root, 'buildtools', 'win'), + gclient_paths.GetBuildtoolsPlatformBinaryPath()) + + def testCygwin(self): + self.make_file_tree({'.gclient': '', 'buildtools': ''}) + sys.platform = 'cygwin' + + self.assertEqual( + os.path.join(self.root, 'buildtools', 'win'), + gclient_paths.GetBuildtoolsPlatformBinaryPath()) + + def testMac(self): + self.make_file_tree({'.gclient': '', 'buildtools': ''}) + sys.platform = 'darwin' + + self.assertEqual( + os.path.join(self.root, 'buildtools', 'mac'), + gclient_paths.GetBuildtoolsPlatformBinaryPath()) + + def testLinux(self): + self.make_file_tree({'.gclient': '', 'buildtools': ''}) + sys.platform = 'linux' + + self.assertEqual( + os.path.join(self.root, 'buildtools', 'linux64'), + gclient_paths.GetBuildtoolsPlatformBinaryPath()) + + def testError(self): + self.make_file_tree({'.gclient': '', 'buildtools': ''}) + sys.platform = 'foo' + + with self.assertRaises(gclient_utils.Error, msg='Unknown platform: foo'): + gclient_paths.GetBuildtoolsPlatformBinaryPath() + + +class GetExeSuffixTest(TestBase): + def testGetExeSuffix(self): + sys.platform = 'win' + self.assertEqual('.exe', gclient_paths.GetExeSuffix()) + + sys.platform = 'cygwin' + self.assertEqual('.exe', gclient_paths.GetExeSuffix()) + + sys.platform = 'foo' + self.assertEqual('', gclient_paths.GetExeSuffix()) + + +if __name__ == '__main__': + unittest.main()