diff --git a/gclient_scm.py b/gclient_scm.py index e8c1711f8..85bb0eca0 100644 --- a/gclient_scm.py +++ b/gclient_scm.py @@ -85,6 +85,8 @@ def CreateSCM(url, root_dir=None, relpath=None): scm_name = GetScmName(url) if not scm_name in SCM_MAP: raise gclient_utils.Error('No SCM found for url %s' % url) + if not gclient_utils.FindCommandExecutable(scm_name): + raise gclient_utils.Error('%s command not found' % scm_name) return SCM_MAP[scm_name](url, root_dir, relpath) diff --git a/gclient_utils.py b/gclient_utils.py index 35f8b3bf4..ac5f0b4f3 100644 --- a/gclient_utils.py +++ b/gclient_utils.py @@ -193,6 +193,16 @@ def safe_makedirs(tree): raise +def FindCommandExecutable(cmd): + """Finds the specified |cmd| in $PATH and returns its path. Returns None + if it's not found.""" + for path in os.environ['PATH'].split(os.pathsep): + full_path = os.path.join(path, cmd) + if os.path.isfile(full_path) and os.access(full_path, os.X_OK): + return full_path + return None + + def CheckCallAndFilterAndHeader(args, always=False, **kwargs): """Adds 'header' support to CheckCallAndFilter. diff --git a/tests/gclient_scm_test.py b/tests/gclient_scm_test.py index 94ab4203a..c83822ce2 100755 --- a/tests/gclient_scm_test.py +++ b/tests/gclient_scm_test.py @@ -55,6 +55,7 @@ class BaseTestCase(GCBaseTestCase, SuperMoxTestBase): 'CheckCallAndFilterAndHeader') self.mox.StubOutWithMock(gclient_scm.gclient_utils, 'FileRead') self.mox.StubOutWithMock(gclient_scm.gclient_utils, 'FileWrite') + self.mox.StubOutWithMock(gclient_scm.gclient_utils, 'FindCommandExecutable') self.mox.StubOutWithMock(gclient_scm.gclient_utils, 'RemoveDirectory') self.mox.StubOutWithMock(gclient_scm.scm.SVN, 'Capture') self.mox.StubOutWithMock(gclient_scm.scm.SVN, '_CaptureInfo') @@ -111,6 +112,9 @@ class SVNWrapperTestCase(BaseTestCase): 'url', ] + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") + self.mox.ReplayAll() + # If you add a member, be sure to add the relevant test! self.compareMembers(self._scm_wrapper('svn://a'), members) @@ -122,6 +126,7 @@ class SVNWrapperTestCase(BaseTestCase): def testSVNFullUrlForRelativeUrl(self): self.url = 'svn://a/b/c/d' + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") self.mox.ReplayAll() scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, relpath=self.relpath) @@ -130,6 +135,7 @@ class SVNWrapperTestCase(BaseTestCase): def testGITFullUrlForRelativeUrl(self): self.url = 'git://a/b/c/d' + gclient_scm.gclient_utils.FindCommandExecutable('git').AndReturn("./git") self.mox.ReplayAll() scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, relpath=self.relpath) @@ -138,6 +144,7 @@ class SVNWrapperTestCase(BaseTestCase): def testGITFakeHttpUrl(self): self.url = 'git+http://foo' + gclient_scm.gclient_utils.FindCommandExecutable('git').AndReturn("./git") self.mox.ReplayAll() scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, relpath=self.relpath) @@ -146,6 +153,7 @@ class SVNWrapperTestCase(BaseTestCase): def testGITFakeHttpsUrl(self): self.url = 'git+https://foo' + gclient_scm.gclient_utils.FindCommandExecutable('git').AndReturn("./git") self.mox.ReplayAll() scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, relpath=self.relpath) @@ -155,6 +163,7 @@ class SVNWrapperTestCase(BaseTestCase): options = self.Options(verbose=False) gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(False) + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") self.mox.ReplayAll() scm = self._scm_wrapper(url=self.url, root_dir=self.root_dir, @@ -169,6 +178,7 @@ class SVNWrapperTestCase(BaseTestCase): def testRevertMissing(self): options = self.Options(verbose=True) + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") gclient_scm.os.path.isdir(self.base_path).AndReturn(False) gclient_scm.os.path.exists(self.base_path).AndReturn(False) gclient_scm.scm.SVN.Capture(['--version'], None @@ -211,6 +221,7 @@ class SVNWrapperTestCase(BaseTestCase): gclient_scm.os.path.exists(parent).AndReturn(False) gclient_scm.os.makedirs(parent) gclient_scm.os.path.exists(parent).AndReturn(True) + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") files_list = self.mox.CreateMockAnything() gclient_scm.scm.SVN.Capture(['--version'], None ).AndReturn('svn, version 1.6') @@ -230,6 +241,7 @@ class SVNWrapperTestCase(BaseTestCase): def testRevertNone(self): options = self.Options(verbose=True) + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") gclient_scm.os.path.isdir(self.base_path).AndReturn(True) gclient_scm.os.path.isdir(join(self.base_path, '.svn')).AndReturn(True) gclient_scm.scm.SVN.CaptureStatus(None, self.base_path).AndReturn([]) @@ -250,6 +262,7 @@ class SVNWrapperTestCase(BaseTestCase): options = self.Options(verbose=True) gclient_scm.os.path.isdir(self.base_path).AndReturn(True) gclient_scm.os.path.isdir(join(self.base_path, '.svn')).AndReturn(True) + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") items = [ ('~ ', 'a'), ] @@ -279,6 +292,7 @@ class SVNWrapperTestCase(BaseTestCase): options = self.Options(verbose=True) gclient_scm.os.path.isdir(self.base_path).AndReturn(True) gclient_scm.os.path.isdir(join(self.base_path, '.svn')).AndReturn(True) + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") items = [ ('~ ', '.'), ] @@ -302,6 +316,7 @@ class SVNWrapperTestCase(BaseTestCase): def testStatus(self): options = self.Options(verbose=True) + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") gclient_scm.os.path.isdir(self.base_path).AndReturn(True) gclient_scm.scm.SVN.RunAndGetFileList( options.verbose, @@ -324,6 +339,7 @@ class SVNWrapperTestCase(BaseTestCase): file_info.url = self.url file_info.uuid = 'ABC' file_info.revision = 42 + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(False) # Checkout. @@ -358,6 +374,7 @@ class SVNWrapperTestCase(BaseTestCase): gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(False) gclient_scm.os.path.exists(self.base_path).AndReturn(True) + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") # Checkout or update. dotted_path = join(self.base_path, '.') @@ -398,6 +415,7 @@ class SVNWrapperTestCase(BaseTestCase): 'UUID': 'ABC', 'Revision': 42, } + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(False) gclient_scm.os.path.exists(self.base_path).AndReturn(True) @@ -431,6 +449,7 @@ class SVNWrapperTestCase(BaseTestCase): 'UUID': 'ABC', 'Revision': 42, } + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(False) gclient_scm.os.path.exists(self.base_path).AndReturn(True) @@ -476,6 +495,7 @@ class SVNWrapperTestCase(BaseTestCase): ).AndReturn('svn, version 1.5.1 (r32289)') gclient_scm.os.path.exists(join(self.base_path, '.svn')).AndReturn(False) gclient_scm.os.path.exists(join(self.base_path, 'DEPS')).AndReturn(False) + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") # Verify no locked files. dotted_path = join(self.base_path, '.') @@ -515,6 +535,7 @@ class SVNWrapperTestCase(BaseTestCase): gclient_scm.scm.SVN.Capture(['--version'], None ).AndReturn('svn, version 1.4.4 (r25188)') gclient_scm.os.path.exists(self.base_path).AndReturn(True) + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") # When checking out a single file with svn 1.4, we use svn export files_list = self.mox.CreateMockAnything() @@ -543,6 +564,7 @@ class SVNWrapperTestCase(BaseTestCase): # the old DEPS file. gclient_scm.os.path.exists(join(self.base_path, 'DEPS')).AndReturn(True) gclient_scm.os.remove(join(self.base_path, 'DEPS')) + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") # Verify no locked files. gclient_scm.scm.SVN.CaptureStatus( @@ -587,6 +609,7 @@ class SVNWrapperTestCase(BaseTestCase): gclient_scm.scm.SVN.Capture(['--version'], None ).AndReturn('svn, version 1.5.1 (r32289)') gclient_scm.os.path.exists(join(self.base_path, '.svn')).AndReturn(True) + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") # Verify no locked files. gclient_scm.scm.SVN.CaptureStatus(None, join(self.base_path, '.') @@ -610,6 +633,7 @@ class SVNWrapperTestCase(BaseTestCase): def testUpdateGit(self): options = self.Options(verbose=True) + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") file_path = gclient_scm.os.path.join(self.root_dir, self.relpath, '.git') gclient_scm.os.path.exists(file_path).AndReturn(True) @@ -623,6 +647,7 @@ class SVNWrapperTestCase(BaseTestCase): def testUpdateHg(self): options = self.Options(verbose=True) + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") gclient_scm.os.path.exists(join(self.base_path, '.git')).AndReturn(False) gclient_scm.os.path.exists(join(self.base_path, '.hg')).AndReturn(True) @@ -645,6 +670,7 @@ class SVNWrapperTestCase(BaseTestCase): ).AndReturn(True) gclient_scm.scm.SVN.IsValidRevision(url='%s@%s' % (self.url, 'fake') ).AndReturn(False) + gclient_scm.gclient_utils.FindCommandExecutable('svn').AndReturn("./svn") self.mox.ReplayAll() @@ -1104,6 +1130,7 @@ class ManagedGitWrapperTestCaseMox(BaseTestCase): ).AndReturn(False) gclient_scm.scm.os.path.isdir(self.base_path).AndReturn(True) + gclient_scm.gclient_utils.FindCommandExecutable('git').AndReturn("./git") self.mox.ReplayAll() @@ -1157,6 +1184,7 @@ class ManagedGitWrapperTestCaseMox(BaseTestCase): gclient_scm.os.path.isdir(self.base_path).AndReturn(False) gclient_scm.os.path.isdir(self.base_path).MultipleTimes().AndReturn(True) + gclient_scm.gclient_utils.FindCommandExecutable('git').AndReturn("./git") self.mox.ReplayAll() diff --git a/tests/gclient_utils_test.py b/tests/gclient_utils_test.py index 9f01c5d1e..071fd820b 100755 --- a/tests/gclient_utils_test.py +++ b/tests/gclient_utils_test.py @@ -30,10 +30,10 @@ class GclientUtilsUnittest(GclientUtilBase): members = [ 'Annotated', 'AutoFlush', 'CheckCallAndFilter', 'CheckCallAndFilterAndHeader', 'Error', 'ExecutionQueue', 'FileRead', - 'FileWrite', 'FindFileUpwards', 'FindGclientRoot', - 'GetGClientRootAndEntries', 'GetEditor', 'IsDateRevision', - 'MakeDateRevision', 'MakeFileAutoFlush', 'MakeFileAnnotated', - 'PathDifference', 'ParseCodereviewSettingsContent', + 'FileWrite', 'FindCommandExecutable', 'FindFileUpwards', + 'FindGclientRoot', 'GetGClientRootAndEntries', 'GetEditor', + 'IsDateRevision', 'MakeDateRevision', 'MakeFileAutoFlush', + 'MakeFileAnnotated', 'PathDifference', 'ParseCodereviewSettingsContent', 'PrintableObject', 'RemoveDirectory', 'RunEditor', 'SplitUrlRevision', 'SyntaxErrorToError', 'UpgradeToHttps', 'Wrapper', 'WorkItem',