Why is this needed?
In Build Phase scripts there is a need to understand in which directory the Workspace is located.
There is no way to find out this directory using Xcode’s built-in environment variables, as far as I know. (SRCROOT
is the .xcodeproj directory of the project, not the workspace)
For example, I have a SwiftGen script in the build phase, in which I need to specify the path to a custom stenicl template.
swiftgen run xcassets \
--quiet \
--templatePath "/Users/ernest0n/Projects/MY_PROJECT/Tuist/ProjectDescriptionHelpers/TargetScript/SwiftGen/Templates/Assets.stencil" \
--output "/Users/ernest0n/Projects/MY_PROJECT/iOSApp/Sources/Generated/Assets+Generated.swift" \
"/Users/ernest0n/Projects/MY_PROJECT/iOSApp/Resources/Assets"
All custom templates are located in the root directory of the project and in the script I need to know where this folder with templates is located, without using ../
to return to the previous directory, because the manifests of my projects can be located at different levels of the file structure.
Another example: Execute the swiftlint
command from the root directory, not the specific xcodeproj
directory.
cd "/Users/ernest0n/Projects/MY_PROJECT
swiftlint lint --config "/Users/ernest0n/Projects/MY_PROJECT/.swiftlint.yml" \
This way the linter will scan the files of the entire project, not the specific xcodeproj
, which may be located in a child directory.
Proposal
Add an extension to Path
to get the path to absolute and relative paths:
// Users/<username>/my-project/
Path.rootDirectory.absoluteString
// ./
Path.rootDirectory.relativeString
// Users/<username>/my-project/Modules/Core/
Path.manifestDirectory.absoluteString
// ./Modules/Core/
Path.manifestDirectory.relativeString
// ./Modules/Core/Resources/Foo.strings
// Path.relativeToManifest("Resources/Foo.strings").relativeString
Thanks @Ernest0N for sharing this idea.
My suggestion is that your scripts don’t contain absolute paths, because this creates an implicit dependency between the project and the environment, going against aiming for hemerticity, which is something you’ll most likely want to have in your project.
How are you currently solving this? I’d go as far as to suggest not to run those things in build phases. Script build phases are compilation units in your graph, that might make your Swift UI previews work unreliably. So the fewer, the better. I know it sounds odd, considering it’s possible, but not everything that’s possible in Xcode is a good idea if you want the tool to work reliably,
In my opinion, in Tuist, the configurations of all projects is a single environment, not isolated environments.
We cannot technically generate an xcodeproj based only on Project.swift of one feature module, because it will not contain information about links to dependencies from other projects.
# Example
tuist generate --path Features/FeatureName/
Also, we cannot open the xcodeproj of a feature module and compile target if it is part of a larger xcworkspace (which refers to other targets).
The single entry point is Workspace.swift
(or .xcworkspace), which knows about each project, and each project in it can (technically) depend on the targets of other projects (i.e. know).
Therefore, I do not see any contradictions and a [hemerticity(Hermeticity | Bazel) problem. I would be glad to know your opinion on this matter!
How are you currently solving this?
I hope after I tell you, this hack won’t be fixed 
Now I take #filePath
and recursively go back up the parent directory until I find the folder that contains Workspace.swift
.
An alternative way (possibly more cache-predictable) is to add the TUIST_WORKSPACE_ROOT
variable via mise.toml
which will reference the project’s root directory.
I agree that Xcode Build Phase has its drawbacks, but we are still willing to pay fractions of a second to reduce the routine in our work.
I’ll add another way to the Proposal’s options of how one could access the absolute path:
TargetScript.pre(
script: """
\(.workspacePath)/to/filepath.swift
"""
)
Inspired by the interesting way interpolation was used in Noora)
I’m onboard with adding a declarative API to scripts such that developers can fetch an absolute path, or construct one relative to a directory known at generation time. This is something that we do in other places. I think Noora’s interpolation of elements in a string is a good API, but the example that you shared might not be the best one. I’d suggest that we take care of the concatenation, which should never be done by concatenating strings:
TargetScript.pre(
script: """
\(.pathRelativeToWorkspace("/to/filepath.swift"))
"""
)
What do you think @marekfort?
1 Like
It would have been great if we could leverage the Xcode variables like PROJECT_DIR
, but I don’t know of any way to get such a path for the actual workspace, so a convenience API seems like a good alternative 
@Ernest0N would you like to take this on? If so, we can help with the review once it’s ready 