Update mtime when copying files

Without updating the mtime of files when copied, the Bazel-built
version of Tulsi may not correctly run due to old *.pyc files being
used when *.py files change (because Bazel resets their timestamps).

PiperOrigin-RevId: 210111838
diff --git a/src/TulsiGenerator/XcodeProjectGenerator.swift b/src/TulsiGenerator/XcodeProjectGenerator.swift
index b4691b8..da09fa5 100644
--- a/src/TulsiGenerator/XcodeProjectGenerator.swift
+++ b/src/TulsiGenerator/XcodeProjectGenerator.swift
@@ -1353,6 +1353,8 @@
           try fileManager.removeItem(at: targetURL)
         }
         try fileManager.copyItem(at: sourceURL, to: targetURL)
+        // Touch the file so Python knows to update the .pyc files.
+        try fileManager.setAttributes([.modificationDate: Date()], ofItemAtPath: targetURL.path)
         errorInfo = nil
       } catch let e as NSError {
         errorInfo = e.localizedDescription
diff --git a/src/TulsiGeneratorTests/XcodeProjectGeneratorTests.swift b/src/TulsiGeneratorTests/XcodeProjectGeneratorTests.swift
index 2cb4874..48d6958 100644
--- a/src/TulsiGeneratorTests/XcodeProjectGeneratorTests.swift
+++ b/src/TulsiGeneratorTests/XcodeProjectGeneratorTests.swift
@@ -86,6 +86,10 @@
       XCTAssert(mockFileManager.copyOperations.keys.contains(cacheReaderURL.path))
 
       let xcp = "\(xcodeProjectPath)/xcuserdata/USER.xcuserdatad/xcschemes/xcschememanagement.plist"
+      XCTAssert(!mockFileManager.attributesMap.isEmpty)
+      mockFileManager.attributesMap.forEach { (path, attrs) in
+        XCTAssertNotNil(attrs[.modificationDate])
+      }
       XCTAssert(mockFileManager.writeOperations.keys.contains(xcp))
     } catch let e {
       XCTFail("Unexpected exception \(e)")
@@ -382,6 +386,7 @@
   var writeOperations = [String: Data]()
   var removeOperations = [String]()
   var mockContent = [String: Data]()
+  var attributesMap = [String: [FileAttributeKey: Any]]()
 
   override open var homeDirectoryForCurrentUser: URL {
     return URL(fileURLWithPath: "/Users/__MOCK_USER__", isDirectory: true)
@@ -392,10 +397,13 @@
   }
 
   override func createDirectory(at url: URL,
-                                     withIntermediateDirectories createIntermediates: Bool,
-                                     attributes: [FileAttributeKey:Any]?) throws {
+                                withIntermediateDirectories createIntermediates: Bool,
+                                attributes: [FileAttributeKey: Any]?) throws {
     guard !allowedDirectoryCreates.contains(url.path) else {
       directoryOperations.append(url.path)
+      if let attributes = attributes {
+        self.setAttributes(attributes, path: url.path)
+      }
       return
     }
     throw NSError(domain: "MockFileManager: Directory creation disallowed",
@@ -404,10 +412,13 @@
   }
 
   override func createDirectory(atPath path: String,
-                                      withIntermediateDirectories createIntermediates: Bool,
-                                      attributes: [FileAttributeKey:Any]?) throws {
+                                withIntermediateDirectories createIntermediates: Bool,
+                                attributes: [FileAttributeKey: Any]?) throws {
     guard !allowedDirectoryCreates.contains(path) else {
       directoryOperations.append(path)
+      if let attributes = attributes {
+        self.setAttributes(attributes, path: path)
+      }
       return
     }
     throw NSError(domain: "MockFileManager: Directory creation disallowed",
@@ -440,8 +451,23 @@
       fatalError("Attempting to overwrite an existing file at \(path)")
     }
     writeOperations[path] = data
+    if let attr = attr {
+      self.setAttributes(attr, path: path)
+    }
     return true
   }
+
+  fileprivate func setAttributes(_ attributes: [FileAttributeKey : Any], path: String) {
+    var currentAttributes = attributesMap[path] ?? [FileAttributeKey : Any]()
+    attributes.forEach { (k, v) in
+      currentAttributes[k] = v
+    }
+    attributesMap[path] = currentAttributes
+  }
+
+  override func setAttributes(_ attributes: [FileAttributeKey : Any], ofItemAtPath path: String) throws {
+    self.setAttributes(attributes, path: path)
+  }
 }