Script variable path error fixed (#3861)

Resolves https://github.com/tuist/tuist/issues/1870

- `TargetScriptsContentHasher` now accounts for input and output paths that include variables

Test Plan:

```bash
$ mkdir baklava
$ cd baklava
$ tuist init --platform ios
$ tuist edit
```

add a script phase with output path, including an Xcode build environment variables such as `DERIVED_FILE_DIR`
(see more env variables by running `xcodebuild -showBuildSettings`)

```swift
  sources: ["Targets/\(name)/Sources/**"], resources: [], scripts: [
                          .pre(
                            script: "ls -la > ${SCRIPT_OUTPUT_FILE_0}",
                            name: "",
                            outputPaths: ["$(DERIVED_DILE_DIR)/script_ran.txt"]
                          )
                         ],
```

```bash
$ tuist test
```

You'll see the logs as

```bash
Generating project for testing
File not found at /Users/eekin/Desktop/baklava/$(DERIVED_DILE_DIR)/script_ran.txt
Consider creating an issue using the following link: https://github.com/tuist/tuist/issues/new/choose
```
This commit is contained in:
Erk Ekin 2022-01-05 20:21:37 +03:00 committed by GitHub
parent 74fb6660bb
commit 0d305e31f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 225 additions and 4 deletions

View File

@ -27,10 +27,18 @@ public final class TargetScriptsContentHasher: TargetScriptsContentHashing {
for script in targetScripts { for script in targetScripts {
var pathsToHash: [AbsolutePath] = [] var pathsToHash: [AbsolutePath] = []
script.path.map { pathsToHash.append($0) } script.path.map { pathsToHash.append($0) }
pathsToHash.append(contentsOf: script.inputPaths) let scriptPaths = script.inputPaths + script.inputFileListPaths + script.outputPaths + script.outputFileListPaths
pathsToHash.append(contentsOf: script.inputFileListPaths) scriptPaths.forEach { path in
pathsToHash.append(contentsOf: script.outputPaths) if path.pathString.contains("$") {
pathsToHash.append(contentsOf: script.outputFileListPaths) stringsToHash.append(path.pathString)
logger
.notice(
"The path of the file \'\(path.url.lastPathComponent)\' is hashed, not the content. Because it has a build variable."
)
} else {
pathsToHash.append(path)
}
}
let fileHashes = try pathsToHash.map { try contentHasher.hash(path: $0) } let fileHashes = try pathsToHash.map { try contentHasher.hash(path: $0) }
stringsToHash.append( stringsToHash.append(
contentsOf: fileHashes + contentsOf: fileHashes +

View File

@ -55,6 +55,51 @@ final class TargetScriptsContentHasherTests: TuistUnitTestCase {
// MARK: - Tests // MARK: - Tests
func test_hash_targetAction_withBuildVariables_callsMockHasherWithOnlyPathWithoutBuildVariable() throws {
// Given
let inputPaths1Hash = "inputPaths1-hash"
let inputFileListPaths1 = "inputFileListPaths1-hash"
let outputPaths1 = "outputPaths1-hash"
let outputFileListPaths1 = "outputFileListPaths1-hash"
mockContentHasher.stubHashForPath[AbsolutePath("/$(SRCROOT)/inputPaths1")] = inputPaths1Hash
mockContentHasher.stubHashForPath[AbsolutePath("/$(SRCROOT)/inputFileListPaths1")] = inputFileListPaths1
mockContentHasher.stubHashForPath[AbsolutePath("/$(DERIVED_FILE_DIR)/outputPaths1")] = outputPaths1
mockContentHasher.stubHashForPath[AbsolutePath("/outputFileListPaths1")] = outputFileListPaths1
let targetScript = makeTargetScript(
inputPaths: [AbsolutePath("/$(SRCROOT)/inputPaths1")],
inputFileListPaths: [AbsolutePath("/$(SRCROOT)/inputFileListPaths1")],
outputPaths: [AbsolutePath("/$(DERIVED_FILE_DIR)/outputPaths1")],
outputFileListPaths: [AbsolutePath("/outputFileListPaths1")]
)
// When
_ = try subject.hash(targetScripts: [targetScript])
// Then
let expected = [
"/$(SRCROOT)/inputPaths1",
"/$(SRCROOT)/inputFileListPaths1",
"/$(DERIVED_FILE_DIR)/outputPaths1",
outputFileListPaths1,
"1",
"tool1",
"pre",
"arg1",
"arg2",
]
XCTAssertPrinterOutputContains(
"The path of the file 'inputPaths1' is hashed, not the content. Because it has a build variable."
)
XCTAssertPrinterOutputContains(
"The path of the file 'inputFileListPaths1' is hashed, not the content. Because it has a build variable."
)
XCTAssertPrinterOutputContains(
"The path of the file 'outputPaths1' is hashed, not the content. Because it has a build variable."
)
XCTAssertEqual(mockContentHasher.hashStringsSpy, expected)
}
func test_hash_targetAction_callsMockHasherWithExpectedStrings() throws { func test_hash_targetAction_callsMockHasherWithExpectedStrings() throws {
// Given // Given
let inputPaths1Hash = "inputPaths1-hash" let inputPaths1Hash = "inputPaths1-hash"

View File

@ -17,6 +17,16 @@ Scenario: The project is an iOS application with target actions
Then I should be able to build for iOS the scheme App Then I should be able to build for iOS the scheme App
Then I should be able to build for iOS the scheme AppWithSpace Then I should be able to build for iOS the scheme AppWithSpace
Scenario: The project is an iOS application with target actions with build variable
Given that tuist is available
And I have a working directory
Then I copy the fixture ios_app_with_build_variables into the working directory
Then tuist generates the project
Then in project App the target App should have the build phase Tuist in the first position
Then in the build phase the field for output paths should have a path with $(DEVELOPER_FILE_DIR)/output.txt
Then I should be able to warm the cache and get no errors
Then I should be able to build for iOS the scheme App
Scenario: The project is an iOS application with remote Swift package (ios_app_with_remote_swift_package) Scenario: The project is an iOS application with remote Swift package (ios_app_with_remote_swift_package)
Given that tuist is available Given that tuist is available
And I have a working directory And I have a working directory

View File

@ -243,6 +243,10 @@ An iOS app whose Config file requires an Xcode version that is not available in
An iOS app with a target that has pre and post actions. An iOS app with a target that has pre and post actions.
## ios_app_with_build_variables
An iOS app with a Xcode build variables defined in pre action.
## ios_app_with_remote_swift_package ## ios_app_with_remote_swift_package
An iOS application with remote Swift package An iOS application with remote Swift package

View File

@ -0,0 +1,64 @@
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Xcode ###
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
### Xcode Patch ###
*.xcodeproj/*
!*.xcodeproj/project.pbxproj
!*.xcodeproj/xcshareddata/
!*.xcworkspace/contents.xcworkspacedata
/*.gcno
### Projects ###
*.xcodeproj
*.xcworkspace
Pods/

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright ©. All rights reserved.</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,23 @@
import ProjectDescription
let project = Project(
name: "App",
targets: [
Target(
name: "App",
platform: .iOS,
product: .app,
bundleId: "io.tuist.app",
infoPlist: "Info.plist",
sources: ["Sources/**"],
scripts: [
.pre(
tool: "/bin/echo",
arguments: ["\"tuist\""],
name: "Tuist",
outputPaths: ["$(DERIVED_FILE_DIR)/output.txt"]
),
]
),
]
)

View File

@ -0,0 +1,18 @@
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(
_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let viewController = UIViewController()
viewController.view.backgroundColor = .white
window?.rootViewController = viewController
window?.makeKeyAndVisible()
return true
}
}

View File

@ -0,0 +1 @@
echo "script"

View File

@ -0,0 +1,5 @@
import ProjectDescription
let workspace = Workspace(name: "App", projects: [
"App",
])