| ... | ... | @@ -311,7 +311,7 @@ class Downloader: | 
| 311 | 311 |  
 | 
| 312 | 312 |          return read_blobs
 | 
| 313 | 313 |  
 | 
| 314 |  | -    def _fetch_file(self, digest, file_path):
 | 
|  | 314 | +    def _fetch_file(self, digest, file_path, is_executable=False):
 | 
| 315 | 315 |          """Fetches a file using ByteStream.Read()"""
 | 
| 316 | 316 |          if self.instance_name:
 | 
| 317 | 317 |              resource_name = '/'.join([self.instance_name, 'blobs',
 | 
| ... | ... | @@ -332,7 +332,10 @@ class Downloader: | 
| 332 | 332 |  
 | 
| 333 | 333 |              assert byte_file.tell() == digest.size_bytes
 | 
| 334 | 334 |  
 | 
| 335 |  | -    def _queue_file(self, digest, file_path):
 | 
|  | 335 | +        if is_executable:
 | 
|  | 336 | +            os.chmod(file_path, 0o755) # rwxr-xr-x
 | 
|  | 337 | +
 | 
|  | 338 | +    def _queue_file(self, digest, file_path, is_executable=False):
 | 
| 336 | 339 |          """Queues a file for later batch download"""
 | 
| 337 | 340 |          if self.__file_request_size + digest.ByteSize() > MAX_REQUEST_SIZE:
 | 
| 338 | 341 |              self.flush()
 | 
| ... | ... | @@ -341,22 +344,25 @@ class Downloader: | 
| 341 | 344 |          elif self.__file_request_count >= MAX_REQUEST_COUNT:
 | 
| 342 | 345 |              self.flush()
 | 
| 343 | 346 |  
 | 
| 344 |  | -        self.__file_requests[digest.hash] = (digest, file_path)
 | 
|  | 347 | +        self.__file_requests[digest.hash] = (digest, file_path, is_executable)
 | 
| 345 | 348 |          self.__file_request_count += 1
 | 
| 346 | 349 |          self.__file_request_size += digest.ByteSize()
 | 
| 347 | 350 |          self.__file_response_size += digest.size_bytes
 | 
| 348 | 351 |  
 | 
| 349 | 352 |      def _fetch_file_batch(self, batch):
 | 
| 350 | 353 |          """Sends queued data using ContentAddressableStorage.BatchReadBlobs()"""
 | 
| 351 |  | -        batch_digests = [digest for digest, _ in batch.values()]
 | 
|  | 354 | +        batch_digests = [digest for digest, _, _ in batch.values()]
 | 
| 352 | 355 |          batch_blobs = self._fetch_blob_batch(batch_digests)
 | 
| 353 | 356 |  
 | 
| 354 |  | -        for (_, file_path), file_blob in zip(batch.values(), batch_blobs):
 | 
|  | 357 | +        for (_, file_path, is_executable), file_blob in zip(batch.values(), batch_blobs):
 | 
| 355 | 358 |              os.makedirs(os.path.dirname(file_path), exist_ok=True)
 | 
| 356 | 359 |  
 | 
| 357 | 360 |              with open(file_path, 'wb') as byte_file:
 | 
| 358 | 361 |                  byte_file.write(file_blob)
 | 
| 359 | 362 |  
 | 
|  | 363 | +            if is_executable:
 | 
|  | 364 | +                os.chmod(file_path, 0o755) # rwxr-xr-x
 | 
|  | 365 | +
 | 
| 360 | 366 |      def _fetch_directory(self, digest, directory_path):
 | 
| 361 | 367 |          """Fetches a file using ByteStream.GetTree()"""
 | 
| 362 | 368 |          # Better fail early if the local root path cannot be created:
 | 
| ... | ... | @@ -414,7 +420,7 @@ class Downloader: | 
| 414 | 420 |          for file_node in root_directory.files:
 | 
| 415 | 421 |              file_path = os.path.join(root_path, file_node.name)
 | 
| 416 | 422 |  
 | 
| 417 |  | -            self._queue_file(file_node.digest, file_path)
 | 
|  | 423 | +            self._queue_file(file_node.digest, file_path, is_executable=file_node.is_executable)
 | 
| 418 | 424 |  
 | 
| 419 | 425 |          for directory_node in root_directory.directories:
 | 
| 420 | 426 |              directory_path = os.path.join(root_path, directory_node.name)
 |