--- URL: "/ru/server/on-premise/metrics" LLMS_URL: "/ru/server/on-premise/metrics.md" title: "Metrics" titleTemplate: ":title | On-premise | Server | Tuist" description: "Optimize your build times by caching compiled binaries and sharing them across different environments." --- # Metrics {#metrics} You can ingest metrics gathered by the Tuist server using [Prometheus](https://prometheus.io/) and a visualization tool such as [Grafana](https://grafana.com/) to create a custom dashboard tailored to your needs. The Prometheus metrics are served via the `/metrics` endpoint on port 9091. The Prometheus' [scrape_interval](https://prometheus.io/docs/introduction/first_steps/#configuring-prometheus) should be set as less than 10_000 seconds (we recommend keeping the default of 15 seconds). ## Elixir metrics {#elixir-metrics} By default we include metrics of the Elixir runtime, BEAM, Elixir, and some of the libraries we use. The following are some of the metrics you can expect to see: - [Application](https://hexdocs.pm/prom_ex/PromEx.Plugins.Application.html) - [BEAM](https://hexdocs.pm/prom_ex/PromEx.Plugins.Beam.html) - [Phoenix](https://hexdocs.pm/prom_ex/PromEx.Plugins.Phoenix.html) - [Phoenix LiveView](https://hexdocs.pm/prom_ex/PromEx.Plugins.PhoenixLiveView.html) - [Ecto](https://hexdocs.pm/prom_ex/PromEx.Plugins.Ecto.html) - [Oban](https://hexdocs.pm/prom_ex/PromEx.Plugins.Oban.html) We recommend checking those pages to know which metrics are available and how to use them. ## Runs metrics {#runs-metrics} A set of metrics related to Tuist Runs. ### `tuist_runs_total` (counter) {#tuist_runs_total-counter} The total number of Tuist Runs. #### Tags {#tuist-runs-total-tags} | Tag | Description | | -------- | ------------------------------------------------------------------------------------------- | | `name` | The name of the `tuist` command that was run, such as `build`, `test`, etc. | | `is_ci` | A boolean indicating if the executor was a CI or a developer's machine. | | `status` | `0` in case of `success`, `1` in case of `failure`. | ### `tuist_runs_duration_milliseconds` (histogram) {#tuist_runs_duration_milliseconds-histogram} The total duration of each tuist run in milliseconds. #### Tags {#tuist-runs-duration-miliseconds-tags} | Tag | Description | | -------- | ------------------------------------------------------------------------------------------- | | `name` | The name of the `tuist` command that was run, such as `build`, `test`, etc. | | `is_ci` | A boolean indicating if the executor was a CI or a developer's machine. | | `status` | `0` in case of `success`, `1` in case of `failure`. | ## Cache metrics {#cache-metrics} A set of metrics related to the Tuist Cache. ### `tuist_cache_events_total` (counter) {#tuist_cache_events_total-counter} The total number of binary cache events. #### Tags {#tuist-cache-events-total-tags} | Tag | Description | | ------------ | ---------------------------------------------------------------------- | | `event_type` | Can be either of `local_hit`, `remote_hit`, or `miss`. | ### `tuist_cache_uploads_total` (counter) {#tuist_cache_uploads_total-counter} The number of uploads to the binary cache. ### `tuist_cache_uploaded_bytes` (sum) {#tuist_cache_uploaded_bytes-sum} The number of bytes uploaded to the binary cache. ### `tuist_cache_downloads_total` (counter) {#tuist_cache_downloads_total-counter} The number of downloads to the binary cache. ### `tuist_cache_downloaded_bytes` (sum) {#tuist_cache_downloaded_bytes-sum} The number of bytes downloaded from the binary cache. --- ## Previews metrics {#previews-metrics} A set of metrics related to the previews feature. ### `tuist_previews_uploads_total` (sum) {#tuist_previews_uploads_total-counter} The total number of previews uploaded. ### `tuist_previews_downloads_total` (sum) {#tuist_previews_downloads_total-counter} The total number of previews downloaded. --- ## Storage metrics {#storage-metrics} A set of metrics related to the storage of artifacts in a remote storage (e.g. s3). > [!TIP] > These metrics are useful to understand the performance of the storage operations and to identify potential bottlenecks. ### `tuist_storage_get_object_size_size_bytes` (histogram) {#tuist_storage_get_object_size_size_bytes-histogram} The size (in bytes) of an object fetched from the remote storage. #### Tags {#tuist-storage-get-object-size-size-bytes-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_size_duration_miliseconds` (histogram) {#tuist_storage_get_object_size_duration_miliseconds-histogram} The duration (in milliseconds) of fetching an object size from the remote storage. #### Tags {#tuist-storage-get-object-size-duration-miliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_size_count` (counter) {#tuist_storage_get_object_size_count-counter} The number of times an object size was fetched from the remote storage. #### Tags {#tuist-storage-get-object-size-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_delete_all_objects_duration_milliseconds` (histogram) {#tuist_storage_delete_all_objects_duration_milliseconds-histogram} The duration (in milliseconds) of deleting all objects from the remote storage. #### Tags {#tuist-storage-delete-all-objects-duration-milliseconds-tags} | Tag | Description | | -------------- | -------------------------------------------------------------------------------- | | `project_slug` | The project slug of the project whose objects are being deleted. | ### `tuist_storage_delete_all_objects_count` (counter) {#tuist_storage_delete_all_objects_count-counter} The number of times all project objects were deleted from the remote storage. #### Tags {#tuist-storage-delete-all-objects-count-tags} | Tag | Description | | -------------- | -------------------------------------------------------------------------------- | | `project_slug` | The project slug of the project whose objects are being deleted. | ### `tuist_storage_multipart_start_upload_duration_milliseconds` (histogram) {#tuist_storage_multipart_start_upload_duration_milliseconds-histogram} The duration (in milliseconds) of starting an upload to the remote storage. #### Tags {#tuist-storage-multipart-start-upload-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_multipart_start_upload_duration_count` (counter) {#tuist_storage_multipart_start_upload_duration_count-counter} The number of times an upload was started to the remote storage. #### Tags {#tuist-storage-multipart-start-upload-duration-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_as_string_duration_milliseconds` (histogram) {#tuist_storage_get_object_as_string_duration_milliseconds-histogram} The duration (in milliseconds) of fetching an object as a string from the remote storage. #### Tags {#tuist-storage-get-object-as-string-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_as_string_count` (count) {#tuist_storage_get_object_as_string_count-count} The number of times an object was fetched as a string from the remote storage. #### Tags {#tuist-storage-get-object-as-string-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_check_object_existence_duration_milliseconds` (histogram) {#tuist_storage_check_object_existence_duration_milliseconds-histogram} The duration (in milliseconds) of checking the existence of an object in the remote storage. #### Tags {#tuist-storage-check-object-existence-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_check_object_existence_count` (count) {#tuist_storage_check_object_existence_count-count} The number of times the existence of an object was checked in the remote storage. #### Tags {#tuist-storage-check-object-existence-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_generate_download_presigned_url_duration_milliseconds` (histogram) {#tuist_storage_generate_download_presigned_url_duration_milliseconds-histogram} The duration (in milliseconds) of generating a download presigned URL for an object in the remote storage. #### Tags {#tuist-storage-generate-download-presigned-url-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_generate_download_presigned_url_count` (count) {#tuist_storage_generate_download_presigned_url_count-count} The number of times a download presigned URL was generated for an object in the remote storage. #### Tags {#tuist-storage-generate-download-presigned-url-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_multipart_generate_upload_part_presigned_url_duration_milliseconds` (histogram) {#tuist_storage_multipart_generate_upload_part_presigned_url_duration_milliseconds-histogram} The duration (in milliseconds) of generating a part upload presigned URL for an object in the remote storage. #### Tags {#tuist-storage-multipart-generate-upload-part-presigned-url-duration-milliseconds-tags} | Tag | Description | | ------------- | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `part_number` | The part number of the object being uploaded. | | `upload_id` | The upload ID of the multipart upload. | ### `tuist_storage_multipart_generate_upload_part_presigned_url_count` (count) {#tuist_storage_multipart_generate_upload_part_presigned_url_count-count} The number of times a part upload presigned URL was generated for an object in the remote storage. #### Tags {#tuist-storage-multipart-generate-upload-part-presigned-url-count-tags} | Tag | Description | | ------------- | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `part_number` | The part number of the object being uploaded. | | `upload_id` | The upload ID of the multipart upload. | ### `tuist_storage_multipart_complete_upload_duration_milliseconds` (histogram) {#tuist_storage_multipart_complete_upload_duration_milliseconds-histogram} The duration (in milliseconds) of completing an upload to the remote storage. #### Tags {#tuist-storage-multipart-complete-upload-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `upload_id` | The upload ID of the multipart upload. | ### `tuist_storage_multipart_complete_upload_count` (count) {#tuist_storage_multipart_complete_upload_count-count} The total number of times an upload was completed to the remote storage. #### Tags {#tuist-storage-multipart-complete-upload-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `upload_id` | The upload ID of the multipart upload. | --- ## Projects metrics {#projects-metrics} A set of metrics related to the projects. ### `tuist_projects_total` (last_value) {#tuist_projects_total-last_value} The total number of projects. --- ## Accounts metrics {#accounts-metrics} A set of metrics related to accounts (users and organizations). ### `tuist_accounts_organizations_total` (last_value) {#tuist_accounts_organizations_total-last_value} The total number of organizations. ### `tuist_accounts_users_total` (last_value) {#tuist_accounts_users_total-last_value} The total number of users. ## Database metrics {#database-metrics} A set of metrics related to the database connection. ### `tuist_repo_pool_checkout_queue_length` (last_value) {#tuist_repo_pool_checkout_queue_length-last_value} The number of database queries that are sitting in a queue waiting to be assigned to a database connection. ### `tuist_repo_pool_ready_conn_count` (last_value) {#tuist_repo_pool_ready_conn_count-last_value} The number of database connections that are ready to be assigned to a database query. ### `tuist_repo_pool_db_connection_connected` (counter) {#tuist_repo_pool_db_connection_connected-counter} The number of connections that have been established to the database. ### `tuist_repo_pool_db_connection_disconnected` (counter) {#tuist_repo_pool_db_connection_disconnected-counter} The number of connections that have been disconnected from the database. ## HTTP metrics {#http-metrics} A set of metrics related to Tuist's interactions with other services via HTTP. ### `tuist_http_request_count` (counter) {#tuist_http_request_count-last_value} The number of outgoing HTTP requests. ### `tuist_http_request_duration_nanosecond_sum` (sum) {#tuist_http_request_duration_nanosecond_sum-last_value} The sum of the duration of the outgoing requests (including the time that they spent waiting to be assigned to a connection). ### `tuist_http_request_duration_nanosecond_bucket` (distribution) {#tuist_http_request_duration_nanosecond_bucket-distribution} The distribution of the duration of outgoing requests (including the time that they spent waiting to be assigned to a connection). ### `tuist_http_queue_count` (counter) {#tuist_http_queue_count-counter} The number of requests that have been retrieved from the pool. ### `tuist_http_queue_duration_nanoseconds_sum` (sum) {#tuist_http_queue_duration_nanoseconds_sum-sum} The time it takes to retrieve a connection from the pool. ### `tuist_http_queue_idle_time_nanoseconds_sum` (sum) {#tuist_http_queue_idle_time_nanoseconds_sum-sum} The time a connection has been idle waiting to be retrieved. ### `tuist_http_queue_duration_nanoseconds_bucket` (distribution) {#tuist_http_queue_duration_nanoseconds_bucket-distribution} The time it takes to retrieve a connection from the pool. ### `tuist_http_queue_idle_time_nanoseconds_bucket` (distribution) {#tuist_http_queue_idle_time_nanoseconds_bucket-distribution} The time a connection has been idle waiting to be retrieved. ### `tuist_http_connection_count` (counter) {#tuist_http_connection_count-counter} The number of connections that have been established. ### `tuist_http_connection_duration_nanoseconds_sum` (sum) {#tuist_http_connection_duration_nanoseconds_sum-sum} The time it takes to establish a connection against a host. ### `tuist_http_connection_duration_nanoseconds_bucket` (distribution) {#tuist_http_connection_duration_nanoseconds_bucket-distribution} The distribution of the time it takes to establish a connection against a host. ### `tuist_http_send_count` (counter) {#tuist_http_send_count-counter} The number of requests that have been sent once assigned to a connection from the pool. ### `tuist_http_send_duration_nanoseconds_sum` (sum) {#tuist_http_send_duration_nanoseconds_sum-sum} The time that it takes for requests to complete once assigned to a connection from the pool. ### `tuist_http_send_duration_nanoseconds_bucket` (distribution) {#tuist_http_send_duration_nanoseconds_bucket-distribution} The distribution of the time that it takes for requests to complete once assigned to a connection from the pool. ### `tuist_http_receive_count` (counter) {#tuist_http_receive_count-counter} The number of responses that have been received from sent requests. ### `tuist_http_receive_duration_nanoseconds_sum` (sum) {#tuist_http_receive_duration_nanoseconds_sum-sum} The time spent receiving responses. ### `tuist_http_receive_duration_nanoseconds_bucket` (distribution) {#tuist_http_receive_duration_nanoseconds_bucket-distribution} The distribution of the time spent receiving responses. ### `tuist_http_queue_available_connections` (last_value) {#tuist_http_queue_available_connections-last_value} The number of connections available in the queue. ### `tuist_http_queue_in_use_connections` (last_value) {#tuist_http_queue_in_use_connections-last_value} The number of queue connections that are in use. --- URL: "/ru/server/on-premise/install" LLMS_URL: "/ru/server/on-premise/install.md" title: "Installation" titleTemplate: ":title | On-premise | Server | Tuist" description: "Learn how to install Tuist on your infrastructure." --- # On-premise installation {#onpremise-installation} We offer a self-hosted version of the Tuist server for organizations that require more control over their infrastructure. This version allows you to host Tuist on your own infrastructure, ensuring that your data remains secure and private. > [!IMPORTANT] ENTERPRISE CUSTOMERS ONLY > The on-premise version of Tuist is available only for organizations on the Enterprise plan. If you are interested in this version, please reach out to [contact@tuist.dev](mailto:contact@tuist.dev). ## Release cadence {#release-cadence} The Tuist server is **released every Monday** and the version name follows the convention name `{MAJOR}.YY.MM.DD`. The date component is used to warn the CLI user if their hosted version is 60 days older than the release date of the CLI. It's crucial that on-premise organizations keep up with Tuist updates to ensure their developers benefit from the most recent improvements and that we can drop deprecated features with the confidence that we are not breaking any of the on-premise setups. The major component of the CLI is used to flag breaking changes in the Tuist server that will require coordination with the on-premise users. You should not expect us to use it, and in case we needed, rest asure we'll work with you in making the transition smooth. > [!NOTE] RELEASE NOTES > You'll be given access to a `tuist/registry` repository associated with the registry where images are published. Every new released will be published in that repository as a GitHub release and will contain release notes to inform you about what changes come with it. ## Runtime requirements {#runtime-requirements} This section outlines the requirements for hosting the Tuist server on your infrastructure. ### Running Docker-virtualized images {#running-dockervirtualized-images} We distribute the server as a [Docker](https://www.docker.com/) image via [GitHub’s Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry). To run it, your infrastructure must support running Docker images. Note that most infrastructure providers support it because it’s become the standard container for distributing and running software in production environments. ### Postgres database {#postgres-database} In addition to running the Docker images, you’ll need a [Postgres database](https://www.postgresql.org/) to store relational data. Most infrastructure providers include Posgres databases in their offering (e.g., [AWS](https://aws.amazon.com/rds/postgresql/) & [Google Cloud](https://cloud.google.com/sql/docs/postgres)). For performant analytics, we use a [Timescale Postgres extension](https://www.timescale.com/). You need to make sure that TimescaleDB is installed on the machine running the Postgres database. Follow the installation instructions [here](https://docs.timescale.com/self-hosted/latest/install/) to learn more. If you are unable to install the Timescale extension, you can set up your own dashboard using the Prometheus metrics. > [!INFO] MIGRATIONS > The Docker image's entrypoint automatically runs any pending schema migrations before starting the service. ### Storage {#storage} You’ll also need a solution to store files (e.g. framework and library binaries). Currently we support any storage that's S3-compliant. ## Configuration {#configuration} The configuration of the service is done at runtime through environment variables. Given the sensitive nature of these variables, we advise encrypting and storing them in secure password management solutions. Rest assured, Tuist handles these variables with utmost care, ensuring they are never displayed in logs. > [!NOTE] LAUNCH CHECKS > The necessary variables are verified at startup. If any are missing, the launch will fail and the error message will detail the absent variables. ### License configuration {#license-configuration} As an on-premise user, you'll receive a license key that you'll need to expose as an environment variable. This key is used to validate the license and ensure that the service is running within the terms of the agreement. | Environment variable | Description | Required | Default | Example | | -------------------- | -------------------------------------------------------------- | -------- | ------- | -------- | | `TUIST_LICENSE` | The license provided after signing the service level agreement | Yes | | `******` | > [!IMPORTANT] EXPIRATION DATE > Licenses have an expiration date. Users will receive a warning while using Tuist commands that interact with the server if the license expires in less than 30 days. If you are interested in renewing your license, please reach out to [contact@tuist.dev](mailto:contact@tuist.dev). ### Base environment configuration {#base-environment-configuration} | Environment variable | Description | Required | Default | Example | | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------ | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | | `TUIST_APP_URL` | The base URL to access the instance from the Internet | Yes | | https://cloud.tuist.io | | | `TUIST_SECRET_KEY_BASE` | The key to use to encrypt information (e.g. sessions in a cookie) | Yes | | | `c5786d9f869239cbddeca645575349a570ffebb332b64400c37256e1c9cb7ec831345d03dc0188edd129d09580d8cbf3ceaf17768e2048c037d9c31da5dcacfa` | | `TUIST_SECRET_KEY_PASSWORD` | Pepper to generate hashed passwords | No | `$TUIST_SECRET_KEY_BASE` | | | | `TUIST_SECRET_KEY_TOKENS` | Secret key to generate random tokens | No | `$TUIST_SECRET_KEY_BASE` | | | | `TUIST_USE_IPV6` | When `1` it configures the app to use IPv6 addresses | No | `0` | `1` | | | `TUIST_LOG_LEVEL` | The log level to use for the app | No | `info` | [Log levels](https://hexdocs.pm/logger/1.12.3/Logger.html#module-levels) | | | `TUIST_GITHUB_APP_PRIVATE_KEY` | The private key used for the GitHub app to unlock extra functionality such as posting automatic PR comments | No | `-----BEGIN RSA...` | | | | `TUIST_OPS_USER_HANDLES` | A comma-separated list of user handles that have access to the operations URLs | No | | `user1,user2` | | ### Database configuration {#database-configuration} The following environment variables are used to configure the database connection: | Environment variable | Description | Required | Default | Example | | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | ---------------------------------------------------------------------- | | `DATABASE_URL` | The URL to access the Postgres database. Note that the URL should contain the authentication information | Yes | | `postgres://username:password@cloud.us-east-2.aws.test.com/production` | | `TUIST_USE_SSL_FOR_DATABASE` | When true, it uses [SSL](https://en.wikipedia.org/wiki/Transport_Layer_Security) to connect to the database | No | `1` | `1` | | `TUIST_DATABASE_POOL_SIZE` | The number of connections to keep open in the connection pool | No | `10` | `10` | | `TUIST_DATABASE_QUEUE_TARGET` | The interval (in miliseconds) for checking if all the connections checked out from the pool took more than the queue interval [(More information)](https://hexdocs.pm/db_connection/DBConnection.html#start_link/2-queue-config) | No | `300` | `300` | | `TUIST_DATABASE_QUEUE_INTERVAL` | The threshold time (in miliseconds) in the queue that the pool uses to determine if it should start dropping new connections [(More information)](https://hexdocs.pm/db_connection/DBConnection.html#start_link/2-queue-config) | No | `1000` | `1000` | ### Authentication environment configuration {#authentication-environment-configuration} We facilitate authentication through [identity providers (IdP)](https://en.wikipedia.org/wiki/Identity_provider). To utilize this, ensure all necessary environment variables for the chosen provider are present in the server's environment. **Missing variables** will result in Tuist bypassing that provider. #### GitHub {#github} We recommend authenticating using a [GitHub App](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps) but you can also use the [OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app). Make sure to include all essential environment variables specified by GitHub in the server environment. Absent variables will cause Tuist to overlook the GitHub authentication. To properly set up the GitHub app: - In the GitHub app's general settings: - Copy the `Client ID` and set it as `TUIST_GITHUB_APP_CLIENT_ID` - Create and copy a new `client secret` and set it as `TUIST_GITHUB_APP_CLIENT_SECRET` - Set the `Callback URL` as `http://YOUR_APP_URL/users/auth/github/callback`. `YOUR_APP_URL` can also be your server's IP address. - In the `Permissions and events`'s `Account permissions` section, set the `Email addresses` permission to `Read-only`. You'll then need to expose the following environment variables in the environment where the Tuist server runs: | Environment variable | Description | Required | Default | Example | | -------------------------------- | --------------------------------------- | -------- | ------- | ------------------------------------------ | | `TUIST_GITHUB_APP_CLIENT_ID` | The client ID of the GitHub application | Yes | | `Iv1.a629723000043722` | | `TUIST_GITHUB_APP_CLIENT_SECRET` | The client secret of the application | Yes | | `232f972951033b89799b0fd24566a04d83f44ccc` | #### Google {#google} You can set up authentication with Google using [OAuth 2](https://developers.google.com/identity/protocols/oauth2). For that, you'll need to create a new credential of type OAuth client ID. When creating the credentials, select "Web Application" as application type, name it `Tuist`, and set the redirect URI to `{base_url}/users/auth/google/callback` where `base_url` is the URL your hosted-service is running at. Once you create the app, copy the client ID and secret and set them as environment variables `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` respectively. > [!NOTE] CONSENT SCREEN SCOPES > You might need to create a consent screen. When you do so, make sure to add the `userinfo.email` and `openid` scopes and mark the app as internal. #### Okta {#okta} You can enable authentication with Okta through the [OAuth 2.0](https://oauth.net/2/) protocol. You'll have to [create an app](https://developer.okta.com/docs/en/guides/implement-oauth-for-okta/main/#create-an-oauth-2-0-app-in-okta) on Okta with the following configuration: - **App integration name:** `Tuist` - **Grant type:** Enable _Authorization Code_ for _Client acting on behalf of a user_ - **Sign-in redirect URL:** `{url}/users/auth/okta/callback` where `url` is the public URL your service is accessed through. - **Assignments:** This configuration will depend on your security team requirements. Once the app is created you'll need to set the following environment variables: | Environment variable | Description | Required | Default | Example | | -------------------------- | ---------------------------------------------- | -------- | ------- | --------------------------- | | `TUIST_OKTA_SITE` | The URL of your Okta organization | Yes | | `https://your-org.okta.com` | | `TUIST_OKTA_CLIENT_ID` | The client ID to authenticate against Okta | Yes | | | | `TUIST_OKTA_CLIENT_SECRET` | The client secret to authenticate against Okta | Yes | | | ### Storage environment configuration {#storage-environment-configuration} Tuist needs storage to house artifacts uploaded through the API. It's **essential to configure one of the supported storage solutions** for Tuist to operate effectively. #### S3-compliant storages {#s3compliant-storages} You can use any S3-compliant storage provider to store artifacts. The following environment variables are required to authenticate and configure the integration with the storage provider: | Environment variable | Description | Required | Default | Example | | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | ------------------------------------------ | | `TUIST_ACCESS_KEY_ID` or `AWS_ACCESS_KEY_ID` | The access key ID to authenticate against the storage provider | Yes | | `AKIAIOSFOD` | | `TUIST_SECRET_ACCESS_KEY` or `AWS_SECRET_ACCESS_KEY` | The secret access key to authenticate against the storage provider | Yes | | `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` | | `TUIST_S3_REGION` or `AWS_REGION` | The region where the bucket is located | Yes | | `us-west-2` | | `TUIST_S3_ENDPOINT` or `AWS_ENDPOINT` | The endpoint of the storage provider | Yes | | `https://s3.us-west-2.amazonaws.com` | | `TUIST_S3_BUCKET_NAME` | The name of the bucket where the artifacts will be stored | Yes | | `tuist-artifacts` | | `TUIST_S3_REQUEST_TIMEOUT` | The timeout (in seconds) for requests to the storage provider | No | `30` | `30` | | `TUIST_S3_POOL_TIMEOUT` | The timeout (in seconds) for the connection pool to the storage provider | No | `5` | `5` | | `TUIST_S3_POOL_COUNT` | The number of pools to use for connections to the storage provider | No | `1` | `1` | | `TUIST_S3_PROTOCOL` | The protocol to use when connecting to the storage provider (`http1` or `http2`) | No | `http2` | `http2` | | `TUIST_S3_VIRTUAL_HOST` | Whether the URL should be constructed with the bucket name as a sub-domain (virtual host). | No | No | `1` | > [!NOTE] AWS authentication with Web Identity Token from environment variables > If your storage provider is AWS and you'd like to authenticate using a web identity token, you can set the environment variable `TUIST_S3_AUTHENTICATION_METHOD` to `aws_web_identity_token_from_env_vars`, and Tuist will use that method using the conventional AWS environment variables. #### Google Cloud Storage {#google-cloud-storage} For Google Cloud Storage, follow [these docs](https://cloud.google.com/storage/docs/authentication/managing-hmackeys) to get the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` pair. The `AWS_ENDPOINT` should be set to `https://storage.googleapis.com`. Other environment variables are the same as for any other S3-compliant storage. ### Git platform configuration {#git-platform-configuration} Tuist can integrate with Git platforms to provide extra features such as automatically posting comments in your pull requests. #### GitHub {#platform-github} You will need to [create a GitHub app](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps). You can reuse the one you created for authentication, unless you created an OAuth GitHub app. In the `Permissions and events`'s `Repository permissions` section, you will need to additionally set the `Pull requests` permission to `Read and write`. On top of the `TUIST_GITHUB_APP_CLIENT_ID` and `TUIST_GITHUB_APP_CLIENT_SECRET`, you will need the following environment variables: | Environment variable | Description | Required | Default | Example | | ------------------------------ | ----------------------------------------- | -------- | ------- | ------------------------------------ | | `TUIST_GITHUB_APP_PRIVATE_KEY` | The private key of the GitHub application | Yes | | `-----BEGIN RSA PRIVATE KEY-----...` | ## Deployment {#deployment} On-premise users are granted access to the repository located at [tuist/registry](https://github.com/cloud/registry) which has a linked container registry for pulling images. Currently, the container registry allows authentication only as an individual user. Therefore, users with repository access must generate a **personal access token** within the Tuist organization, ensuring they have the necessary permissions to read packages. After submission, we will promptly approve this token. > [!IMPORTANT] USER VS ORGANIZATION-SCOPED TOKENS > Using a personal access token presents a challenge because it's associated with an individual who might eventually depart from the enterprise organization. GitHub recognizes this limitation and is actively developing a solution to allow GitHub apps to authenticate with app-generated tokens. ### Pulling the Docker image {#pulling-the-docker-image} After generating the token, you can retrieve the image by executing the following command: ```bash echo $TOKEN | docker login ghcr.io -u USERNAME --password-stdin docker pull ghcr.io/tuist/tuist:latest ``` ### Deploying the Docker image {#deploying-the-docker-image} The deployment process for the Docker image will differ based on your chosen cloud provider and your organization's continuous deployment approach. Since most cloud solutions and tools, like [Kubernetes](https://kubernetes.io/), utilize Docker images as fundamental units, the examples in this section should align well with your existing setup. We recommend establishing a deployment pipeline that that runs **every Tuesday**, pulling and deploying fresh images. This ensures you consistently benefit from the latest improvements. > [!IMPORTANT] > If your deployment pipeline needs to validate that the server is up and running, you can send a `GET` HTTP request to `/ready` and assert a `200` status code in the response. #### Fly {#fly} To deploy the app on [Fly](https://fly.io/), you'll require a `fly.toml` configuration file. Consider generating it dynamically within your Continuous Deployment (CD) pipeline. Below is a reference example for your use: ```toml app = "tuist" primary_region = "fra" kill_signal = "SIGINT" kill_timeout = "5s" [experimental] auto_rollback = true [env] # Your environment configuration goes here # Or exposed through Fly secrets [processes] app = "/usr/local/bin/hivemind /app/Procfile" [[services]] protocol = "tcp" internal_port = 8080 auto_stop_machines = false auto_start_machines = false processes = ["app"] http_options = { h2_backend = true } [[services.ports]] port = 80 handlers = ["http"] force_https = true [[services.ports]] port = 443 handlers = ["tls", "http"] [services.concurrency] type = "connections" hard_limit = 100 soft_limit = 80 [[services.http_checks]] interval = 10000 grace_period = "10s" method = "get" path = "/ready" protocol = "http" timeout = 2000 tls_skip_verify = false [services.http_checks.headers] [[statics]] guest_path = "/app/public" url_prefix = "/" ``` Then you can run `fly launch --local-only --no-deploy` to launch the app. On subsequent deploys, instead of running `fly launch --local-only`, you will need to run `fly deploy --local-only`. Fly.io doesn't allow to pull private Docker images, which is why we need to use the `--local-only` flag. ### Docker Compose {#docker-compose} Below is an example of a `docker-compose.yml` file that you can use as a reference to deploy the service: ```yaml version: '3.8' services: db: image: timescale/timescaledb-ha:pg16 restart: always environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - PGDATA=/var/lib/postgresql/data/pgdata ports: - '5432:5432' volumes: - db:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 pgweb: container_name: pgweb restart: always image: sosedoff/pgweb ports: - "8081:8081" links: - db:db environment: PGWEB_DATABASE_URL: postgres://postgres:postgres@db:5432/postgres?sslmode=disable depends_on: - db tuist: image: ghcr.io/tuist/tuist:latest container_name: tuist depends_on: - db ports: - "80:80" - "8080:8080" - "443:443" expose: - "80" - "8080" - "443:443" environment: # Base Tuist Env - https://docs.tuist.io/en/guides/dashboard/on-premise/install#base-environment-configuration TUIST_USE_SSL_FOR_DATABASE: "0" TUIST_LICENSE: # ... DATABASE_URL: postgres://postgres:postgres@db:5432/postgres?sslmode=disable TUIST_APP_URL: https://localhost:8080 TUIST_SECRET_KEY_BASE: # ... WEB_CONCURRENCY: 80 # Auth - one method # GitHub Auth - https://docs.tuist.io/en/guides/dashboard/on-premise/install#github TUIST_GITHUB_OAUTH_ID: TUIST_GITHUB_APP_CLIENT_SECRET: # Okta Auth - https://docs.tuist.io/en/guides/dashboard/on-premise/install#okta TUIST_OKTA_SITE: TUIST_OKTA_CLIENT_ID: TUIST_OKTA_CLIENT_SECRET: TUIST_OKTA_AUTHORIZE_URL: # Optional TUIST_OKTA_TOKEN_URL: # Optional TUIST_OKTA_USER_INFO_URL: # Optional TUIST_OKTA_EVENT_HOOK_SECRET: # Optional # Storage AWS_ACCESS_KEY_ID: # ... AWS_SECRET_ACCESS_KEY: # ... AWS_S3_REGION: # ... AWS_ENDPOINT: # https://amazonaws.com TUIST_S3_BUCKET_NAME: # ... # Other volumes: db: driver: local ``` ## Operations {#operations} Tuist provides a set of utilities under `/ops/` that you can use to manage your instance. > [!IMPORTANT] Authorization > Only people whose handles are listed in the `TUIST_OPS_USER_HANDLES` environment variable can access the `/ops/` endpoints. - **Errors (`/ops/errors`):** You can view unexpected errors that ocurred in the application. This is useful for debugging and understanding what went wrong and we might ask you to share this information with us if you're facing issues. - **Dashboard (`/ops/dashboard`):** You can view a dashboard that provides insights into the application's performance and health (e.g. memory consumption, processes running, number of requests). This dashboard can be quite useful to understand if the hardware you're using is enough to handle the load. --- URL: "/ru/server/introduction/why-a-server" LLMS_URL: "/ru/server/introduction/why-a-server.md" title: "Why a server?" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn why Tuist has a server and how it can help scale your app development." --- # Why a server? {#why-a-server} At a certain scale, optimizing a project and developers' interactions with them require access to data that changes over time, and integrations with other internet services where teams collaborate. This is only possible with **a server that can store data in a database, process it asynchonously, and integrate it with other services.** While the role of a server is common in other ecosystems, it's not that common in app development. Teams leaned heavily on open source solutions that leveraged the capabilities of CI services to approximate the capabilities of a server. However, as the complexity of the projects and the number of developers working on them increased, the limitations of these solutions became more evident. We believe teams shouldn't have to worry about setting up and maintaining a server to scale their projects. That's why we built a server that Tuist and [Xcode projects](https://developer.apple.com/documentation/xcode/creating-an-xcode-project-for-an-app) can integrate with to scale their projects and teams. > [!TIP] GIVING YOUR PROJECTS AND WORKFLOWS SUPERPOWERS > A way of thinking about the server is as a superpower that you can give to your projects and workflows. > Some superpowers like binary caching require you to have a Tuist project but others just work with vanilla Xcode projects. --- URL: "/ru/server/introduction/integrations" LLMS_URL: "/ru/server/introduction/integrations.md" title: "Integrations" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn how to connect Tuist to other tools and services." --- # Integrations {#integrations} We strongly believe we should meet developers where they are, and let's be honest, developers spend time outside of their coding environments, such as reviewing pull request on [GitHub](https://github.com) or communicating with their team on [Slack](https://slack.com). That's why we've built integrations with popular tools and services to make it easier for you to use Tuist in your workflows. This page lists the integrations we currently support. ## Git platforms {#git-platforms} Git repositories are the centerpiece of the vast majority of software projects out there. We integrate with your Git platform to provide Tuist insights right in your pull requests or to save you some configuration such as syncing your default branch. ### GitHub {#github} Install the [Tuist GitHub app](https://github.com/marketplace/tuist). Once installed, you will need to tell Tuist the URL of your repository, such as: ```sh tuist project update tuist/tuist --repository-url https://github.com/tuist/tuist ``` --- URL: "/ru/server/introduction/authentication" LLMS_URL: "/ru/server/introduction/authentication.md" title: "Authentication" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn how to authenticate with the Tuist server from the CLI." --- # Аутентификация {#authentication} To interact with the server, the CLI needs to authenticate the requests using [bearer authentication](https://swagger.io/docs/specification/authentication/bearer-authentication/). The CLI supports authenticating as a user or as a project. ## As a user {#as-a-user} When using the CLI locally on your machine, we recommend authenticating as a user. To authenticate as a user, you need to run the following command: ```bash tuist auth login ``` The command will take you through a web-based authentication flow. Once you authenticate, the CLI will store a long-lived refresh token and a short-lived access token under `~/.config/tuist/credentials`. Each file in the directory represents the domain you authenticated against, which by default should be `cloud.tuist.io.json`. The information stored in that directory is sensitive, so **make sure to keep it safe**. The CLI will automatically look up the credentials when making requests to the server. If the access token is expired, the CLI will use the refresh token to get a new access token. ### Organization SSO {#organization-sso} If you have a Google Workspace organization and you want any developer who signs in with the same Google hosted domain to be added to your Tuist organization, you can set it up with: ```bash tuist organization update sso my-organization --provider google --organization-id my-google-domain.com ``` For on-premise customers that have Okta set up, you can get the same behavior as for Google by running: ```bash tuist organization update sso my-organization --provider okta --organization-id my-okta-domain.com ``` > [!IMPORTANT] > You must be authenticated with Google using an email tied to the organization whose domain you are setting up. ## As a project {#as-a-project} In non-interactive environments like continuous integrations', you can't authenticate through an interactive flow. For those environments, we recommend authenticating as a project by using a project-scoped token: ```bash tuist project tokens create ``` The CLI expects the token to be defined as the environment variable `TUIST_CONFIG_TOKEN`, and the `CI=1` environment variable to be set. The CLI will use the token to authenticate the requests. > [!IMPORTANT] LIMITED SCOPE > The permissions of the project-scoped token are limited to the actions that we consider safe for projects to perform from a CI environment. We plan to document the permissions that the token has in the future. --- URL: "/ru/server/introduction/accounts-and-projects" LLMS_URL: "/ru/server/introduction/accounts-and-projects.md" title: "Accounts and projects" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn how to create and manage accounts and projects in Tuist." --- # Accounts and projects {#accounts-and-projects} ## Accounts {#accounts} To use the server, you'll need an account. There are two types of accounts: - **Personal account:** Those accounts are created automaticaly when you sign up and are identified by a handle that's obtained either from the identity provider (e.g. GitHub) or the first part of the email address. - **Organization account:** Those accounts are manually created and are identified by a handle that's defined by the developer. Organizations allow inviting other members to collaborate on projects. If you are familiar with [GitHub](https://github.com), the concept is similar to theirs, where you can have personal and organization accounts, and they are identified by a _handle_ that's used when constructing URLs. > [!NOTE] CLI-FIRST > Most operations to manage accounts and projects are done through the CLI. We are working on a web interface that will make it easier to manage accounts and projects. You can manage the organization through the subcommands under `tuist organization`. To create a new organization account, run: ```bash tuist organization create {account-handle} ``` ## Projects {#projects} Your projects, either Tuist's or raw Xcode's, need to be integrated with your account through a remote project. Continuing with the comparison with GitHub, it's like having a local and a remote repository where you push your changes. You can use the `tuist project` to create and manage projects. Projects are identified by a full handle, which is the result of concatenating the organization handle and the project handle. For example, if you have an organization with the handle `tuist`, and a project with the handle `tuist`, the full handle would be `tuist/tuist`. The binding between the local and the remote project is done through the configuration file. If you don't have any, create it at `Tuist.swift` and add the following content: ```swift let tuist = Tuist(fullHandle: "{account-handle}/{project-handle}") // e.g. tuist/tuist ``` > [!IMPORTANT] TUIST PROJECT-ONLY FEATURES > Note that there are some features like binary caching that require you having a Tuist project. If you are using raw Xcode projects, you won't be able to use those features. Your project's URL is constructed by using the full handle. For example, Tuist's dashboard, which is public, is accessible at [cloud.tuist.io/tuist/tuist](https://cloud.tuist.io/tuist/tuist), where `tuist/tuist` is the project's full handle. --- URL: "/ru/references/project-description/[identifier]" LLMS_URL: "/ru/references/project-description/[identifier].md" editLink: false titleTemplate: ":title · Project Description · References · Tuist" --- --- URL: "/ru/references/migrations/from-v3-to-v4" LLMS_URL: "/ru/references/migrations/from-v3-to-v4.md" title: "From v3 to v4" titleTemplate: ":title · Migrations · References · Tuist" description: "This page documents how to migrate the Tuist CLI from the version 3 to version 4." --- # From Tuist v3 to v4 {#from-tuist-v3-to-v4} With the release of [Tuist 4](https://github.com/tuist/tuist/releases/tag/4.0.0), we took the opportunity to introduce some breaking changes to the project, which we believed would make the project easier to use and maintain in the long run. This document outlines the changes you will need to make to your project to upgrade from Tuist 3 to Tuist 4. ### Dropped version management through `tuistenv` {#dropped-version-management-through-tuistenv} Prior to Tuist 4, the installation script installed a tool, `tuistenv`, that would get renamed to `tuist` at installation time. The tool would take care of installing and activating versions of Tuist ensuring determinism across environments. With the aim of reducing the feature surface of Tuist, we decided to drop `tuistenv` in favor of [Mise](https://mise.jdx.dev/), a tool that does the same job but is more flexible and can be used across different tools. If you were using `tuistenv`, you'll have to uninstall the current version of Tuist by running `curl -Ls https://uninstall.tuist.io | bash` and then install it using the installation method of your choice. We strongly recommend the usage of Mise because it's able to install and activate versions deterministically across environments. ::: code-group ```bash [Uninstall tuistenv] curl -Ls https://uninstall.tuist.io | bash ``` ::: > [!IMPORTANT] MISE IN CI ENVIRONMENTS AND XCODE PROJECTS > If you decide to embrace the determinism that Mise brings across the board, we recommend checking out the documentation for how to use Mise in [CI environments](https://mise.jdx.dev/continuous-integration.html) and [Xcode projects](https://mise.jdx.dev/ide-integration.html#xcode). > [!NOTE] HOMEBREW IS SUPPORTED > Note that you can still install Tuist using Homebrew, which is a popular package manager for macOS. You can find the instructions on how to install Tuist using Homebrew in the installation guide. ### Dropped `init` constructors from `ProjectDescription` models {#dropped-init-constructors-from-projectdescription-models} With the aim of improving the readability and expressiveness of the APIs, we decided to remove the `init` constructors from all the `ProjectDescription` models. Every model now provides a static constructor that you can use to create instances of the models. If you were using the `init` constructors, you'll have to update your project to use the static constructors instead. > [!TIP] NAMING CONVENTION > The naming convention that we follow is to use the name of the model as the name of the static constructor. For example, the static constructor for the `Target` model is `Target.target`. ### Renamed `--no-cache` to `--no-binary-cache` {#renamed-nocache-to-nobinarycache} Because the `--no-cache` flag was ambiguous, we decided to rename it to `--no-binary-cache` to make it clear that it refers to the binary cache. If you were using the `--no-cache` flag, you'll have to update your project to use the `--no-binary-cache` flag instead. ### Renamed `tuist fetch` to `tuist install` {#renamed-tuist-fetch-to-tuist-install} We renamed the `tuist fetch` command to `tuist install` to align with the industry convention. If you were using the `tuist fetch` command, you'll have to update your project to use the `tuist install` command instead. ### [Adopt `Package.swift` as the DSL for dependencies](https://github.com/tuist/tuist/pull/5862) {#adopt-packageswift-as-the-dsl-for-dependencieshttpsgithubcomtuisttuistpull5862} Before Tuist 4, you could define dependencies in a `Dependencies.swift` file. This proprietary format broke the support in tools like [Dependabot](https://github.com/dependabot) or [Renovatebot](https://github.com/renovatebot/renovate) to automatically update dependencies. Moreover, it introduced unnecessary indirections for users. Therefore, we decided to embrace `Package.swift` as the only way to define dependencies in Tuist. If you were using the `Dependencies.swift` file, you'll have to move the content from your `Tuist/Dependencies.swift` to a `Package.swift` at the root, and use the `#if TUIST` directive to configure the integration. You can read more about how to integrate Swift Package dependencies here ### Renamed `tuist cache warm` to `tuist cache` {#renamed-tuist-cache-warm-to-tuist-cache} For brevity, we decided to rename the `tuist cache warm` command to `tuist cache`. If you were using the `tuist cache warm` command, you'll have to update your project to use the `tuist cache` command instead. ### Renamed `tuist cache print-hashes` to `tuist cache --print-hashes` {#renamed-tuist-cache-printhashes-to-tuist-cache-printhashes} We decided to rename the `tuist cache print-hashes` command to `tuist cache --print-hashes` to make it clear that it's a flag of the `tuist cache` command. If you were using the `tuist cache print-hashes` command, you'll have to update your project to use the `tuist cache --print-hashes` flag instead. ### Removed caching profiles {#removed-caching-profiles} Before Tuist 4, you could define caching profiles in `Tuist/Config.swift` which contained a configuration for the cache. We decided to remove this feature because it could lead to confusion when using it in the generation process with a profile other than the one that was used to generate the project. Moreover, it could lead to users using a debug profile to build a release version of the app, which could lead to unexpected results. In its place, we introduced the `--configuration` option, which you can use to specify the configuration you want to use when generating the project. If you were using caching profiles, you'll have to update your project to use the `--configuration` option instead. ### Removed `--skip-cache` in favor of arguments {#removed-skipcache-in-favor-of-arguments} We removed the flag `--skip-cache` from the `generate` command in favor of controlling for which targets the binary cache should be skipped by using the arguments. If you were using the `--skip-cache` flag, you'll have to update your project to use the arguments instead. ::: code-group ```bash [Before] tuist generate --skip-cache Foo ``` ```bash [After] tuist generate Foo ``` ::: ### [Dropped signing capabilities](https://github.com/tuist/tuist/pull/5716) {#dropped-signing-capabilitieshttpsgithubcomtuisttuistpull5716} Signing is already solved by community tooling like [Fastlane](https://fastlane.tools/) and Xcode itself, which do a much better job at that. We felt that signing was an stretch goal for Tuist, and that it was better to focus on the core features of the project. If you were using Tuist signing capabilities, which consisted of encrypting the certificates and profiles in the repository and installing them in the right places at generation time, you might want to replicate that logic in your own scripts that run before project generation. In particular: - A script that decrypts the certificates and profiles using a key either stored in the file-system or in an environment variable, and installs certificates in the keychain, and the provisioning profiles in the directory `~/Library/MobileDevice/Provisioning\ Profiles`. - A script that can take an existing profiles and certificates and encrypt them. > [!TIP] SIGNING REQUIREMENTS > Signing requires the right certificates to be present in the keychain and the provisioning profiles to be present in the directory `~/Library/MobileDevice/Provisioning\ Profiles`. You can use the `security` command-line tool to install certificates in the keychain and the `cp` command to copy the provisioning profiles to the right directory. ### Dropped Carthage integration via `Dependencies.swift` {#dropped-carthage-integration-via-dependenciesswift} Before Tuist 4, Carthage dependencies could be defined in a `Dependencies.swift` file, which users could then fetch by running `tuist fetch`. We also felt that this was a stretch goal for Tuist, specially considering a future where Swift Package Manager would be the preferred way to manage dependencies. If you were using Carthage dependencies, you'll have to use `Carthage` directly to pull the pre-compiled frameworks and XCFrameworks into Carthage's standard directory, and then reference those binaries from your tagets using the `TargetDependency.xcframework` and `TargetDependency.framework` cases. > [!NOTE] CARTHAGE IS STILL SUPPORTED > Some users understood that we dropped Carthage support. We didn't. The contract between Tuist and Carthage's output is to system-stored frameworks and XCFrameworks. The only thing that changed is who is responsible for fetching the dependencies. It used to be Tuist through Carthage, now it's Carthage. ### Dropped the `TargetDependency.packagePlugin` API {#dropped-the-targetdependencypackageplugin-api} Before Tuist 4, you could define a package plugin dependency using the `TargetDependency.packagePlugin` case. After seeing the Swift Package Manager introducing new package types, we decided to iterate on the API towards something that would be more flexible and future-proof. If you were using `TargetDependency.packagePlugin`, you'll have to use `TargetDependency.package` instead, and pass the type of package you want to use as an argument. ### [Dropped deprecated APIs](https://github.com/tuist/tuist/pull/5560) {#dropped-deprecated-apishttpsgithubcomtuisttuistpull5560} We removed the APIs that were marked as deprecated in Tuist 3. If you were using any of the deprecated APIs, you'll have to update your project to use the new APIs. --- URL: "/ru/references/examples/[example]" LLMS_URL: "/ru/references/examples/[example].md" editLink: false titleTemplate: ":title · Examples · References · Tuist" --- Check out example --- URL: "/ru" LLMS_URL: "/ru.md" title: "What is Tuist?" description: "Extend your Apple native tooling to better apps at scale." --- # From idea to the store We are the only **integrated extension of Apple's native toolchain to build better apps faster.**
## Installation Install Tuist and run `tuist init` to get started: ::: code-group ```bash [Homebrew] brew tap tuist/tuist brew install --formula tuist tuist init ``` ```bash [Mise] mise x tuist@latest -- tuist init ``` ::: Check out our installation guide for more details. ## Discover more Try out Tuist in minutes and learn how to get the most out of Tuist. ## Watch our latest talks Explore our team's presentations. Stay informed and gain expertise. ## Join the community See the source code, connect with others, and get connected. --- URL: "/ru/guides/tuist/about" LLMS_URL: "/ru/guides/tuist/about.md" title: "About Tuist" titleTemplate: ":title · Guides · Tuist" description: "Extend your Apple native tooling to better apps at scale." --- # About Tuist {#about-tuist} In the world of app development, particularly for platforms like Apple's, organizations often encounter **productivity roadblocks.** These can include sluggish compilation times, unreliable tests, and intricate automation workflows that drain resources. Traditionally, companies address these issues by forming dedicated platform teams. These specialists maintain codebase health and integrity, freeing other developers to focus on feature creation. However, this approach can be expensive and risky, as the departure of key team members can severely impact productivity. ## What {#what} **Tuist is a toolchain designed to accelerate and enhance app development.** We integrate seamlessly with official tools and systems, meeting developers in familiar territory. By shouldering the burden of tool and system integration, we enable teams to channel their energy into feature development and improving the overall developer experience. In essence, Tuist serves as your virtual platform team. We're with you every step of the way - from the spark of an app idea to its user launch - tackling challenges as they arise. Tuist is comprised of a [CLI](https://github.com/tuist/tuist), which is the main entry point for developers, and a server that the CLI integrates with to persist state and integrate with other publicly available services. ## Why {#why} Why choose Tuist? Here are compelling reasons: ### Simplify 🌱 {#simplify} As projects grow and span multiple platforms, modularization becomes crucial. Tuist streamlines this complexity, offering tools to optimize and better understand your project's structure. **Further reading:** Projects ### Optimize workflows 🚀 {#optimize-workflows} Leveraging project information, Tuist enhances efficiency through selective test execution and deterministic binary reuse across builds. **Further reading:** Cache, Selective testing, Registry, and Previews ### Foster healthy project evolution 📈 {#foster-healthy-project-evolution} We provide insights into your project's dynamics and expert guidance for informed decision-making. This approach prevents the frustration and productivity loss associated with unhealthy projects, which can lead to developer attrition and missed business goals. **Further reading:** Server ### Break down silos 💜 {#break-down-silos} Unlike platform-specific ecosystems (e.g., Xcode's contained environment), Tuist offers web-centric experiences and integrates seamlessly with popular tools like Slack, Prometheus, and GitHub, enhancing cross-tool collaboration. **Further reading:** Projects --- If you want to know more about Tuist, the project, and the company, you can check out our [handbook](https://handbook.tuist.io/), which contains detailed information about our vision, values, and the team behind Tuist. --- URL: "/ru/guides/share/previews" LLMS_URL: "/ru/guides/share/previews.md" title: "Previews" titleTemplate: ":title · Share · Guides · Tuist" description: "Learn how to generate and share previews of your apps with anyone." --- # Previews {#previews} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project When building an app, you may want to share it with others to get feedback. Traditionally, this is something that teams do by building, signing, and pushing their apps to platforms like Apple's [TestFlight](https://developer.apple.com/testflight/). However, this process can be cumbersome and slow, especially when you're just looking for quick feedback from a colleague or a friend. To make this process more streamlined, Tuist provides a way to generate and share previews of your apps with anyone. > [!IMPORTANT] DEVICE BUILDS NEED TO BE SIGNED > When building for device, it is currently your responsibility to ensure the app is signed correctly. We plan to streamline this in the future. :::code-group ```bash [Tuist Project] tuist build App # Build the app for the simulator tuist build App -- -destination 'generic/platform=iOS' # Build the app for the device tuist share App ``` ```bash [Xcode Project] xcodebuild -scheme App -project App.xcodeproj -configuration Debug # Build the app for the simulator xcodebuild -scheme App -project App.xcodeproj -configuration Debug -destination 'generic/platform=iOS' # Build the app for the device tuist share App --configuration Debug --platforms iOS tuist share App.ipa # Share an existing .ipa file ``` ::: The command will generate a link that you can share with anyone to run the app – either on a simulator or an actual device. All they'll need to do is to run the command below: ```bash tuist run {url} tuist run --device "My iPhone" {url} # Run the app on a specific device ``` When sharing an `.ipa` file, you can download the app directly from the mobile device using the Preview link. The links to `.ipa` previews are by default _public_. In the future, you will have an option to make them private, so that the recipient of the link would need to authenticate with their Tuist account to download the app. `tuist run` also enables you to run a latest preview based on a specifier such as `latest`, branch name, or a specific commit hash: ```bash tuist run App@latest # Runs latest App preview associated with the project's default branch tuist run App@my-feature-branch # Runs latest App preview associated with a given branch tuist run App@00dde7f56b1b8795a26b8085a781fb3715e834be # Runs latest App preview associated with a given git commit sha ``` > [!IMPORTANT] PREVIEWS' VISIBILITY > Only people with access to the organization the project belongs to can access the previews. We plan to add support for expiring links. ## Tuist macOS app {#tuist-macos-app}

Tuist

Download
To make running Tuist Previews even easier, we developed a Tuist macOS menu bar app. Instead of running Previews via the Tuist CLI, you can [download](https://tuist.dev/download) the macOS app. You can also install the app by running `brew install --cask tuist/tuist/tuist`. When you now click on "Run" in the Preview page, the macOS app will automatically launch it on your currently selected device. > [!IMPORTANT] REQUIREMENTS > To download Previews, you need to first authenticate with the `tuist auth login` command. > In the future, you will be able to authenticate directly in the app. > > Additionally, you need to have Xcode locally installed. ## Pull/merge request comments {#pullmerge-request-comments} > [!IMPORTANT] INTEGRATION WITH GIT PLATFORM REQUIRED > To get automatic pull/merge request comments, integrate your remote project with a Git platform. Testing new functionality should be a part of any code review. But having to build an app locally adds unnecessary friction, often leading to developers skipping testing functionality on their device at all. But _what if each pull request contained a link to the build that would automatically run the app on a device you selected in the Tuist macOS app?_ Once your Tuist project is connected with your Git platform such as [GitHub](https://github.com), add a `tuist share MyApp` to your CI workflow. Tuist will then post a Preview link directly in your pull requests: ![GitHub app comment with a Tuist Preview link](/images/guides/share/github-app-with-preview.png) ## README badge {#readme-badge} To make Tuist Previews more visible in your repository, you can add a badge to your `README` file that points to the latest Tuist Preview: [![Tuist Preview](https://tuist.dev/Dimillian/IcySky/previews/latest/badge.svg)](https://tuist.dev/Dimillian/IcySky/previews/latest) To add the badge to your `README`, use the following markdown and replace the account and project handles with your own: ``` [![Tuist Preview](https://tuist.dev/{account-handle}/{project-handle}/previews/latest/badge.svg)](https://tuist.dev/{account-handle}/{project-handle}/previews/latest) ``` ## Automations {#automations} You can use the `--json` flag to get a JSON output from the `tuist share` command: ``` tuist share --json ``` The JSON output is useful to create custom automations, such as posting a Slack message using your CI provider. The JSON contains a `url` key with the full preview link and a `qrCodeURL` key with the URL to the QR code image to make it easier to download previews from a real device. An example of a JSON output is below: ```json { "id": 1234567890, "url": "https://cloud.tuist.io/preview/1234567890", "qrCodeURL": "https://cloud.tuist.io/preview/1234567890/qr-code.svg" } ``` --- URL: "/ru/guides/quick-start/install-tuist" LLMS_URL: "/ru/guides/quick-start/install-tuist.md" title: "Установка Tuist" titleTemplate: ":title · Начало · Руководства · Tuist" description: "Узнайте, как установить Tuist в вашей среде." --- # Установка Tuist {#install-tuist} Tuist CLI состоит из исполняемого файла, динамических фреймворков и набора ресурсов (например, шаблонов). Хотя вы можете самостоятельно собрать Tuist из [исходников](https://github.com/tuist/tuist, **мы рекомендуем использовать один из следующих методов установки.** ### Mise {#recommended-mise} :::info Mise является рекомендуемой альтернативой [Homebrew](https://brew.sh), если вы работаете в команде или организации, которая должна обеспечить детерминированные версии инструментов в различных средах. ::: Вы можете установить Tuist с помощью любой из следующих команд: ```bash mise install tuist # Установить текущую версию, указанную в .tool-versions/.mise.toml mise install tuist@x.y.z # Установить версию с указанным номером mise install tuist@3 # Установить версию с нестрогим номером ``` Обратите внимание, что в отличие от инструментов, таких как Homebrew, устанавливающих и активирующих одну версию инструмента глобально, **Mise требует активации версии** либо глобально, либо в рамках проекта. Это делается выполнением `mise use`: ```bash mise use tuist@x.y.z # Использовать tuist версии x.y.z в текущей директории mise use tuist@latest # Использовать tuist последней версии в текущей директории mise use -g tuist@x.y.z # Использовать tuist версии x.y.z глобально mise use -g tuist@system # Использовать системный tuist глобально ``` ### Homebrew {#recommended-homebrew} Вы можете установить Tuist, используя [Homebrew](https://brew.sh) и [наши формулы](https://github.com/tuist/homebrew-tuist): ```bash brew tap tuist/tuist brew install --formula tuist brew install --formula tuist@x.y.z ``` :::tip ПОДТВЕРЖДЕНИЕ ПОДЛИННОСТИ БИНАРНЫХ ФАЙЛОВ ```bash curl -fsSL "https://docs.tuist.dev/verify.sh" | bash ``` ::: --- URL: "/ru/guides/quick-start/get-started" LLMS_URL: "/ru/guides/quick-start/get-started.md" title: "Начало работы" titleTemplate: ":title · Начало · Руководства · Tuist" description: "Узнайте, как установить Tuist в вашей среде." --- # Начало работы {#get-started} Самый простой способ начать работу с Tuist в любом каталоге или в каталоге вашего Xcode-проекта или workspace: ::: code-group ```bash [Mise] mise x tuist@latest -- tuist init ``` ```bash [Global Tuist (Homebrew)] tuist init ``` ::: Команда проведет вас по шагам для создания сгенерированного проекта или интегрирования существующего Xcode-проекта или workspace. Это поможет вам подключить вашу среду к удаленному серверу, предоставляя доступ к таким функциям, как выборочное тестирование, предварительные просмотры, и реестры. > [!NOTE] MIGRATE AN EXISTING PROJECT > If you want to migrate an existing project to generated projects to improve the developer experience and take advantage of our cache, check out our migration guide. --- URL: "/ru/guides/quick-start/gather-insights" LLMS_URL: "/ru/guides/quick-start/gather-insights.md" title: "Gather insights" titleTemplate: ":title · Начало · Руководства · Tuist" description: "Learn how to gather insights about your project." --- # Gather insights {#gather-insights} Tuist can integrate with a server to extend its capabilities. One of those capabilities is gathering insights about your project and builds. All you need is to have an account with a project in the server. First of all, you'll need to authenticate by running: ```bash tuist auth login ``` ## Создание проекта {#create-a-project} You can then create a project by running: ```bash tuist project create my-handle/MyApp # Tuist project my-handle/MyApp was successfully created 🎉 {#tuist-project-myhandlemyapp-was-successfully-created-} ``` Copy `my-handle/MyApp`, which represents the full handle of the project. ## Connect projects {#connect-projects} After creating the project on the server, you'll have to connect it to your local project. Run `tuist edit` and edit the `Tuist.swift` file to include the full handle of the project: ```swift import ProjectDescription let tuist = Tuist(fullHandle: "my-handle/MyApp") ``` Voilà! You're now ready to gather insights about your project and builds. Run `tuist test` to run the tests reporting the results to the server. > [!NOTE] > Tuist enqueues the results locally and tries to send them without blocking the command. Therefore, they might not be sent immediately after the command finishes. In CI, the results are sent immediately. ![An image that shows a list of runs in the server](/images/guides/quick-start/runs.png) Having data from your projects and builds is crucial in making informed decisions. Tuist will continue to extend its capabilities, and you'll benefit from them without having to change your project configuration. Magic, right? 🪄 --- URL: "/ru/guides/quick-start/add-dependencies" LLMS_URL: "/ru/guides/quick-start/add-dependencies.md" title: "Добавление зависимостей" titleTemplate: ":title · Начало · Руководства · Tuist" description: "Научитесь добавлять зависимости к вашему первому Swift проекту" --- # Добавление зависимостей {#add-dependencies} Часто проекты зависят от сторонних библиотек для обеспечения дополнительной функциональности. Для того чтобы улучшить опыт редактирования вашего проекта, воспользуемтесь командой: ```bash tuist edit ``` Откроется проект Xcode, содержащий файлы вашего проекта. Отредактируйте файл Package.swift и добавьте ```swift // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies .package(url: "https://github.com/onevcat/Kingfisher", .upToNextMajor(from: "7.12.0")) // [!code ++] ] ) ``` Затем отредактируйте таргет приложения в вашем проекте, чтобы объявить Kingfisher как зависимость: ```swift import ProjectDescription let project = Project( name: "MyApp", targets: [ .target( name: "MyApp", destinations: .iOS, product: .app, bundleId: "io.tuist.MyApp", infoPlist: .extendingDefault( with: [ "UILaunchStoryboardName": "LaunchScreen.storyboard", ] ), sources: ["MyApp/Sources/**"], resources: ["MyApp/Resources/**"], dependencies: [ .external(name: "Kingfisher") // [!code ++] ] ), .target( name: "MyAppTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyAppTests", infoPlist: .default, sources: ["MyApp/Tests/**"], resources: [], dependencies: [.target(name: "MyApp")] ), ] ) ``` Затем выполните команду tuist install, чтобы разрешить и загрузить зависимости с использованием [Swift Package Manager](https://www.swift.org/documentation/package-manager/). > [!NOTE] SPM в качестве средства разрешения зависимостей > Рекомендуемый подход Tuist к управлению зависимостями предполагает использование Swift Package Manager (SPM) только для разрешения зависимостей. Затем Tuist преобразует их в Xcode проекты и таргеты для максимальной настраиваемости и контроля. ## Визуализируйте проект {#visualize-the-project} Вы можете визуализировать структуру проекта, запустив команду: ```bash tuist graph ``` Команда создаст и откроет файл graph.png в директории проекта: ![Project graph](/images/guides/quick-start/graph.png) ## Использование зависимости {#use-the-dependency} Запустите tuist generate, чтобы открыть проект в Xcode, и внесите следующие изменения в файл ContentView.swift: ```swift import SwiftUI import Kingfisher // [!code ++] public struct ContentView: View { public init() {} public var body: some View { Text("Hello, World!") // [!code --] .padding() // [!code --] KFImage(URL(string: "https://cloud.tuist.io/images/tuist_logo_32x32@2x.png")!) // [!code ++] } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } ``` Запустите приложение из Xcode, и вы должны увидеть изображение, загруженное по URL. --- URL: "/ru/guides/develop/test" LLMS_URL: "/ru/guides/develop/test.md" title: "tuist test" titleTemplate: ":title · Develop · Guides · Tuist" description: "Learn how to run tests efficiently with Tuist." --- # Test {#test} Tuist provides a command, `tuist test` to generate the project if needed, and then run the tests with the the platform-specific build tool (e.g. `xcodebuild` for Apple platforms). You might wonder what's the value of using `tuist test` over generating the project with `tuist generate` and running the tests with the platform-specific build tool. - **Single command:** `tuist test` ensures the project is generated if needed before compiling the project. - **Beautified output:** Tuist enriches the output using tools like [xcbeautify](https://github.com/cpisciotta/xcbeautify) that make the output more user-friendly. - Cache: It optimizes the build by deterministically reusing the build artifacts from a remote cache. - Smart runner: It runs only the tests that need to be run, saving time and resources. - Flakiness: Prevent, detect, and fix flaky tests. ## Usage {#usage} To run the tests of a project, you can use the `tuist test` command. This command will generate the project if needed, and then run the tests using the platform-specific build tool. We support the use of the `--` terminator to forward all subsequent arguments directly to the build tool. ::: code-group ```bash [Running scheme tests] tuist test MyScheme ``` ```bash [Running all tests without binary cache] tuist test --no-binary-cache ``` ```bash [Running all tests without selective testing] tuist test --no-selective-testing ``` ::: ## Pull/merge request comments {#pullmerge-request-comments} > [!IMPORTANT] REQUIREMENTS > To get automatic pull/merge request comments, integrate your remote project with a Git platform. When running tests in your CI environments we can correlate the test results with the pull/merge request that triggered the CI build. This allows us to post a comment on the pull/merge request with the test results. ![GitHub App example](/images/contributors/scheme-arguments.png) --- URL: "/ru/guides/develop/selective-testing/xcodebuild" LLMS_URL: "/ru/guides/develop/selective-testing/xcodebuild.md" title: "xcodebuild" titleTemplate: ":title · Selective testing · Develop · Guides · Tuist" description: "Learn how to leverage selective testing with `xcodebuild`." --- # xcodebuild {#xcodebuild} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project To run tests selectively using `xcodebuild`, you can prepend your `xcodebuild` command with `tuist` – for example, `tuist xcodebuild test -scheme App`. The command hashes your project and on success, it persists the hashes to determine what has changed in future runs. In future runs `tuist xcodebuild test` transparently uses the hashes to filter down the tests to run only the ones that have changed since the last successful test run. For example, assuming the following dependency graph: - `FeatureA` has tests `FeatureATests`, and depends on `Core` - `FeatureB` has tests `FeatureBTests`, and depends on `Core` - `Core` has tests `CoreTests` `tuist xcodebuild test` will behave as such: | Action | Description | Internal state | | ---------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | `tuist xcodebuild test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The hashes of `FeatureATests`, `FeatureBTests` and `CoreTests` are persisted | | `FeatureA` is updated | The developer modifies the code of a target | Same as before | | `tuist xcodebuild test` invocation | Runs the tests in `FeatureATests` because it hash has changed | The new hash of `FeatureATests` is persisted | | `Core` is updated | The developer modifies the code of a target | Same as before | | `tuist xcodebuild test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The new hash of `FeatureATests` `FeatureBTests`, and `CoreTests` are persisted | To use `tuist xcodebuild test` on your CI, follow the instructions in the Continuous integration guide. --- URL: "/ru/guides/develop/selective-testing/generated-project" LLMS_URL: "/ru/guides/develop/selective-testing/generated-project.md" title: "Сгенерированный проект" titleTemplate: ":title · Selective testing · Develop · Guides · Tuist" description: "Learn how to leverage selective testing with a generated project." --- # Generated project {#generated-project} > [!IMPORTANT] REQUIREMENTS > > - A generated project > - A Tuist account and project To run tests selectively with your generated project, use the `tuist test` command. The command hashes your Xcode project the same way it does for warming the cache, and on success, it persists the hashes on to determine what has changed in future runs. In future runs `tuist test` transparently uses the hashes to filter down the tests to run only the ones that have changed since the last successful test run. For example, assuming the following dependency graph: - `FeatureA` has tests `FeatureATests`, and depends on `Core` - `FeatureB` has tests `FeatureBTests`, and depends on `Core` - `Core` has tests `CoreTests` `tuist test` will behave as such: | Action | Description | Internal state | | ----------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | `tuist test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The hashes of `FeatureATests`, `FeatureBTests` and `CoreTests` are persisted | | `FeatureA` is updated | The developer modifies the code of a target | Same as before | | `tuist test` invocation | Runs the tests in `FeatureATests` because it hash has changed | The new hash of `FeatureATests` is persisted | | `Core` is updated | The developer modifies the code of a target | Same as before | | `tuist test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The new hash of `FeatureATests` `FeatureBTests`, and `CoreTests` are persisted | `tuist test` integrates directly with binary caching to use as many binaries from your local or remote storage to improve the build time when running your test suite. The combination of selective testing with binary caching can dramatically reduce the time it takes to run tests on your CI. ## UI Tests {#ui-tests} Tuist supports selective testing of UI tests. However, Tuist needs to know the destination in advance. Only if you specify the `destination` parameter, Tuist will run the UI tests selectively, such as: ```sh tuist test --device 'iPhone 14 Pro' # or tuist test -- -destination 'name=iPhone 14 Pro' # or tuist test -- -destination 'id=SIMULATOR_ID' ``` --- URL: "/ru/guides/develop/selective-testing" LLMS_URL: "/ru/guides/develop/selective-testing.md" title: "Selective testing" titleTemplate: ":title · Develop · Guides · Tuist" description: "Use selective testing to run only the tests that have changed since the last successful test run." --- # Selective testing {#selective-testing} As your project grows, so does the amount of your tests. For a long time, running all tests on every PR or push to `main` takes tens of seconds. But this solution does not scale to thousands of tests your team might have. On every test run on the CI, you most likely re-run all the tests, regardless of the changes. Tuist's selective testing helps you to drastically speed up running the tests themselves by running only the tests that have changed since the last successful test run based on our hashing algorithm. Selective testing works with `xcodebuild`, which supports any Xcode project, or if you generate your projects with Tuist, you can use the `tuist test` command instead that provides some extra convenience such as integration with the binary cache. To get started with selective testing, follow the instructions based on your project setup: - xcodebuild - Generated project > [!WARNING] MODULE VS FILE-LEVEL GRANULARITY > Due to the impossibility of detecting the in-code dependencies between tests and sources, the maximum granularity of selective testing is at the target level. Therefore, we recommend keeping your targets small and focused to maximize the benefits of selective testing. ## Pull/merge request comments {#pullmerge-request-comments} > [!IMPORTANT] INTEGRATION WITH GIT PLATFORM REQUIRED > To get automatic pull/merge request comments, integrate your Tuist project with a Git platform. Once your Tuist project is connected with your Git platform such as [GitHub](https://github.com), and you start using `tuist xcodebuild test` or `tuist test` as part of your CI wortkflow, Tuist will post a comment directly in your pull/merge requests, including which tests were run and which skipped: ![GitHub app comment with a Tuist Preview link](/images/guides/develop/github-app-comment.png) --- URL: "/ru/guides/develop/registry/xcodeproj-integration" LLMS_URL: "/ru/guides/develop/registry/xcodeproj-integration.md" title: "Generated project with the XcodeProj-based package integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in a generated Xcode project with the XcodeProj-based package integration." --- # Generated project with the XcodeProj-based package integration {#generated-project-with-xcodeproj-based-integration} When using the XcodeProj-based integration, you can use the `--replace-scm-with-registry` flag to resolve dependencies from the registry if they are available. Add it to the `installOptions` in your `Tuist.swift` file: ```swift import ProjectDescription let tuist = Tuist( fullHandle: "{account-handle}/{project-handle}", project: .tuist( installOptions: .options(passthroughSwiftPackageManagerArguments: ["--replace-scm-with-registry"]) ) ) ``` If you want to ensure that the registry is used every time you resolve dependencies, you will need to update `dependencies` in your `Tuist/Package.swift` file to use the registry identifier instead of a URL. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `swift-composable-architecture` package, do the following: ```diff dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") + .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ] ``` --- URL: "/ru/guides/develop/registry/xcode-project" LLMS_URL: "/ru/guides/develop/registry/xcode-project.md" title: "Xcode project" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in an Xcode project." --- # Xcode project {#xcode-project} To add packages using the registry in your Xcode project, use the default Xcode UI. You can search for packages in the registry by clicking on the `+` button in the `Package Dependencies` tab in Xcode. If the package is available in the registry, you will see the `tuist.dev` registry in the top right: ![Adding package dependencies](/images/guides/develop/build/registry/registry-add-package.png) > [!NOTE] > Xcode currently doesn't support automatically replacing source control packages with their registry equivalents. You will need to manually remove the source control package and add the registry package to speed up the resolution. --- URL: "/ru/guides/develop/registry/swift-package" LLMS_URL: "/ru/guides/develop/registry/swift-package.md" title: "Swift package" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in a Swift package." --- # Swift package {#swift-package} If you are working on a Swift package, you can use the `--replace-scm-with-registry` flag to resolve dependencies from the registry if they are available: ```bash swift package --replace-scm-with-registry resolve ``` If you want to ensure that the registry is used every time you resolve dependencies, you will need to update `dependencies` in your `Package.swift` file to use the registry identifier instead of a URL. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `swift-composable-architecture` package, do the following: ```diff dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") + .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ] ``` --- URL: "/ru/guides/develop/registry/generated-project" LLMS_URL: "/ru/guides/develop/registry/generated-project.md" title: "Generated project with the Xcode package integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in a generated Xcode project with the Xcode package integration." --- # Generated project with the Xcode package integration {#generated-project-with-xcode-based-integration} If you are using the Xcode's default integration of packages with Tuist Projects, you need to use the registry identifier instead of a URL when adding a package: ```swift import ProjectDescription let project = Project( name: "MyProject", packages: [ // Source control resolution // .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") // Registry resolution .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ], .target( name: "App", product: .app, bundleId: "io.tuist.App", dependencies: [ .package(product: "ComposableArchitecture"), ] ) ) ``` --- URL: "/ru/guides/develop/registry/continuous-integration" LLMS_URL: "/ru/guides/develop/registry/continuous-integration.md" title: "Continuous integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in continuous integration." --- # Непрерывная интеграция (CI) {#continuous-integration-ci} To use the registry on your CI, you need to ensure that you have logged in to the registry by running `tuist registry login` as part of your workflow. > [!NOTE] ONLY XCODE INTEGRATION > Creating a new pre-unlocked keychain is required only if you are using the Xcode integration of packages. Since the registry credentials are stored in a keychain, you need to ensure the keychain can be accessed in the CI environment. Note some CI providers or automation tools like [Fastlane](https://fastlane.tools/) already create a temporary keychain or provide a built-in way how to create one. However, you can also create one by creating a custom step with the following code: ```bash TMP_DIRECTORY=$(mktemp -d) KEYCHAIN_PATH=$TMP_DIRECTORY/keychain.keychain KEYCHAIN_PASSWORD=$(uuidgen) security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security default-keychain -s $KEYCHAIN_PATH security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH ``` `tuist registry login` will then store the credentials in the default keychain. Ensure that your default keychain is created and unlocked _before_ `tuist registry login` is run. Additionally, you need to ensure the `TUIST_CONFIG_TOKEN` environment variable is set. You can create one by following the documentation here. An example workflow for GitHub Actions could then look like this: ```yaml name: Build jobs: build: steps: - # Your set up steps... - name: Create keychain run: | TMP_DIRECTORY=$(mktemp -d) KEYCHAIN_PATH=$TMP_DIRECTORY/keychain.keychain KEYCHAIN_PASSWORD=$(uuidgen) security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security default-keychain -s $KEYCHAIN_PATH security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH - name: Log in to the Tuist Registry env: TUIST_CONFIG_TOKEN: ${{ secrets.TUIST_CONFIG_TOKEN }} run: tuist registry login - # Your build steps ``` ### Incremental resolution across environments {#incremental-resolution-across-environments} Clean/cold resolutions are slightly faster with our registry, and you can experience even greater improvements if you persist the resolved dependencies across CI builds. Note that thanks to the registry, the size of the directory that you need to store and restore is much smaller than without the registry, taking significantly less time. To cache dependencies when using the default Xcode package integration, the best way is to specify a custom `-clonedSourcePackagesDirPath` when resolving dependencies via `xcodebuild`, such as: ```sh xcodebuild -resolvePackageDependencies -clonedSourcePackagesDirPath .build ``` Additionally, you will need to find a path of the `Package.resolved`. You can grab the path by running `ls **/Package.resolved`. The path should look something like `App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved`. For Swift packages and the XcodeProj-based integration, we can use the default `.build` directory located either in the root of the project or in the `Tuist` directory. Make sure the path is correct when setting up your pipeline. Here's an example workflow for GitHub Actions for resolving and caching dependencies when using the default Xcode package integration: ```yaml - name: Restore cache id: cache-restore uses: actions/cache/restore@v4 with: path: .build key: ${{ runner.os }}-${{ hashFiles('App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} restore-keys: .build - name: Resolve dependencies if: steps.cache-restore.outputs.cache-hit != 'true' run: xcodebuild -resolvePackageDependencies -clonedSourcePackagesDirPath .build - name: Save cache id: cache-save uses: actions/cache/save@v4 with: path: .build key: ${{ steps.cache-restore.outputs.cache-primary-key }} ``` --- URL: "/ru/guides/develop/registry" LLMS_URL: "/ru/guides/develop/registry.md" title: "Registry" titleTemplate: ":title · Develop · Guides · Tuist" description: "Optimize your Swift package resolution times by leveraging the Tuist Registry." --- # Registry {#registry} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project As the number of dependencies grows, so does the time to resolve them. While other package managers like [CocoaPods](https://cocoapods.org/) or [npm](https://www.npmjs.com/) are centralized, Swift Package Manager is not. Because of that, SwiftPM needs to resolve dependencies by doing a deep clone of each repository, which can be time-consuming. To address this, Tuist provides an implementation of the [Package Registry](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/PackageRegistryUsage.md), so you can download only the commit you _actually need_. The packages in the registry are based on the [Swift Package Index](https://swiftpackageindex.com/) – if you can find a package there, the package is also available in the Tuist Registry. Additionally, the packages are distributed across the globe using an edge storage for minimum latency when resolving them. ## Usage {#usage} To set up and log in to the registry, run the following command in your project's directory: ```bash tuist registry setup ``` This command generates a registry configuration files and logs you in to the registry. To ensure the rest of your team can access the registry, ensure the generated files is committed and that your team members run the following command to log in: ```bash tuist registry login ``` Now you can access the registry! To resolve dependencies from the registry instead of from source control, continue reading based on your project setup: - Xcode project - Generated project with the Xcode package integration - Generated project with the XcodeProj-based package integration - Swift package To set up the registry on the CI, follow this guide: Continuous integration. ### Package registry identifiers {#package-registry-identifiers} If you want to ensure that the registry is used every time you resolve dependencies, you will need to update `dependencies` in your `Package.swift` file to use the registry identifier instead of a URL. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `swift-composable-architecture` package, do the following: > [!NOTE] > The identifier can't contain more than one dot. If the repository name contains a dot, it's replaced with an underscore. > For example, the `https://github.com/groue/GRDB.swift` package would have the registry identifier `groue.GRDB_swift`. --- URL: "/ru/guides/develop/projects/tma-architecture" LLMS_URL: "/ru/guides/develop/projects/tma-architecture.md" title: "The Modular Architecture (TMA)" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about The Modular Architecture (TMA) and how to structure your projects using it." --- # The Modular Architecture (TMA) {#the-modular-architecture-tma} TMA is an architectural approach to structure Apple OS applications to enable scalability, optimize build and test cycles, and ensure good practices in your team. Its core idea is to build your apps by building independent features that are interconnected using clear and concise APIs. These guidelines introduce the principles of the architecture, helping you identify and organize your application features in different layers. It also introduces tips, tools, and advice if you decide to use this architecture. > [!INFO] µFEATURES > This architecture was previously known as µFeatures. We've renamed it to The Modular Architecture (TMA) to better reflect its purpose and the principles behind it. ## Core principle {#core-principle} Developers should be able to **build, test, and try** their features fast, independently of the main app, and while ensuring Xcode features like UI previews, code completion, and debugging work reliably. ## What is a module {#what-is-a-module} A module represents an application feature and is a combination of the following five targets (where target referts to an Xcode target): - **Source:** Contains the feature source code (Swift, Objective-C, C++, JavaScript...) and its resources (images, fonts, storyboards, xibs). - **Interface:** It's a companion target that contains the public interface and models of the feature. - **Tests:** Contains the feature unit and integration tests. - **Testing:** Provides testing data that can be used in tests and the example app. It also provides mocks for module classes and protocols that can be used by other features as we'll see later. - **Example:** Contains an example app that developers can use to try out the feature under certain conditions (different languages, screen sizes, settings). We recommend following a naming convention for targets, something that you can enforce in your project thanks to Tuist's DSL. | Target | Зависимости | Content | | ------------------ | --------------------------- | --------------------------- | | `Feature` | `FeatureInterface` | Source code and resources | | `FeatureInterface` | - | Public interface and models | | `FeatureTests` | `Feature`, `FeatureTesting` | Unit and integration tests | | `FeatureTesting` | `FeatureInterface` | Testing data and mocks | | `FeatureExample` | `FeatureTesting`, `Feature` | Example app | > [!TIP] UI Previews > `Feature` can use `FeatureTesting` as a Development Asset to allow for UI previews > [!IMPORTANT] COMPILER DIRECTIVES INSTEAD OF TESTING TARGETS > Alternatively, you can use compiler directives to include test data and mocks in the `Feature` or `FeatureInterface` targets when compiling for `Debug`. You simplify the graph, but you'll end up compiling code that you won't need for running the app. ## Why a module {#why-a-module} ### Clear and concise APIs {#clear-and-concise-apis} When all the app source code lives in the same target it is very easy to build implicit dependencies in code and end up with the so well-known spaghetti code. Everything is strongly coupled, the state is sometimes unpredictable, and introducing new changes become a nightmare. When we define features in independent targets we need to design public APIs as part of our feature implementation. We need to decide what should be public, how our feature should be consumed, what should remain private. We have more control over how we want our feature clients to use the feature and we can enforce good practices by designing safe APIs. ### Small modules {#small-modules} [Divide and conquer](https://en.wikipedia.org/wiki/Divide_and_conquer). Working in small modules allows you to have more focus and test and try the feature in isolation. Moreover, development cycles are much faster since we have a more selective compilation, compiling only the components that are necessary to get our feature working. The compilation of the whole app is only necessary at the very end of our work, when we need to integrate the feature into the app. ### Reusability {#reusability} Reusing code across apps and other products like extensions is encouraged using frameworks or libraries. By building modules reusing them is pretty straightforward. We can build an iMessage extension, a Today Extension, or a watchOS application by just combining existing modules and adding _(when necessary)_ platform-specific UI layers. ## Зависимости {#dependencies} When a module depends on another module, it declares a dependency against its interface target. The benefit of this is two-fold. It prevents the implementation of a module to be coupled to the implementation of another module, and it speeds up clean builds because they only have to compile the implementation of our feature, and the interfaces of direct and transitive dependencies. This approach is inspired by SwiftRock's idea of [Reducing iOS Build Times by using Interface Modules](https://swiftrocks.com/reducing-ios-build-times-by-using-interface-targets). Depending on interfaces requires apps to build the graph of implementations at runtime, and dependency-inject it into the modules that need it. Although TMA is non-opinionated about how to do this, we recommend using dependency-injection solutions or patterns or solutions that don't add built-time indirections or use platform APIs that were not designed for this purpose. ## Product types {#product-types} When building a module, you can choose between **libraries and frameworks**, and **static and dynamic linking** for the targets. Without Tuist, making this decision is a bit more complex because you need to configure the dependency graph manually. However, thanks to Tuist Projects, this is no longer a problem. We recommend using dynamic libraries or frameworks during development using bundle accessors to decouple the bundle-accessing logic from the library or framework nature of the target. This is key for fast compilation times and to ensure [SwiftUI Previews](https://developer.apple.com/documentation/swiftui/previews-in-xcode) work reliably. And static libraries or frameworks for the release builds to ensure the app boots fast. You can leverage dynamic configuration to change the product type at generation-time: ```bash # You'll have to read the value of the variable from the manifest {#youll-have-to-read-the-value-of-the-variable-from-the-manifest} # and use it to change the linking type {#and-use-it-to-change-the-linking-type} TUIST_PRODUCT_TYPE=static-library tuist generate ``` ```swift // You can place this in your manifest files or helpers // and use the returned value when instantiating targets. func productType() -> Product { if case let .string(productType) = Environment.productType { return productType == "static-library" ? .staticLibrary : .framework } else { return .framework } } ``` > [!IMPORTANT] MERGEABLE LIBRARIES > Apple attempted to alleviate the cumbersomeness of switching between static and dynamic libraries by introducing [mergeable libraries](https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries). However, that introduces build-time non-determinism that makes your build non-reproducible and harder to optimize so we don't recommend using it. ## Code {#code} TMA is non-opinionated about the code architecture and patterns for your modules. However, we'd like to share some tips based on our experience: - **Leveraging the compiler is great.** Over-leveraging the compiler might end up being non-productive and cause some Xcode features like previews to work unreliably. We recommend using the compiler to enforce good practices and catch errors early, but not to the point that it makes the code harder to read and maintain. - **Use Swift Macros sparingly.** They can be very powerful but can also make the code harder to read and maintain. - **Embrace the platform and the language, don't abstract them.** Trying to come up with ellaborated abstraction layers might end up being counterproductive. The platform and the language are powerful enough to build great apps without the need for additional abstraction layers. Use good programming and design patterns as a reference to build your features. ## Resources {#resources} - [Building µFeatures](https://speakerdeck.com/pepibumur/building-ufeatures) - [Framework Oriented Programming](https://speakerdeck.com/pepibumur/framework-oriented-programming-mobilization-dot-pl) - [A Journey into frameworks and Swift](https://speakerdeck.com/pepibumur/a-journey-into-frameworks-and-swift) - [Leveraging frameworks to speed up our development on iOS - Part 1](https://developers.soundcloud.com/blog/leveraging-frameworks-to-speed-up-our-development-on-ios-part-1) - [Library Oriented Programming](https://academy.realm.io/posts/justin-spahr-summers-library-oriented-programming/) - [Building Modern Frameworks](https://developer.apple.com/videos/play/wwdc2014/416/) - [The Unofficial Guide to xcconfig files](https://pewpewthespells.com/blog/xcconfig_guide.html) - [Static and Dynamic Libraries](https://pewpewthespells.com/blog/static_and_dynamic_libraries.html) --- URL: "/ru/guides/develop/projects/templates" LLMS_URL: "/ru/guides/develop/projects/templates.md" title: "Templates" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to create and use templates in Tuist to generate code in your projects." --- # Templates {#templates} In projects with an established architecture, developers might want to bootstrap new components or features that are consistent with the project. With `tuist scaffold` you can generate files from a template. You can define your own templates or use the ones that are vendored with Tuist. These are some scenarios where scaffolding might be useful: - Create a new feature that follows a given architecture: `tuist scaffold viper --name MyFeature`. - Create new projects: `tuist scaffold feature-project --name Home` > [!NOTE] NON-OPINIONATED > Tuist is not opinionated about the content of your templates, and what you use them for. They are only required to be in a specific directory. ## Defining a template {#defining-a-template} To define templates, you can run `tuist edit` and then create a directory called `name_of_template` under `Tuist/Templates` that represents your template. So if you are creating a template called `framework`, you should create a new directory `framework` at `Tuist/Templates` with a manifest file called `framework.swift` that could look like this: To define templates, you can run `tuist edit` and then create a directory called `name_of_template` under `Tuist/Templates` that represents your template. To define templates, you can run `tuist edit` and then create a directory called `name_of_template` under `Tuist/Templates` that represents your template. ```swift import ProjectDescription let nameAttribute: Template.Attribute = .required("name") let template = Template( description: "Custom template", attributes: [ nameAttribute, .optional("platform", default: "ios"), ], items: [ .string( path: "Project.swift", contents: "My template contents of name \(nameAttribute)" ), .file( path: "generated/Up.swift", templatePath: "generate.stencil" ), .directory( path: "destinationFolder", sourcePath: "sourceFolder" ), ] ) ``` ## Using a template {#using-a-template} After defining the template, we can use it from the `scaffold` command: ```bash tuist scaffold name_of_template --name Name --platform macos ``` > [!NOTE] > Since platform is an optional argument, we can also call the command without the `--platform macos` argument. If `.string` and `.files` don't provide enough flexibility, you can leverage the [Stencil](https://stencil.fuller.li/en/latest/) templating language via the `.file` case. Besides that, you can also use additional filters defined here. Using string interpolation, `\(nameAttribute)` above would resolve to `{{ name }}`. If you'd like to use Stencil filters in the template definition, you can use that interpolation manually and add any filters you like. For example, you might use `{ { name | lowercase } }` instead of `\(nameAttribute)` to get the lowercased value of the name attribute. You can also use `.directory` which gives the possibility to copy entire folders to a given path. > [!TIP] PROJECT DESCRIPTION HELPERS > Templates support the use of project description helpers to reuse code across templates. --- URL: "/ru/guides/develop/projects/synthesized-files" LLMS_URL: "/ru/guides/develop/projects/synthesized-files.md" title: "Synthesized files" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about synthesized files in Tuist projects." --- # Synthesized files {#synthesized-files} Tuist can generate files and code at generation-time to bring some convenience to managing and working with Xcode projects. In this page you'll learn about this functionality, and how you can use it in your projects. ## Target resources {#target-resources} Xcode projects support adding resources to targets. However, they present teams with a few challenges, specially when working with a modular project where sources and resources are often moved around: - **Inconsistent runtime access**: Where the resources end up in the final product and how you access them depends on the target product. For example, if your target represents an application, the resources are copied to the application bundle. This leads to code accessing the resources that makes assumptions on the bundle structure, which is not ideal because it makes the code harder to reason about and the resources to move around. - **Products that don't support resources**: There are certain products like static libraries that are not bundles and therefore don't support resources. Because of that, you either have to resort to a different product type, for example frameworks, that might add some overhead on your project or app. For example, static frameworks will be linked statically to the final product, and a build phase is required to only copy the resources to the final product. Or dynamic frameworks, where Xcode will copy both the binary and the resources into the final product, but it'll increase the startup time of your app because the framework needs to be loaded dynamically. - **Prone to runtime errors**: Resources are identified by their name and extension (strings). Therefore, a typo in any of those will lead to a runtime error when trying to access the resource. This is not ideal because it's not caught at compile time and might lead to crashes in release. Tuist solves the problems above by **synthesizing a unified interface to access bundles and resources** that abstracts away the implementation details. > [!IMPORTANT] RECOMMENDED > Even though accessing resources through the Tuist-synthesized interface is not mandatory, we recommend it because it makes the code easier to reason about and the resources to move around. ## Resources {#resources} Tuist provides interfaces to declare the content of files such as `Info.plist` or entitlements in Swift. This is useful to ensure consistency across targets and projects, and leverage the compiler to catch issues at compile time. You can also come up with your own abstractions to model the content and share it across targets and projects. When your project is generated, Tuist will synthesize the content of those files and write them into the `Derived` directory relative to the directory containing the project that defines them. > [!TIP] GITIGNORE THE DERIVED DIRECTORY > We recommend adding the `Derived` directory to the `.gitignore` file of your project. ## Bundle accessors {#bundle-accessors} Tuist synthesizes an interface to access the bundle that contains the target resources. ### Swift {#swift} The target will contain an extension of the `Bundle` type that exposes the bundle: ```swift let bundle = Bundle.module ``` ### Objective-C {#objectivec} In Objective-C, you'll get an interface `{Target}Resources` to access the bundle: ```objc NSBundle *bundle = [MyFeatureResources bundle]; ``` > [!TIP] SUPPORTING RESOURCES IN LIBRARIES THROUGH BUNDLES > If a target product, for example a library, doesn't support resources, Tuist will include the resources in a target of product type `bundle` ensuring that it ends up in the final product and that the interface points to the right bundle. ## Resource accessors {#resource-accessors} Resources are identified by their name and extension using strings. This is not ideal because it's not caught at compile time and might lead to crashes in release. To prevent that, Tuist integrates [SwiftGen](https://github.com/SwiftGen/SwiftGen) into the project generation process to synthesize an interface to access the resources. Thanks to that, you can confidently access the resources leveraging the compiler to catch any issues. Tuist includes [templates](https://github.com/tuist/tuist/tree/main/Sources/TuistGenerator/Templates) to synthesize accessors for the following resource types by default: | Resource type | Synthesized file | | ----------------- | ------------------------ | | Images and colors | `Assets+{Target}.swift` | | Strings | `Strings+{Target}.swift` | | Plists | `{NameOfPlist}.swift` | | Fonts | `Fonts+{Target}.swift` | | Files | `Files+{Target}.swift` | > Note: You can disable the synthesizing of resource accessors on a per-project basis by passing the `disableSynthesizedResourceAccessors` option to the project options. #### Custom templates {#custom-templates} If you want to provide your own templates to synthesize accessors to other resource types, which must be supported by [SwiftGen](https://github.com/SwiftGen/SwiftGen), you can create them at `Tuist/ResourceSynthesizers/{name}.stencil`, where the name is the camel-case version of the resource. | Resource | Template name | | ---------------- | -------------------------- | | strings | `Strings.stencil` | | assets | `Assets.stencil` | | plists | `Plists.stencil` | | fonts | `Fonts.stencil` | | coreData | `CoreData.stencil` | | interfaceBuilder | `InterfaceBuilder.stencil` | | json | `JSON.stencil` | | yaml | `YAML.stencil` | | files | `Files.stencil` | If you want to configure the list of resource types to synthesize accessors for, you can use the `Project.resourceSynthesizers` property passing the list of resource synthesizers you want to use: ```swift let project = Project(resourceSynthesizers: [.string(), .fonts()]) ``` > [!NOTE] REFERENCE > You can check out [this fixture](https://github.com/tuist/tuist/tree/main/fixtures/ios_app_with_templates) to see an example of how to use custom templates to synthesize accessors to resources. --- URL: "/ru/guides/develop/projects/plugins" LLMS_URL: "/ru/guides/develop/projects/plugins.md" title: "Plugins" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to create and use plugins in Tuist to extend its functionality." --- # Plugins {#plugins} Plugins are a tool to share and reuse Tuist artifacts across multiple projects. The following artifacts are supported: - Project description helpers across multiple projects. - Templates across multiple projects. - Tasks across multiple projects. - Resource accessor template across multiple projects Note that plugins are designed to be a simple way to extend Tuist's functionality. Therefore there are **some limitations to consider**: - A plugin cannot depend on another plugin. - A plugin cannot depend on third-party Swift packages - A plugin cannot use project description helpers from the project that uses the plugin. If you need more flexibility, consider suggesting a feature for the tool or building your own solution upon Tuist's generation framework, [`TuistGenerator`](https://github.com/tuist/tuist/tree/main/Sources/TuistGenerator). ## Plugin types {#plugin-types} ### Project description helper plugin {#project-description-helper-plugin} A project description helper plugin is represented by a directory containing a `Plugin.swift` manifest file that declares the plugin's name and a `ProjectDescriptionHelpers` directory containing the helper Swift files. ::: code-group ```bash [Plugin.swift] import ProjectDescription let plugin = Plugin(name: "MyPlugin") ``` ```bash [Directory structure] . ├── ... ├── Plugin.swift ├── ProjectDescriptionHelpers └── ... ``` ::: ### Resource accessor templates plugin {#resource-accessor-templates-plugin} If you need to share synthesized resource accessors you can use this type of plugin. The plugin is represented by a directory containing a `Plugin.swift` manifest file that declares the plugin's name and a `ResourceSynthesizers` directory containing the resource accessor template files. ::: code-group ```bash [Plugin.swift] import ProjectDescription let plugin = Plugin(name: "MyPlugin") ``` ```bash [Directory structure] . ├── ... ├── Plugin.swift ├── ResourceSynthesizers ├───── Strings.stencil ├───── Plists.stencil ├───── CustomTemplate.stencil └── ... ``` ::: The name of the template is the [camel case](https://en.wikipedia.org/wiki/Camel_case) version of the resource type: | Resource type | Template file name | | ----------------- | ---------------------------------------- | | Strings | Strings.stencil | | Assets | Assets.stencil | | Property Lists | Plists.stencil | | Fonts | Fonts.stencil | | Core Data | CoreData.stencil | | Interface Builder | InterfaceBuilder.stencil | | JSON | JSON.stencil | | YAML | YAML.stencil | When defining the resource synthesizers in the project, you can specify the plugin name to use the templates from the plugin: ```swift let project = Project(resourceSynthesizers: [.strings(plugin: "MyPlugin")]) ``` ### Task plugin {#task-plugin-badge-typewarning-textdeprecated-} > [!WARNING] DEPRECATED > Task plugins are deprecated. Check out [this blog post](https://tuist.dev/blog/2025/04/15/automation-in-swift-projects) if you are looking for an automation solution for your project. Tasks are `$PATH`-exposed executables that are invocable through the `tuist` command if they follow the naming convention `tuist-`. In earlier versions, Tuist provided some weak conventions and tools under `tuist plugin` to `build`, `run`, `test` and `archive` tasks represented by executables in Swift Packages, but we have deprecated this feature since it increases the maintenance burden and complexity of the tool. If you were using Tuist for distributing tasks, we recommend building your - You can continue using the `ProjectAutomation.xcframework` distributed with every Tuist release to have access to the project graph from your logic with `let graph = try Tuist.graph()`. The command uses sytem process to run the `tuist` command, and return the in-memory representation of the project graph. - To distribute tasks, we recommend including the a fat binary that supports the `arm64` and `x86_64` in GitHub releases, and using [Mise](https://mise.jdx.dev) as an installation tool. To instruct Mise on how to install your tool, you'll need a plugin repository. You can use [Tuist's](https://github.com/asdf-community/asdf-tuist) as a reference. - If you name your tool `tuist-{xxx}` and users can install it by running `mise install`, they can run it either invoking it directly, or through `tuist xxx`. > [!NOTE] THE FUTURE OF PROJECTAUTOMATION > We plan to consolidate the models of `ProjectAutomation` and `XcodeGraph` into a single backward-compatible framework that exposes the entirity of the project graph to the user. Moreover, we'll extract the generation logic into a new layer, `XcodeGraph` that you can also use from your own CLI. Think of it as building your own Tuist. ## Using plugins {#using-plugins} To use a plugin, you'll have to add it to your project's `Tuist.swift` manifest file: ```swift import ProjectDescription let tuist = Tuist( project: .tuist(plugins: [ .local(path: "/Plugins/MyPlugin") ]) ) ``` If you want to reuse a plugin across projects that live in different repositories, you can push your plugin to a Git repository and reference it in the `Tuist.swift` file: ```swift import ProjectDescription let tuist = Tuist( project: .tuist(plugins: [ .git(url: "https://url/to/plugin.git", tag: "1.0.0"), .git(url: "https://url/to/plugin.git", sha: "e34c5ba") ]) ) ``` After adding the plugins, `tuist install` will fetch the plugins in a global cache directory. > [!NOTE] NO VERSION RESOLUTION > As you might have noted, we don't provide version resolution for plugins. We recommend using Git tags or SHAs to ensure reproducibility. > [!TIP] PROJECT DESCRIPTION HELPERS PLUGINS > When using a project description helpers plugin, the name of the module that contains the helpers is the name of the plugin > > ```swift > import ProjectDescription > import MyTuistPlugin > let project = Project.app(name: "MyCoolApp", platform: .iOS) > ``` --- URL: "/ru/guides/develop/projects/manifests" LLMS_URL: "/ru/guides/develop/projects/manifests.md" title: "Манифесты" titleTemplate: ":title · Проекты · Разработка · Руководства · Tuist" description: "Узнайте про манифест файлы, которые Tuist использует, чтобы описать проекты и рабочие пространства и настроить процесс генерации." --- # Манифесты {#manifests} Tuist по умолчанию использует Swift файлы в качестве основного способа определения проектов и рабочих пространств, а также настройки процесса генерации. В документации эти файлы называются **манифест файлами**. Решение использовать Swift было вдохновлено менеджером пакетов [Swift Package Manager](https://www.swift.org/documentation/package-manager/), который также использует Swift файлы для описания пакетов. Благодаря использованию Swift мы можем использовать компилятор для валидации содержимого и повторного использования кода в различных манифест файлах, а также Xcode для предоставления первоклассного опыта редактирования благодаря подсветке синтаксиса, автодополнению и валидации. > [!NOTE] Кэширование > Поскольку манифест файлы представляют собой файлы Swift, которые необходимо скомпилировать, Tuist кэширует результаты компиляции, чтобы ускорить процесс анализа. Поэтому вы заметите, что при первом запуске Tuist генерация проекта может занять немного больше времени. Последующие запуски будут быстрее. ## Project.swift {#projectswift} Манифест `Project.swift` объявляет проект Xcode. Проект создается в той же директории, где находится манифест файл, с именем, указанным в параметре `name`. ```swift // Project.swift let project = Project( name: "App", targets: [ // .... ] ) ``` > [!WARNING] КОРНЕВЫЕ ПЕРЕМЕННЫЕ > Единственная переменная, которая должна находиться в корне манифеста – это `let project = Project(...)`. Если вам необходимо переиспользовать код в различных частях манифеста, вы можете воспользоваться функциями Swift. ## Workspace.swift {#workspaceswift} По умолчанию Tuist генерирует [Xcode Workspace](https://developer.apple.com/documentation/xcode/projects-and-workspaces), содержащий создаваемый проект и проекты его зависимостей. Если по какой-либо причине вы хотите настроить рабочее пространство для добавления дополнительных проектов или включения файлов и групп, вы можете сделать это, определив манифест `Workspace.swift`. ```swift // Workspace.swift import ProjectDescription let workspace = Workspace( name: "App-Workspace", projects: [ "./App", // Путь к директории, содержащий файл Project.swift ] ) ``` > [!NOTE] > Tuist создаст граф зависимостей и включит проекты зависимостей в рабочее пространство. Вам не нужно добавлять их вручную. Это необходимо для того, чтобы система сборки правильно разрешила зависимости. ### Мульти или монопроект {#multi-or-monoproject} Часто возникает вопрос, следует ли использовать один или несколько проектов в рабочем пространстве. В мире без Tuist, где настройка монопроекта приводит к частым конфликтам Git, поэтому использование рабочих пространств приветствуется. Однако, поскольку мы не рекомендуем включать проекты Xcode, созданные Tuist, в репозиторий Git, конфликты Git не являются проблемой. Поэтому решение об использовании одного или нескольких проектов в рабочем пространстве остается за вами. В проекте Tuist мы опираемся на монопроекты, поскольку время холодной генерации меньше (компилируется меньше манифест файлов), и мы используем помощников описания проекта как единицу инкапсуляции. Однако, вы можете использовать проекты Xcode в качестве единицы инкапсуляции для представления различных доменов вашего приложения, что более точно соответствует рекомендуемой структуре проекта Xcode. ## Tuist.swift {#tuistswift} Tuist предоставляет целесообразные значения по умолчанию для упрощения конфигурации проекта. Однако, вы можете настроить конфигурацию, определив `Tuist.swift` в корне проекта, который будет использоваться Tuist для определения корня проекта. ```swift import ProjectDescription let tuist = Tuist( project: .tuist(generationOptions: .options(enforceExplicitDependencies: true)) ) ``` --- URL: "/ru/guides/develop/projects/inspect/implicit-dependencies" LLMS_URL: "/ru/guides/develop/projects/inspect/implicit-dependencies.md" title: "Неявные импорты" titleTemplate: ":title · Inspect · Projects · Develop · Guides · Tuist" description: "Узнайте, как использовать Tuist для обнаружения неявных импортов." --- # Неявные импорты {#implicit-imports} Чтобы снизить сложность поддержания графа проекта Xcode, Apple спроектировала систему сборки таким образом, что зависимости могут быть определены неявно. Это означает, что продукт, например приложение, может зависеть от фреймворка, даже без явного указания этой зависимости. В небольших проектах это не вызывает проблем, но по мере увеличения сложности графа проекта неявные зависимости могут приводить к ненадежной инкрементальной сборке или проблемам в функциях редактора, таких как предварительный просмотр или автодополнение кода. Проблема в том, что невозможно полностью предотвратить возникновение неявных зависимостей. Любой разработчик может добавить оператор `import` в свой код на Swift, и будет создана неявная зависимость. И здесь на помощь приходит Tuist. Tuist предоставляет команду для проверки неявных зависимостей посредством статического анализа кода в вашем проекте. Следующая команда выведет неявные зависимости вашего проекта: ```bash tuist inspect implicit-imports ``` Если команда обнаружит какие-либо неявные импорты, она завершится с кодом выхода, отличным от нуля. > [!СОВЕТ] ВАЛИДАЦИЯ В CI > Мы настоятельно рекомендуем запускать эту команду как часть процесса непрерывной интеграции каждый раз, когда новый код отправляется в основную ветку. > [!ВАЖНО] НЕ ВСЕ НЕЯВНЫЕ СЛУЧАИ ОБНАРУЖИВАЮТСЯ > Поскольку Tuist полагается на статический анализ кода для обнаружения неявных зависимостей, он может не выявить все случаи. Например, Tuist не может обработать условные импорты, которые выполняются через директивы компилятора в коде. --- URL: "/ru/guides/develop/projects/hashing" LLMS_URL: "/ru/guides/develop/projects/hashing.md" title: "Hashing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about Tuist's hashing logic upon which features like binary caching and selective testing are built." --- # Hashing {#hashing} Features like caching or smart test execution require a way to determine whether a target has changed. Tuist calculates a hash for each target in the dependency graph to determine if a target has changed. The hash is calculated based on the following attributes: - The target's attributes (e.g., name, platform, product, etc.) - The target's files - The hash of the target's dependencies ### Cache attributes {#cache-attributes} Additionally, when calculating the hash for caching, we also hash the following attributes. #### Swift version {#swift-version} We hash the Swift version obtained from running the command `/usr/bin/xcrun swift --version` to prevent compilation errors due to Swift version mismatches between the targets and the binaries. > [!NOTE] MODULE STABILITY > Previous versions of binary caching relied on the `BUILD_LIBRARY_FOR_DISTRIBUTION` build setting to enable [module stability](https://www.swift.org/blog/library-evolution#enabling-library-evolution-support) and enable using binaries with any compiler version. However, it caused compilation issues in projects with targets that don't support module stability. Generated binaries are bound to the Swift version used to compile them, and the Swift version must match the one used to compile the project. #### Configuration {#configuration} The idea behind the flag `-configuration` was to ensure debug binaries were not used in release builds and viceversa. However, we are still missing a mechanism to remove the other configurations from the projects to prevent them from being used. ## Debugging {#debugging} If you notice non-deterministic behaviors when using the caching across environments or invocations, it might be related to differences across the environments or a bug in the hashing logic. We recommend following these steps to debug the issue: 1. Ensure the same [configuration](#configuration) and [Swift version](#swift-version) is used across environments. 2. Check if there are differences between the Xcode projects generated by two consecutive invocations of `tuist generate` or across environments. You can use the `diff` command to compare the projects. The generated projects might include **absolute paths** causing the hashing logic to be non-deterministic. > [!NOTE] BETTER DEBUGGING EXPERIENCE PLANNED > Improving our debugging experience is in our roadmap. The print-hashes command, which lacks the context to understand the differences, will be replaced by a more user-friendly command that uses a tree-like structure to show the differences between the hashes. --- URL: "/ru/guides/develop/projects/editing" LLMS_URL: "/ru/guides/develop/projects/editing.md" title: "Редактирование" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Узнайте, как использовать редактор Tuist, чтобы объявить свой проект, используя возможности системы сборки и редактора Xcode." --- # Редактирование {#editing} В отличие от традиционных проектов Xcode или пакетов Swift Packages, где изменения вносятся через интерфейс Xcode, проекты, управляемые Tuist, определяются в коде Swift, содержащемся в **манифест файлах**. Если вы знакомы с Swift Packages и файлом `Package.swift`, то подход окажется очень похож. Вы можете редактировать эти файлы с помощью любого текстового редактора, но мы рекомендуем использовать для этого редактор, предоставляемый Tuist – `tuist edit`. Редактор создает проект Xcode, содержащий все манифест файлы, и позволяет редактировать и компилировать их. Благодаря использованию Xcode, вы получаете все преимущества **дополнения кода, подсветки синтаксиса и проверки ошибок**. ## Редактирование проекта {#edit-the-project} Чтобы отредактировать проект, вы можете выполнить следующую команду в директории проекта Tuist или его поддиректории: ```bash tuist edit ``` Команда создает проект Xcode в глобальной директории и открывает его в Xcode. Проект включает в себя директорию `Manifests`, который вы можете собрать, чтобы убедиться, что все ваши манифесты верны. > [!INFO] ПОИСК МАНИФЕСТОВ ЧЕРЕЗ ШАБЛОН > `tuist edit` включает манифесты, найденные с помощью шаблона поиска `**/{Manifest}.swift` из корневой директории проекта (содержащего файл `Tuist.swift`). Убедитесь, что в корне проекта есть корректный файл `Tuist.swift`. ## Процесс редактирования и генерации {#edit-and-generate-workflow} Как вы могли заметить, редактирование невозможно выполнить из сгенерированного проекта Xcode. Это сделано специально, чтобы предотвратить зависимость созданного проекта от Tuist, гарантируя, что в будущем вы сможете без особых усилий перейти с Tuist. При итерации проекта мы рекомендуем запускать `tuist edit` из терминала, чтобы получить проект Xcode для редактирования проекта, и использовать другой сеанс в терминале для запуска `tuist generate`. --- URL: "/ru/guides/develop/projects/dynamic-configuration" LLMS_URL: "/ru/guides/develop/projects/dynamic-configuration.md" title: "Dynamic configuration" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how how to use environment variables to dynamically configure your project." --- # Dynamic configuration {#dynamic-configuration} There are certain scenarios where you might need to dynamically configure your project at generation time. For example, you might want to change the name of the app, the bundle identifier, or the deployment target based on the environment where the project is being generated. Tuist supports that via environment variables, which can be accessed from the manifest files. ## Configuration through environment variables {#configuration-through-environment-variables} Tuist allows passing configuration through environment variables that can be accessed from the manifest files. For example: ```bash TUIST_APP_NAME=MyApp tuist generate ``` If you want to pass multiple environment variables just separate them with a space. For example: ```bash TUIST_APP_NAME=MyApp TUIST_APP_LOCALE=pl tuist generate ``` ## Reading the environment variables from manifests {#reading-the-environment-variables-from-manifests} Variables can be accessed using the `Environment` type. Any variables following the convention `TUIST_XXX` defined in the environment or passed to Tuist when running commands will be accessible using the `Environment` type. The following example shows how we access the `TUIST_APP_NAME` variable: ```swift func appName() -> String { if case let .string(environmentAppName) = Environment.appName { return environmentAppName } else { return "MyApp" } } ``` Accessing variables returns an instance of type `Environment.Value?` which can take any of the following values: | Case | Description | | ----------------- | ----------------------------------------------------------- | | `.string(String)` | Used when the variable represents a string. | You can also retrieve the string or boolean `Environment` variable using either of the helper methods defined below, these methods require a default value to be passed to ensure the user gets consistent results each time. This avoids the need to define the function appName() defined above. ::: code-group ```swift [String] Environment.appName.getString(default: "TuistServer") ``` ```swift [Boolean] Environment.isCI.getBoolean(default: false) ``` ::: --- URL: "/ru/guides/develop/projects/directory-structure" LLMS_URL: "/ru/guides/develop/projects/directory-structure.md" title: "Структура директорий" titleTemplate: ":title · Проекты · Разработка · Руководства · Tuist" description: "Узнайте о структуре Tuist проектов и как их организовать." --- # Структура директорий {#directory-structure} Хотя Tuist-проекты обычно применяются для замены проектов Xcode, они не ограничиваются этим вариантом использования. Tuist-проекты также используются для создания других типов проектов, таких как SPM-пакеты, шаблоны, плагины и задачи. В этом документе описывается структура Tuist-проектов и как их организовать. В последующих разделах мы рассмотрим как задавать шаблоны, плагины и задачи. ## Стандартные Tuist-проекты {#standard-tuist-projects} Tuist-проекты - **наиболее распространенный тип проектов, созданный Туистом.** Они используются для создания приложений, фреймворков и библиотек. В отличие от проектов Xcode, Tuist-проекты заданы с помощью Swift, что делает их более гибкими и простыми для поддержки. Tuist-проекты также более декларативны, что облегчает их чтение и понимание того что они задают. Следующая структура показывает типичный Tuist-проект, который генерирует проект Xcode: ```bash Tuist.swift Tuist/ Package.swift ProjectDescriptionHelpers/ Projects/ App/ Project.swift Feature/ Project.swift Workspace.swift ``` - **Каталог Tuist:** Этот каталог имеет две цели. Во-первых, он сигнализирует **где находится корень проекта**. Это позволяет создавать пути относительно корня проекта, а также запускать команды Tuist из любого каталога проекта. Во-вторых, это контейнер для следующих файлов: - **ProjectDescriptionHelpers:** Этот каталог содержит Swift-код, доступный во всех манифест-файлах. Манифест-файлы могут использовать `import ProjectDescriptionHelpers`, чтобы использовать код, указанный в этой директории. Использование общего кода полезно для избежания дублирования и обеспечения постоянства в рамках проектов. - **Package.swift:** Этот файл содержит зависимости Swift Package для Tuist, для интеграции их в Xcode проекты и для Xcode targets (как [CocoaPods](https://cococapods)) которые настраиваемы и оптимизируемы. Узнайте больше здесь. - **Корневой каталог**: Корневой каталог проекта, который также содержит папку `Tuist`. - Tuist.swift: Этот файл содержит конфигурацию Tuist, разделяемую всеми проектами, рабочими пространствами и окружениями. Например, он может использоваться для отключения автоматической генерации схем или для определения `deployment target` проектов. - Workspace.swift: Этот манифест представляет `Xcode workspace`. Он используется для группировки проектов, а также для добавления дополнительных файлов и схем. - Project.swift: Этот манифест представляет проект Xcode. Он используется для определения `targets` Xcode, являющихся частью проекта, и их зависимостей. При взаимодействии с вышеуказанным проектом, команды ожидают найти либо `Workspace.swift` или `Project.swift` файл в рабочей папке или каталоге, указанном с помощью флага `--path`. Манифест должен быть в каталоге или подкаталоге корня проекта, который содержит директорию `Tuist`. > [!TIP] > `workspaces` Xcode позволяют разбить проекты на несколько для уменьшения вероятности конфликтов слияний. Если это то, для чего вы используете `workspace`, то он не нужен вам в Tuist. Tuist автоматически генерирует `workspace`, содержащий проекты проекта и их зависимости. ## Swift Пакеты {#swift-package-badge-typewarning-textbeta-} Tuist также поддерживает проекты SPM-пакетов. Если вы работаете над пакетом SPM, вам не нужно ничего обновлять. Tuist автоматически найдет ваш коренной `Package.swift` и все возможности Tuist будут работать так, как будто это был манифест файл `Project.swift`. Для начала запустите `tuist install` и `tuist generate` в вашем SPM-пакете. Теперь у вашего проекта должны быть те же схемы и файлы, которые вы бы увидели при обычной интеграции с Xcode SPM. В дополнение теперь вы можете запускать `tuist cache` и иметь большинство ваших SPM зависимостей и модулей предварительно скомпилированными, что делает последующие сборки чрезвычайно быстрыми. --- URL: "/ru/guides/develop/projects/dependencies" LLMS_URL: "/ru/guides/develop/projects/dependencies.md" title: "Зависимости" titleTemplate: ":title · Проекты · Разработка · Руководства · Tuist" description: "Узнайте, как объявлять зависимости в вашем Tuist проекте." --- # Зависимости {#dependencies} Когда проект растет, обычная практика – разделить проект на несколько модулей, чтобы переиспользовать код, определить границы и улучшить время сборки. Многомодульность означает определение зависимостей между ними, формируя **граф зависимостей**, который также может включать и внешние зависимости. ## XcodeProj-codified graphs {#xcodeprojcodified-graphs} Due to Xcode and XcodeProj's design, the maintenance of a dependency graph can be a tedious and error-prone task. Here are some examples of the problems that you might encounter: - Because Xcode's build system outputs all the project's products into the same directory in derived data, targets might be able to import products that they shouldn't. Compilations might fail on CI, where clean builds are more common, or later on when a different configuration is used. - The transitive dynamic dependencies of a target need to be copied into any of the directories that are part of the `LD_RUNPATH_SEARCH_PATHS` build setting. If they aren't, the target won't be able to find them at runtime. This is easy to think about and set up when the graph is small, but it becomes a problem as the graph grows. - When a target links a static [XCFramework](https://developer.apple.com/documentation/xcode/creating-a-multi-platform-binary-framework-bundle), the target needs an additional build phase for Xcode to process the bundle and extract the right binary for the current platform and architecture. This build phase is not added automatically, and it's easy to forget to add it. The above are just a few examples, but there are many more that we've encountered over the years. Imagine if you required a team of engineers to maintain a dependency graph and ensure its validity. Or even worse, that the intricacies were resolved at build-time by a closed-source build system that you can't control or customize. Sounds familiar? This is the approach that Apple took with Xcode and XcodeProj and that the Swift Package Manager has inherited. We strongly believe that the dependency graph should be **explicit** and **static** because only then can it be **validated** and **optimized**. With Tuist, you focus on describing what depends on what, and we take care of the rest. The intricacies and implementation details are abstracted away from you. In the following sections you'll learn how to declare dependencies in your project. > [!TIP] GRAPH VALIDATION > Tuist validates the graph when generating the project to ensure that there are no cycles and that all the dependencies are valid. Thanks to this, any team can take part in evolving the dependency graph without worrying about breaking it. ## Local dependencies {#local-dependencies} Targets can depend on other targets in the same and different projects, and on binaries. When instantiating a `Target`, you can pass the `dependencies` argument with any of the following options: - `Target`: Declares a dependency with a target within the same project. - `Project`: Declares a dependency with a target in a different project. - `Framework`: Declares a dependency with a binary framework. - `Library`: Declares a dependency with a binary library. - `XCFramework`: Declares a dependency with a binary XCFramework. - `SDK`: Declares a dependency with a system SDK. - `XCTest`: Declares a dependency with XCTest. > [!NOTE] DEPENDENCY CONDITIONS > Every dependency type accepts a `condition` option to conditionally link the dependency based on the platform. By default, it links the dependency for all platforms the target supports. ## External dependencies {#external-dependencies} Tuist also allows you to declare external dependencies in your project. ### Swift Packages {#swift-packages} Swift Packages are our recommended way of declaring dependencies in your project. You can integrate them using Xcode's default integration mechanism or using Tuist's XcodeProj-based integration. #### Tuist's XcodeProj-based integration {#tuists-xcodeprojbased-integration} Xcode's default integration while being the most convenient one, lacks flexibility and control that's required for medium and large projects. To overcome this, Tuist offers an XcodeProj-based integration that allows you to integrate Swift Packages in your project using XcodeProj's targets. Thanks to that, we can not only give you more control over the integration but also make it compatible with workflows like caching and smart test runs. XcodeProj's integration is more likely to take more time to support new Swift Package features or handle more package configurations. However, the mapping logic between Swift Packages and XcodeProj targets is open-source and can be contributed to by the community. This is contrary to Xcode's default integration, which is closed-source and maintained by Apple. To add external dependencies, you'll have to create a `Package.swift` either under `Tuist/` or at the root of the project. ::: code-group ```swift [Tuist/Package.swift] // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription import ProjectDescriptionHelpers let packageSettings = PackageSettings( productTypes: [ "Alamofire": .framework, // default is .staticFramework ] ) #endif let package = Package( name: "PackageName", dependencies: [ .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), ], targets: [ .binaryTarget( name: "Sentry", url: "https://github.com/getsentry/sentry-cocoa/releases/download/8.40.1/Sentry.xcframework.zip", checksum: "db928e6fdc30de1aa97200576d86d467880df710cf5eeb76af23997968d7b2c7" ), ] ) ``` ::: > [!TIP] PACKAGE SETTINGS > The `PackageSettings` instance wrapped in a compiler directive allows you to configure how packages are integrated. For example, in the example above it's used to override the default product type used for packages. By default, you shouldn't need it. The `Package.swift` file is just an interface to declare external dependencies, nothing else. That's why you don't define any targets or products in the package. Once you have the dependencies defined, you can run the following command to resolve and pull the dependencies into the `Tuist/Dependencies` directory: ```bash tuist install # Resolving and fetching dependencies. {#resolving-and-fetching-dependencies} # Installing Swift Package Manager dependencies. {#installing-swift-package-manager-dependencies} ``` As you might have noticed, we take an approach similar to [CocoaPods](https://cocoapods.org)', where the resolution of dependencies is its own command. This gives control to the users over when they'd like dependencies to be resolved and updated, and allows opening the Xcode in project and have it ready to compile. This is an area where we believe the developer experience provided by Apple's integration with the Swift Package Manager degrates over time as the project grows. From your project targets you can then reference those dependencies using the `TargetDependency.external` dependency type: ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "App", organizationName: "tuist.io", targets: [ .target( name: "App", destinations: [.iPhone], product: .app, bundleId: "io.tuist.app", deploymentTargets: .iOS("13.0"), infoPlist: .default, sources: ["Targets/App/Sources/**"], dependencies: [ .external(name: "Alamofire"), // [!code ++] ] ), ] ) ``` ::: > [!NOTE] NO SCHEMES GENERATED FOR EXTERNAL PACKAGES > The **schemes** are not automatically created for Swift Package projects to keep the schemes list clean. You can create them via Xcode's UI. #### Xcode's default integration {#xcodes-default-integration} If you want to use Xcode's default integration mechanism, you can pass the list `packages` when instantiating a project: ```swift let project = Project(name: "MyProject", packages: [ .remote(url: "https://github.com/krzyzanowskim/CryptoSwift", requirement: .exact("1.8.0")) ]) ``` And then reference them from your targets: ```swift let target = .target(name: "MyTarget", dependencies: [ .package(product: "CryptoSwift", type: .runtime) ]) ``` For Swift Macros and Build Tool Plugins, you'll need to use the types `.macro` and `.plugin` respectively. > [!WARNING] SPM Build Tool Plugins > SPM build tool plugins must be declared using [Xcode's default integration](#xcode-s-default-integration) mechanism, even when using Tuist's [XcodeProj-based integration](#tuist-s-xcodeproj-based-integration) for your project dependencies. A practical application of an SPM build tool plugin is performing code linting during Xcode's "Run Build Tool Plug-ins" build phase. In a package manifest this is defined as follows: ```swift // swift-tools-version: 5.9 import PackageDescription let package = Package( name: "Framework", products: [ .library(name: "Framework", targets: ["Framework"]), ], dependencies: [ .package(url: "https://github.com/SimplyDanny/SwiftLintPlugins", .upToNextMajor(from: "0.56.1")), ], targets: [ .target( name: "Framework", plugins: [ .plugin(name: "SwiftLint", package: "SwiftLintPlugin"), ] ), ] ) ``` To generate an Xcode project with the build tool plugin intact, you must declare the package in the project manifest's `packages` array, and then include a package with type `.plugin` in a target's dependencies. ```swift import ProjectDescription let project = Project( name: "Framework", packages: [ .remote(url: "https://github.com/SimplyDanny/SwiftLintPlugins", requirement: .upToNextMajor(from: "0.56.1")), ], targets: [ .target( name: "Framework", dependencies: [ .package(product: "SwiftLintBuildToolPlugin", type: .plugin), ] ), ] ) ``` ### Carthage {#carthage} Since [Carthage](https://github.com/carthage/carthage) outputs `frameworks` or `xcframeworks`, you can run `carthage update` to output the dependencies in the `Carthage/Build` directory and then use the `.framework` or `.xcframework` target dependency type to declare the dependency in your target. You can wrap this in a script that you can run before generating the project. ```bash #!/usr/bin/env bash carthage update tuist generate ``` > [!WARNING] BUILD AND TEST > If you build and test your project through `tuist build` and `tuist test`, you will similarly need to ensure that the Carthage-resolved dependencies are present by running the `carthage update` command before `tuist build` or `tuist test` are run. ### CocoaPods {#cocoapods} [CocoaPods](https://cocoapods.org) expects an Xcode project to integrate the dependencies. You can use Tuist to generate the project, and then run `pod install` to integrate the dependencies by creating a workspace that contains your project and the Pods dependencies. You can wrap this in a script that you can run before generating the project. ```bash #!/usr/bin/env bash tuist generate pod install ``` > [!WARNING] > CocoaPods dependencies are not compatible with workflows like `build` or `test` that run `xcodebuild` right after generating the project. They are also incompatible with binary caching and selective testing since the fingerprinting logic doesn't account for the Pods dependencies. ## Static or dynamic {#static-or-dynamic} Frameworks and libraries can be linked either statically or dynamically, **a choice that has significant implications for aspects like app size and boot time**. Despite its importance, this decision is often made without much consideration. The **general rule of thumb** is that you want as many things as possible to be statically linked in release builds to achieve fast boot times, and as many things as possible to be dynamically linked in debug builds to achieve fast iteration times. The challenge with changing between static and dynamic linking in a project graph is that is not trivial in Xcode because a change has cascading effect on the entire graph (e.g. libraries can't contain resources, static frameworks don't need to be embedded). Apple tried to solve the problem with compile time solutions like Swift Package Manager's automatic decision between static and dynamic linking, or [Mergeable Libraries](https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries). However, this adds new dynamic variables to the compilation graph, adding new sources of non-determinism, and potentially causing some features like Swift Previews that rely on the compilation graph to become unreliable. Luckily, Tuist conceptually compresses the complexity associated with changing between static and dynamic and synthesizes bundle accessors that are standard across linking types. In combination with dynamic configurations via environment variables, you can pass the linking type at invocation time, and use the value in your manifests to set the product type of your targets. ```swift // Use the value returned by this function to set the product type of your targets. func productType() -> Product { if case let .string(linking) = Environment.linking { return linking == "static" ? .staticFramework : .framework } else { return .framework } } ``` Note that Tuist does not default to convenience through implicit configuration due to its costs. What this means is that we rely on you setting the linking type and any additional build settings that are sometimes required, like the [`-ObjC` linker flag](https://github.com/pointfreeco/swift-composable-architecture/discussions/1657#discussioncomment-4119184), to ensure the resulting binaries are correct. Therefore, the stance that we take is providing you with the resources, usually in the shape of documentation, to make the right decisions. > [!TIP] EXAMPLE: COMPOSABLE ARCHITECTURE > A Swift Package that many projects integrate is [Composable Architecture](https://github.com/pointfreeco/swift-composable-architecture). As described [here](https://github.com/pointfreeco/swift-composable-architecture/discussions/1657#discussioncomment-4119184) and the [troubleshooting section](#troubleshooting), you'll need to set the `OTHER_LDFLAGS` build setting to `$(inherited) -ObjC` when linking the packages statically, which is Tuist's default linking type. Alternatively, you can override the product type for the package to be dynamic. ### Scenarios {#scenarios} There are some scenarios where setting the linking entirely to static or dynamic is not feasible or a good idea. The following is a non-exhaustive list of scenarios where you might need to mix static and dynamic linking: - **Apps with extensions:** Since apps and their extensions need to share code, you might need to make those targets dynamic. Otherwise, you'll end up with the same code duplicated in both the app and the extension, causing the binary size to increase. - **Pre-compiled external dependencies:** Sometimes you are provided with pre-compiled binaries that are either static or dynamic. Static binaries can be wrapped in dynamic frameworks or libraries to be linked dynamically. When making changes to the graph, Tuist will analyze it and display a warning if it detects a "static side effect". This warning is meant to help you identify issues that might arise from linking a target statically that depends transitively on a static target through dynamic targets. These side effects often manifest as increased binary size or, in the worst cases, runtime crashes. ## Troubleshooting {#troubleshooting} ### Objective-C Dependencies {#objectivec-dependencies} When integrating Objective-C dependencies, the inclusion of certain flags on the consuming target may be necessary to avoid runtime crashes as detailed in [Apple Technical Q&A QA1490](https://developer.apple.com/library/archive/qa/qa1490/_index.html). Since the build system and Tuist have no way of inferring whether the flag is necessary or not, and since the flag comes with potentially undesirable side effects, Tuist will not automatically apply any of these flags, and because Swift Package Manager considers `-ObjC` to be included via an `.unsafeFlag` most packages cannot include it as part of their default linking settings when required. Consumers of Objective-C dependencies (or internal Objective-C targets) should apply `-ObjC` or `-force_load` flags when required by setting `OTHER_LDFLAGS` on consuming targets. ### Firebase & Other Google Libraries {#firebase-other-google-libraries} Google's open source libraries — while powerful — can be difficult to integrate within Tuist as they often use non-standard architecture and techniques in how they are built. Here are a few tips that may be necessary to follow to integrate Firebase and Google's other Apple-platform libraries: #### Ensure `-ObjC` is added to `OTHER_LDFLAGS` {#ensure-objc-is-added-to-other_ldflags} Many of Google's libraries are written in Objective-C. Because of this, any consuming target will need to include the `-ObjC` tag in its `OTHER_LDFLAGS` build setting. This can either be set in an `.xcconfig` file or manually specified in the target's settings within your Tuist manifests. An example: ```swift Target.target( ... settings: .settings( base: ["OTHER_LDFLAGS": "$(inherited) -ObjC"] ) ... ) ``` Refer to the [Objective-C Dependencies](#objective-c-dependencies) section above for more details. #### Set the product type for `FBLPromises` to dynamic framework {#set-the-product-type-for-fblpromises-to-dynamic-framework} Certain Google libraries depend on `FBLPromises`, another of Google's libraries. You may encounter a crash that mentions `FBLPromises`, looking something like this: ``` NSInvalidArgumentException. Reason: -[FBLPromise HTTPBody]: unrecognized selector sent to instance 0x600000cb2640. ``` Explicitly setting the product type of `FBLPromises` to `.framework` in your `Package.swift` file should fix the issue: ```swift [Tuist/Package.swift] // swift-tools-version: 5.10 import PackageDescription #if TUIST import ProjectDescription import ProjectDescriptionHelpers let packageSettings = PackageSettings( productTypes: [ "FPLPromises": .framework, ] ) #endif let package = Package( ... ``` ### Transitive static dependencies leaking through `.swiftmodule` {#transitive-static-dependencies-leaking-through-swiftmodule} When a dynamic framework or library depends on static ones through `import StaticSwiftModule`, the symbols are included in the `.swiftmodule` of the dynamic framework or library, potentially [causing the compilation to fail](https://forums.swift.org/t/compiling-a-dynamic-framework-with-a-statically-linked-library-creates-dependencies-in-swiftmodule-file/22708/1). To prevent that, you'll have to import the static dependency using [`@_implementationOnly`](https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_implementationonly): ```swift @_implementationOnly import StaticModule ``` --- URL: "/ru/guides/develop/projects/cost-of-convenience" LLMS_URL: "/ru/guides/develop/projects/cost-of-convenience.md" title: "Цена удобства" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the cost of convenience in Xcode and how Tuist helps you prevent the issues that come with it." --- # Цена удобства {#the-cost-of-convenience} Designing a code editor that the spectrum **from small to large-scale projects can use** is a challenging task. Many tools approach the problem by layering their solution and providing extensibility. The bottom-most layer is very low-level and close to the underlying build system, and the top-most layer is a high-level abstraction that's convenient to use but less flexible. By doing so, they make the simple things easy, and everything else possible. However, **[Apple](https://www.apple.com) decided to take a different approach with Xcode**. The reason is unknown, but it's likely that optimizing for the challenges of large-scale projects has never been their goal. They overinvested in convenience for small projects, provided little flexibility, and strongly coupled the tools with the underlying build system. To achieve the convenience, they provide sensible defaults, which you can easily replace, and added a lot of implicit build-time-resolved behaviors that are the culprit of many issues at scale. ## Explicitness and scale {#explicitness-and-scale} When working at scale, **explicitness is key**. It allows the build system to analyze and understand the project structure and dependencies ahead of time, and perform optimizations that would be impossible otherwise. The same explicitness is also key in ensuring that editor features such as [SwiftUI previews](https://developer.apple.com/documentation/swiftui/previews-in-xcode) or [Swift Macros](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros/) work reliably and predictably. Because Xcode and Xcode projects embraced implicitness as a valid design choice to achieve convenience, a principle that the Swift Package Manager has inherited, the difficulties of using Xcode are also present in the Swift Package Manager. > [!INFO] THE ROLE OF TUIST > We could summarize Tuist's role as a tool that prevents implicitly-defined projects and leverages explicitness to provide a better developer experience (e.g. validations, optimizations). Tools like [Bazel](https://bazel.build) take it further by bringing it down to the build system level. This is an issue that's barely discussed in the community, but it's a significant one. While working on Tuist, we've noticed many organizations and developers thinking that the current challenges they face will be addressed by the [Swift Package Manager](https://www.swift.org/documentation/package-manager/), but what they don't realize is that because it's building on the same principles, even though it mitigates the so well-known Git conflicts, they degrade the developer experience in other areas and continue to make the projects non-optimizable. In the following sections, we'll discuss some real examples of how implicitness affects the developer experience and the project's health. The list is not exhaustive, but it should give you a good idea of the challenges that you might face when working with Xcode projects or Swift Packages. ## Convenience getting in your way {#convenience-getting-in-your-way} ### Shared built products directory {#shared-built-products-directory} Xcode uses a directory inside the derived data directory for each product. Inside it, it stores the build artifacts, such as the compiled binaries, the dSYM files, and the logs. Because all the products of a project go into the same directory, which is visible by default from other targets to link against, **you might end up with targets that implicitly depend on each other.** While this might not be a problem when having just a few targets, it might manifest as failing builds that are hard to debug when the project grows. The consequence of this design decision is that many projects acidentally compile with a graph that is not well-defined. > [!TIP] TUIST DETECTION OF IMPLICIT DEPENDENCIES > Tuist provides a command to detect implicit dependencies. You can use the command to validate in CI that all your dependencies are explicit. ### Find implicit dependencies in schemes {#find-implicit-dependencies-in-schemes} Defining and maintaining a dependency graph in Xcode gets harder as the project grows. It's hard because they are codified in the `.pbxproj` files as build phases and build settings, there are no tools to visualize and work with the graph, and the changes in the graph (e.g. adding a new dynamic precompiled framework), might require configuration changes upstream (e.g. adding a new build phase to copy the framework into the bundle). Apple decided at some point that instead of evolving the graph model into something more manageable, it'd make more sense to add an option to resolve implicit dependencies at build time. This is once again a questionable design choice because you might end up with slower build times or unpredictable builds. For example, a build might pass locally due to some state in derive data, which acts as a [singleton](https://en.wikipedia.org/wiki/Singleton_pattern), but then fail to compile on CI because the state is different. > [!TIP] > We recommend disabling this in your project schemes, and use like Tuist that eases the management of the dependency graph. ### SwiftUI Previews and static libraries/frameworks {#swiftui-previews-and-static-librariesframeworks} Some editor features like SwiftUI Previews or Swift Macros require the compilation of the dependency graph from the file that's being edited. This integration between the editor requires that the build system resolves any implicitness and output the right artifacts that are necessary for those features to work. As you can imagine, **the more implicit the graph is, the more challenging the task is for the build system**, and therefore it's not surprising that many of these features don't work reliably. We often hear from developers that they stopped using SwiftUI previews long time ago because they were too unreliable. Instead, they are using either example apps, or avoiding certaing things, like the usage of static libraries or script build phases, because they cause the feature to break. ### Mergeable libraries {#mergeable-libraries} Dynamic frameworks, while more flexible and easier to work with, have a negative impact in the launch time of apps. On the other side, static libraries are faster to launch, but impact the compilation time and are a bit harder to work with, specially in complex graph scenarios. _Wouldn't it be great if you could change between one or the other depending on the configuration?_ That's what Apple must have thought when they decided to work on mergeable libraries. But once again, they moved more build-time inference to the build-time. If reasoning about a dependency graph, imagine having to do so when the static or dynamic nature of the target will be resolved at build-time based on some build settings in some targets. Good luck making that work reliably while ensuring features like SwiftUI previews don't break. **Many users come to Tuist wanting to use mergeable libraries and our answer is always the same. You don't need to.** You can control the static or dynamic nature of your targets at generation-time leading to a project whose graph is known ahead of compilation. No variables need to be resolved at build-time. ```bash # The value of TUIST_DYNAMIC can be read from the project {#the-value-of-tuist_dynamic-can-be-read-from-the-project} # to set the product as static or dynamic based on the value. {#to-set-the-product-as-static-or-dynamic-based-on-the-value} TUIST_DYNAMIC=1 tuist generate ``` ## Explicit, explicit, and explicit {#explicit-explicit-and-explicit} If there's an important non-written principle that we recommend every developer or organization that wants their development with Xcode to scale, is that they should embrace explicitness. And if explicitness is hard to manage with raw Xcode projects, they should consider something else, either [Tuist](https://tuist.io) or [Bazel](https://bazel.build). **Only then reliability, predicability, and optimizations will be possible.** ## Future {#future} Whether Apple will do something to prevent all the above issues is unknown. Their continuous decisions embedded into Xcode and the Swift Package Manager don't suggest that they will. Once you allow implicit configuration as a valid state, **it's hard to move from there without introducing breaking changes.** Going back to first principles and rethinking the design of the tools might lead to breaking many Xcode projects that accidentally compiled for years. Imagine the community uproar if that happened. Apple finds itself in a bit of a chicken-and-egg problem. Convenience is what helps developers get started quickly and build more apps for their ecosystem. But their decisions to make the experience convenience at that scale, is making it hard for them to ensure some of the Xcode features work reliably. Because the future is unknown, we try to **be as close as possible to the industry standards and Xcode projects**. We prevent the above issues, and leverage the knowledge that we have to provide a better developer experience. Ideally we wouldn't have to resort to project generation for that, but the lack of extensibility of Xcode and the Swift Package Manager make it the only viable option. And it's also a safe option because they'll have to break the Xcode projects to break Tuist projects. Ideally, **the build system was more extensible**, but wouldn't it be a bad idea to have plugins/extensions that contract with a world of implicitness? It doesn't seem like a good idea. So it seems like we'll need external tools like Tuist or [Bazel](https://bazel.build) to provide a better developer experience. Or maybe Apple will surprise us all and make Xcode more extensible and explicit... Until that happens, you have to choose whether you want to embrace the convencience of Xcode and take on the debt that comes with it, or trust us on this journey to provide a better developer experience. We won't disappoint you. --- URL: "/ru/guides/develop/projects/code-sharing" LLMS_URL: "/ru/guides/develop/projects/code-sharing.md" title: "Code sharing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to share code across manifest files to reduce duplications and ensure consistency" --- # Code sharing {#code-sharing} One of the inconveniences of Xcode when we use it with large projects is that it doesn't allow reusing elements of the projects other than the build settings through `.xcconfig` files. Being able to reuse project definitions is useful for the following reasons: - It eases the **maintenance** because changes can be applied in one place and all the projects get the changes automatically. - It makes it possible to define **conventions** that new projects can conform to. - Projects are more **consistent** and therefore the likelihood of broken builds due inconsistencies is significantly less. - Adding a new projects becomes an easy task because we can reuse the existing logic. Reusing code across manifest files is possible in Tuist thanks to the concept of **project description helpers**. > [!TIP] A TUIST UNIQUE ASSET > Many organizations like Tuist because they see in project description helpers a platform for platform teams to codify their own conventions and come up with their own language for describing their projects. For example, YAML-based project generators have to come up with their own YAML-based propietary templating solution, or force organizations onto building their tools upon. ## Project description helpers {#project-description-helpers} Project description helpers are Swift files that get compiled into a module, `ProjectDescriptionHelpers`, that manifest files can import. The module is compiled by gathering all the files in the `Tuist/ProjectDescriptionHelpers` directory. You can import them into your manifest file by adding an import statement at the top of the file: ```swift // Project.swift import ProjectDescription import ProjectDescriptionHelpers ``` `ProjectDescriptionHelpers` are available in the following manifests: - `Project.swift` - `Package.swift` (only behind the `#TUIST` compiler flag) - `Workspace.swift` ## Example {#example} The snippets below contain an example of how we extend the `Project` model to add static constructors and how we use them from a `Project.swift` file: ::: code-group ```swift [Tuist/Project+Templates.swift] import ProjectDescription extension Project { public static func featureFramework(name: String, dependencies: [TargetDependency] = []) -> Project { return Project( name: name, targets: [ .target( name: name, destinations: .iOS, product: .framework, bundleId: "io.tuist.\(name)", infoPlist: "\(name).plist", sources: ["Sources/\(name)/**"], resources: ["Resources/\(name)/**",], dependencies: dependencies ), .target( name: "\(name)Tests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.\(name)Tests", infoPlist: "\(name)Tests.plist", sources: ["Sources/\(name)Tests/**"], resources: ["Resources/\(name)Tests/**",], dependencies: [.target(name: name)] ) ] ) } } ``` ```swift {2} [Project.swift] import ProjectDescription import ProjectDescriptionHelpers let project = Project.featureFramework(name: "MyFeature") ``` ::: > [!TIP] A TOOL TO ESTABLISH CONVENTIONS > Note how through the function we are defining conventions about the name of the targets, the bundle identifier, and the folders structure. --- URL: "/ru/guides/develop/projects/best-practices" LLMS_URL: "/ru/guides/develop/projects/best-practices.md" title: "Лучшие практики" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Узнайте о лучших практиках работы с Tuist и проектами Xcode." --- # Лучшие практики На протяжении многих лет работы с различными командами и проектами мы выявили набор лучших практик, которые мы рекомендуем соблюдать при работе с Tuist и проектами Xcode. Эти практики не являются обязательными, но они могут помочь вам структурировать ваши проекты таким образом, чтобы их было проще поддерживать и масштабировать. ## Xcode {#xcode} ### Нерекомендуемые паттерны {#discouraged-patterns} #### Конфигурации для моделирования удаленных сред {#configurations-to-model-remote-environments} Многие организации используют конфигурации сборки для моделирования различных удаленных сред (например, `Debug-Production` или `Release-Canary`), но у этого подхода есть некоторые недостатки: - **Несоответствия:** Если в графе сборки имеются конфигурационные несоответствия, система сборки может в итоге использовать неправильную конфигурацию для некоторых тергитов. - **Сложность:** Проекты могут иметь большой список локальных конфигураций и удаленных сред, которые сложно анализировать и поддерживать. Конфигурации сборки изначально предназначены для представления различных настроек сборки, и проектам редко требуется больше, чем просто `Debug` и `Release`. Необходимость моделировать различные среды может быть удовлетворена с помощью схем: - **В Debug сборках:** Вы можете включить все конфигурации, доступные при разработке в приложение (например, конечные точки), и переключить их во время выполнения. Переключение может происходить при помощи переменных окружения в схемах или в пользовательского интерфейсе приложения. - **В Release сборках:** Вы можете включить только конфигурацию, к которой привязана release-сборка, и не включать логику переключения конфигураций с помощью директив компилятора. --- URL: "/ru/guides/develop/projects/adoption/swift-package" LLMS_URL: "/ru/guides/develop/projects/adoption/swift-package.md" title: "Использование Tuist с Swift Package" titleTemplate: ":title · Adoption · Projects · Develop · Guides · Tuist" description: "Узнайте, как использовать Tuist с Swift Package." --- # Использование Tuist с Swift Package {#using-tuist-with-a-swift-package-badge-typewarning-textbeta-} Tuist поддерживает использование `Package.swift` в качестве DSL для проектов и преобразует ваши пакетные модули в Xcode проект и модули Xcode. > [!WARNING] > Цель этой функции - предоставить разработчикам простой способ оценить влияние внедрения Tuist в их Swift пакеты. Поэтому мы не планируем поддерживать весь спектр возможностей Swift Package Manage, а также переносить в область Swift Package Manager все уникальные возможности Tuist, такие, как project description helpers. > [!NOTE] КОРНЕВОЙ КАТАЛОГ > Команды Tuist ожидают наличия определенной структуры папок, корень которой определяется папкой `Tuist` или `.git`. ## Использование Tuist с Swift Package {#using-tuist-with-a-swift-package} Мы собираемся использовать Tuist с [TootSDK пакетом](https://github.com/TootSDK/TootSDK), который содержит Swift пакет. Первое, что нам нужно сделать, это скопировать репозиторий: ```bash git clone https://github.com/TootSDK/TootSDK cd TootSDK ``` Как закончили клонировать, необходимо установить Swift Package Manager зависимости: ```bash tuist install ``` Под капотом `tuist install` использует Swift Package Manager для скачивания зависимостей. После того как установка пакетов будет выполнена, вы сможете сгенерировать проект: ```bash tuist generate ``` Вуаля! Теперь у вас есть собственный проект Xcode, который вы можете открыть и начать работать над ним. --- URL: "/ru/guides/develop/projects/adoption/new-project" LLMS_URL: "/ru/guides/develop/projects/adoption/new-project.md" title: "Создание нового проекта" titleTemplate: ":title · Adoption · Projects · Develop · Guides · Tuist" description: "Научитесь создавать новый проект с помощью Tuist." --- # Создание нового проекта {#create-a-new-project} Самый простой способ начала знакомства с Tuist – использовать команду `tuist init`. Эта команда запускает интерактивный интерфейс командной строки, который поможет вам настроить ваш проект. При появлении соответствующего запроса обязательно выберите опцию создания «сгенерированного проекта». Вы можете редактировать проект запустив `tuist edit` и Xcode откроет проект для редактирования. Одним из созданых файлов будет `Project.swift`, который содержит описание вашего проекта. Если вы знакомы с Swift Package Manager, то это похоже с `Package.swift`, но для настройки проектов Xcode. ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "MyApp", targets: [ .target( name: "MyApp", destinations: .iOS, product: .app, bundleId: "io.tuist.MyApp", infoPlist: .extendingDefault( with: [ "UILaunchScreen": [ "UIColorName": "", "UIImageName": "", ], ] ), sources: ["MyApp/Sources/**"], resources: ["MyApp/Resources/**"], dependencies: [] ), .target( name: "MyAppTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyAppTests", infoPlist: .default, sources: ["MyApp/Tests/**"], resources: [], dependencies: [.target(name: "MyApp")] ), ] ) ``` ::: > [!NOTE] > Мы намеренно держим список доступных шаблонов коротким, чтобы свести к минимуму затраты на их поддержку. Если вы хотите создать проект, который не является приложением, например, фреймворк, вы можете использовать `tuist init` в качестве отправной точки, а затем модифицировать созданный проект в соответствии с вашими нуждами. ## Создание проекта вручную {#manually-creating-a-project} Также, вы можете создать проект вручную. Мы рекомендуем делать это только, если вы уже знакомы с Tuist и его концепциями. Первое, что вам нужно будет сделать – создать дополнительные папки для структуры проекта: ```bash mkdir MyFramework cd MyFramework ``` Затем создайте файл `Tuist.swift`, который будет настраивать Tuist и использоваться для определения корневой папки проекта, а также файл `Project.swift`, где будет объявлен ваш проект: ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "MyFramework", targets: [ .target( name: "MyFramework", destinations: .macOS, product: .framework, bundleId: "io.tuist.MyFramework", sources: ["MyFramework/Sources/**"], dependencies: [] ) ] ) ``` ```swift [Tuist.swift] import ProjectDescription let tuist = Tuist() ``` ::: > [!IMPORTANT] > Tuist использует папку `Tuist/` для определения корня вашего проекта, а затем ищет другие файлы манифеста в папках. Мы рекомендуем создать эти файлы в вашем редакторе, и потом использовать `tuist edit` для редактирования проекта в Xcode. --- URL: "/ru/guides/develop/projects/adoption/migrate/xcodegen-project" LLMS_URL: "/ru/guides/develop/projects/adoption/migrate/xcodegen-project.md" title: "Migrate an XcodeGen project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate your projects from XcodeGen to Tuist." --- # Migrate an XcodeGen project {#migrate-an-xcodegen-project} [XcodeGen](https://github.com/yonaskolb/XcodeGen) is a project-generation tool that uses YAML as [a configuration format](https://github.com/yonaskolb/XcodeGen/blob/master/Docs/ProjectSpec.md) to define Xcode projects. Many organizations **adopted it trying to escape from the frequent Git conflicts that arise when working with Xcode projects.** However, frequent Git conflicts is just one of the many problems that organizations experience. Xcode exposes developers with a lot of intricacies and implicit configurations that make it hard to maintain and optimize projects at scale. XcodeGen falls short there by design because it's a tool that generates Xcode projects, not a project manager. If you need a tool that helps you beyond generating Xcode projects, you might want to consider Tuist. > [!TIP] SWIFT OVER YAML > Many organizations prefer Tuist as a project generation tool too because it uses Swift as a configuration format. Swift is a programming language that developers are familiar with, and that provides them with the convenience of using Xcode's autocompletion, type-checking, and validation features. What follows are some considerations and guidelines to help you migrate your projects from XcodeGen to Tuist. ## Project generation {#project-generation} Both Tuist and XcodeGen provide a `generate` command that turns your project declaration into Xcode projects and workspaces. ::: code-group ```bash [XcodeGen] xcodegen generate ``` ```bash [Tuist] tuist generate ``` ::: The difference lays in the editing experience. With Tuist, you can run the `tuist edit` command, which generates an Xcode project on the fly that you can open and start working on. This is particularly useful when you want to make quick changes to your project. ## `project.yaml` {#projectyaml} XcodeGen's `project.yaml` description file becomes `Project.swift`. Moreover, you can have `Workspace.swift` as a way to customize how projects are grouped in workspaces. You can also have a project `Project.swift` with targets that reference targets from other projects. In those cases, Tuist will generate an Xcode Workspace including all the projects. ::: code-group ```bash [XcodeGen directory structure] / project.yaml ``` ```bash [Tuist directory structure] / Tuist.swift Project.swift Workspace.swift ``` ::: > [!TIP] XCODE'S LANGUAGE > Both XcodeGen and Tuist embrace Xcode's language and concepts. However, Tuist's Swift-based configuration provides you with the convenience of using Xcode's autocompletion, type-checking, and validation features. ## Spec templates {#spec-templates} One of the disadvantages of YAML as a language for project configuration is that it doesn't support reusability across YAML files out of the box. This is a common need when describing projects, which XcodeGen had to solve with their own propietary solution named _"templates"_. With Tuist's re-usability is built into the language itself, Swift, and through a Swift module named project description helpers, which allow reusing code across all your manifest files. ::: code-group ```swift [Tuist/ProjectDescriptionHelpers/Target+Features.swift] import ProjectDescription extension Target { /** This function is a factory of targets that together represent a feature. */ static func featureTargets(name: String) -> [Target] { // ... } } ``` ```swift [Project.swift] import ProjectDescription import ProjectDescriptionHelpers // [!code highlight] let project = Project(name: "MyProject", targets: Target.featureTargets(name: "MyFeature")) // [!code highlight] ``` --- URL: "/ru/guides/develop/projects/adoption/migrate/xcode-project" LLMS_URL: "/ru/guides/develop/projects/adoption/migrate/xcode-project.md" title: "Migrate an Xcode project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate an Xcode project to a Tuist project." --- # Migrate an Xcode project {#migrate-an-xcode-project} Unless you create a new project using Tuist, in which case you get everything configured automatically, you'll have to define your Xcode projects using Tuist's primitives. How tedious this process is, depends on how complex your projects are. As you probably know, Xcode projects can become messy and complex over time: groups that don't match the directory structure, files that are shared across targets, or file references that point to nonexisting files (to mention some). All that accumulated complexity makes it hard for us to provide a command that reliably migrates project. Moreover, manual migration is an excellent exercise to clean up and simplify your projects. Not only the developers in your project will be thankful for that, but Xcode, who will be faster processing and indexing them. Once you have fully adopted Tuist, it will make sure that projects are consistently defined and that they remain simple. In the aim of easing that work, we are giving you some guidelines based on the feedback that we have received from the users. ## Create project scaffold {#create-project-scaffold} First of all, create a scaffold for your project with the following Tuist files: ::: code-group ```js [Tuist.swift] import ProjectDescription let tuist = Tuist() ``` ```js [Project.swift] import ProjectDescription let project = Project( name: "MyApp-Tuist", targets: [ /** Targets will go here **/ ] ) ``` ```js [Tuist/Package.swift] // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies ] ) ``` ::: `Project.swift` is the manifest file where you'll define your project, and `Package.swift` is the manifest file where you'll define your dependencies. The `Tuist.swift` file is where you can define project-scoped Tuist settings for your project. > [!TIP] PROJECT NAME WITH -TUIST SUFFIX > To prevent conflicts with the existing Xcode project, we recommend adding the `-Tuist` suffix to the project name. You can drop it once you've fully migrated your project to Tuist. ## Build and test the Tuist project in CI {#build-and-test-the-tuist-project-in-ci} To ensure the migration of each change is valid, we recommend extending your continuous integration to build and test the project generated by Tuist from your manifest file: ```bash tuist install tuist generate tuist build -- ...{xcodebuild flags} # or tuist test ``` ## Extract the project build settings into `.xcconfig` files {#extract-the-project-build-settings-into-xcconfig-files} Extract the build settings from the project into an `.xcconfig` file to make the project leaner and easier to migrate. You can use the following command to extract the build settings from the project into an `.xcconfig` file: ```bash mkdir -p xcconfigs/ tuist migration settings-to-xcconfig -p MyApp.xcodeproj -x xcconfigs/MyApp-Project.xcconfig ``` Then update your `Project.swift` file to point to the `.xcconfig` file you've just created: ```swift import ProjectDescription let project = Project( name: "MyApp", settings: .settings(configurations: [ .debug(name: "Debug", xcconfig: "./xcconfigs/MyApp-Project.xcconfig"), // [!code ++] .release(name: "Release", xcconfig: "./xcconfigs/MyApp-Project.xcconfig"), // [!code ++] ]), targets: [ /** Targets will go here **/ ] ) ``` Then extend your continuous integration pipeline to run the following command to ensure that changes to build settings are made directly to the `.xcconfig` files: ```bash tuist migration check-empty-settings -p Project.xcodeproj ``` ## Extract package dependencies {#extract-package-dependencies} Extract all your project's dependencies into the `Tuist/Package.swift` file: ```swift // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies .package(url: "https://github.com/onevcat/Kingfisher", .upToNextMajor(from: "7.12.0")) // [!code ++] ] ) ``` > [!TIP] PRODUCT TYPES > You can override the product type for a specific package by adding it to the `productTypes` dictionary in the `PackageSettings` struct. By default, Tuist assumes that all packages are static frameworks. ## Determine the migration order {#determine-the-migration-order} We recommend migrating the targets from the one that is the most dependent upon to the least. You can use the following command to list the targets of a project, sorted by the number of dependencies: ```bash tuist migration list-targets -p Project.xcodeproj ``` Start migrating the targets from the top of the list, as they are the ones that are the most depended upon. ## Migrate targets {#migrate-targets} Migrate the targets one by one. We recommend doing a pull request for each target to ensure that the changes are reviewed and tested before merging them. ### Extract the target build settings into `.xcconfig` files {#extract-the-target-build-settings-into-xcconfig-files} Like you did with the project build settings, extract the target build settings into an `.xcconfig` file to make the target leaner and easier to migrate. You can use the following command to extract the build settings from the target into an `.xcconfig` file: ```bash tuist migration settings-to-xcconfig -p MyApp.xcodeproj -t TargetX -x xcconfigs/TargetX.xcconfig ``` ### Define the target in the `Project.swift` file {#define-the-target-in-the-projectswift-file} Define the target in `Project.targets`: ```swift import ProjectDescription let project = Project( name: "MyApp", settings: .settings(configurations: [ .debug(name: "Debug", xcconfig: "./xcconfigs/Project.xcconfig"), .release(name: "Release", xcconfig: "./xcconfigs/Project.xcconfig"), ]), targets: [ .target( // [!code ++] name: "TargetX", // [!code ++] destinations: .iOS, // [!code ++] product: .framework, // [!code ++] // or .staticFramework, .staticLibrary... bundleId: "io.tuist.targetX", // [!code ++] sources: ["Sources/TargetX/**"], // [!code ++] dependencies: [ // [!code ++] /** Dependencies go here **/ // [!code ++] /** .external(name: "Kingfisher") **/ // [!code ++] /** .target(name: "OtherProjectTarget") **/ // [!code ++] ], // [!code ++] settings: .settings(configurations: [ // [!code ++] .debug(name: "Debug", xcconfig: "./xcconfigs/TargetX.xcconfig"), // [!code ++] .debug(name: "Release", xcconfig: "./xcconfigs/TargetX.xcconfig"), // [!code ++] ]) // [!code ++] ), // [!code ++] ] ) ``` > [!NOTE] TEST TARGETS > If the target has an associated test target, you should define it in the `Project.swift` file as well repeating the same steps. ### Validate the target migration {#validate-the-target-migration} Run `tuist build` and `tuist test` to ensure the project builds and tests pass. Additionally, you can use [xcdiff](https://github.com/bloomberg/xcdiff) to compare the generated Xcode project with the existing one to ensure that the changes are correct. ### Repeat {#repeat} Repeat until all the targets are fully migrated. Once you are done, we recommend updating your CI and CD pipelines to build and test the project using `tuist build` and `tuist test` commands to benefit from the speed and reliability that Tuist provides. ## Troubleshooting {#troubleshooting} ### Compilation errors due to missing files. {#compilation-errors-due-to-missing-files} If the files associated to your Xcode project targets were not all contained in a file-system directory representing the target, you might end up with a project that doesn't compile. Make sure the list of files after generating the project with Tuist matches the list of files in the Xcode project, and take the opportunity to align the file structure with the target structure. --- URL: "/ru/guides/develop/projects/adoption/migrate/swift-package" LLMS_URL: "/ru/guides/develop/projects/adoption/migrate/swift-package.md" title: "Migrate a Swift Package" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate from Swift Package Manager as a solution for managing your projects to Tuist projects." --- # Migrate a Swift Package {#migrate-a-swift-package} Swift Package Manager emerged as a dependency manager for Swift code that uninentionally found itself solving the problem of managing projects and supporting other programming languages like Objective-C. Because the tool was designed with a different purpose in mind, it can be challenging to use it to manage projects at scale because it lacks flexibility, performance, and power that Tuist provides. This is well captured in the [Scaling iOS at Bumble](https://medium.com/bumble-tech/scaling-ios-at-bumble-239e0fa009f2) article, which includes the following table comparing the performance of Swift Package Manager and native Xcode projects: A table that compares the regression in performance when using SPM over native Xcode projects We often come across developers and organizations that challenge the need for Tuist considering that Swift Package Manager can take a similar project management role. Some venture into a migration to later on realize that their developer experience has degraded signicantly. For instance, the rename of a file might take up to 15 seconds to re-index. 15 seconds! **Whether Apple will make Swift Package Manager a built-for-scale project manager is uncertain.** However, we are not seeing any signs that it's happening. In fact, we are seeing quite the opposite. They are making Xcode-inspired decisions, like achieving convenience through implicit configurations, which as you might know, is the source of complications at scale. We believe it'd take Apple to go to first principles and revisit some decisions that made sense as a dependency manager but not as a project manager, for example the usage of a compiled language as an interface to define projects. > [!TIP] SPM AS JUST A DEPENDENCY MANAGER > Tuist treats Swift Package Manager as a dependency manager, and it's a great one. We use it to resolve dependencies and to build them. We don't use it to define projects because it's not designed for that. ## Migrating from Swift Package Manager to Tuist {#migrating-from-swift-package-manager-to-tuist} The similarities between Swift Package Manager and Tuist make the migration process straightforward. The main difference is that you'll be defining your projects using Tuist's DSL instead of `Package.swift`. First, create a `Project.swift` file next to your `Package.swift` file. The `Project.swift` file will contain the definition of your project. Here's an example of a `Project.swift` file that defines a project with a single target: ```swift import ProjectDescription let project = Project( name: "App", targets: [ .target( name: "App", destinations: .iOS, product: .app, bundleId: "io.tuist.App", sources: ["Sources/**/*.swift"]* ), ] ) ``` Some things to note: - **ProjectDescription**: Instead of using `PackageDescription`, you'll be using `ProjectDescription`. - **Project:** Instead of exporting a `package` instance, you'll be exporting a `project` instance. - **Xcode language:** The primitives that you use to define your project mimic Xcode's language, so you'll find schemes, targets, and build phases among others. Then create a `Tuist.swift` file with the following content: ```swift import ProjectDescription let tuist = Tuist() ``` The `Tuist.swift` contains the configuration for your project and its path serves as a reference to determine the root of your project. You can check out the directory structure document to learn more about the structure of Tuist projects. ## Editing the project {#editing-the-project} You can use `tuist edit` to edit the project in Xcode. The command will generate an Xcode project that you can open and start working on. ```bash tuist edit ``` Depending on the size of the project, you might consider using it in one shot or incrementally. We recommend starting with a small project to get familiar with the DSL and the workflow. Our advise is always to start from the most depended upon target and work all the way up to the top-level target. --- URL: "/ru/guides/develop/projects/adoption/migrate/bazel-project" LLMS_URL: "/ru/guides/develop/projects/adoption/migrate/bazel-project.md" title: "Migrate a Bazel project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate your projects from Bazel to Tuist." --- # Migrate a Bazel project {#migrate-a-bazel-project} [Bazel](https://bazel.build) is a build system that Google open-sourced in 2015. It's a powerful tool that allows you to build and test software of any size, quickly and reliably. Some large organizations like [Spotify](https://engineering.atspotify.com/2023/10/switching-build-systems-seamlessly/), [Tinder](https://medium.com/tinder/bazel-hermetic-toolchain-and-tooling-migration-c244dc0d3ae), or [Lyft](https://semaphoreci.com/blog/keith-smiley-bazel) use it, however, it requires an upfront (i.e., learning the technology) and ongoing investment (i.e., keeping up with Xcode updates) to introduce and maintain. While this works for some organizations that treat it as a cross-cutting concern, it might not be the best fit for others that want to focus on their product development. For instance, we've seen organizations whose iOS platform team introduced Bazel and had to drop it after the engineers that led the effort left the company. Apple's stance on the strong coupling between Xcode and the build system is another factor that makes it hard to maintain Bazel projects over time. > [!TIP] TUIST UNIQUENESS LIES IN ITS FINESSE > Instead of fighting Xcode and Xcode projects, Tuist embraces it. It's the same concepts (e.g., targets, schemes, build settings), a familiar language (i.e., Swift), and a simple and enjoyable experience that makes maintaining and scaling projects everyone's job and not just the iOS platform team's. ## Rules {#rules} Bazel uses rules to define how to build and test software. The rules are written in [Starlark](https://github.com/bazelbuild/starlark), a Python-like language. Tuist uses Swift as a configuration language, which provides developers with the convenience of using Xcode's autocompletion, type-checking, and validation features. For example, the following rule describes how to build a Swift library in Bazel: ::: code-group ```txt [BUILD (Bazel)] swift_library( name = "MyLibrary.library", srcs = glob(["**/*.swift"]), module_name = "MyLibrary" ) ``` ```swift [Project.swift (Tuist)] let project = Project( // ... targets: [ .target(name: "MyLibrary", product: .staticLibrary, sources: ["**/*.swift"]) ] ) ``` ::: Here's another example but compating how to define unit tests in Bazel and Tuist: :::code-group ```txt [BUILD (Bazel)] ios_unit_test( name = "MyLibraryTests", bundle_id = "io.tuist.MyLibraryTests", minimum_os_version = "16.0", test_host = "//MyApp:MyLibrary", deps = [":MyLibraryTests.library"], ) ``` ```swift [Project.swift (Tuist)] let project = Project( // ... targets: [ .target( name: "MyLibraryTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyLibraryTests", sources: "Tests/MyLibraryTests/**", dependencies: [ .target(name: "MyLibrary"), ] ) ] ) ``` ::: ## Swift Package Manager dependencies {#swift-package-manager-dependencies} In Bazel, you can use the [`rules_swift_package_manager`](https://github.com/cgrindel/rules_swift_package_manager) [Gazelle](https://github.com/bazelbuild/bazel-gazelle/blob/master/extend.md) plugin to use Swift Packages as dependencies. The plugin requires a `Package.swift` as a source of truth for the dependencies. Tuist's interface is similar to Bazel's in that sense. You can use the `tuist install` command to resolve and pull the dependencies of the package. After the resolution completes, you can then generate the project with the `tuist generate` command. ```bash tuist install # Fetch dependencies defined in Tuist/Package.swift tuist generate # Generate an Xcode project ``` ## Project generation {#project-generation} The community provides a set of rules, [rules_xcodeproj](https://github.com/MobileNativeFoundation/rules_xcodeproj), to generate Xcode projects off Bazel-declared projects. Unlike Bazel, where you need to add some configuration to your `BUILD` file, Tuist doesn't require any configuration at all. You can run `tuist generate` in the root directory of your project, and Tuist will generate an Xcode project for you. --- URL: "/ru/guides/develop/projects" LLMS_URL: "/ru/guides/develop/projects.md" title: "Проекты" titleTemplate: ":title · Разработка · Руководства · Tuist" description: "Узнайте о Tuist DSL для описания Xcode проектов." --- # Сгенерированные проекты {#generated-projects} Сгенерированные проекты - жизнеспособная альтернатива, которая помогает преодолевать проблемы при сохранении сложности и издержек на приемлемом уровне. Xcode-проекты рассматриваются в качестве основополагающего элемента, для обеспечения устойчивости к будущим обновлениям Xcode, и используемые механизмы генерации проектов Xcode предоставляют модуле-ориентированное декларативное API. Tuist использует объявления проектов для упрощения сложностей модуляризации\*\*, оптимизации рабочих процессов, таких как сборка и тестирование в различных средах, и для облегчения доступности эволюции Xcode-проектов для более широкой аудитории. ## Как это работает? {#how-does-it-work} Чтобы начать работу со сгенерированными XCode-проектами, вам нужно определить свой проект с помощью \*\*Предметно-ориентированного языка(DSL) Tuist \*\*. Это подразумевает использование манифест файлов, таких как `Workspace.swift` или `Project.swift`. Если вы прежде работали с менеджером пакетов Swift, то подход покажется вам очень похож. После того как вы определили свой проект, Tuist предоставит вам различные процессы для управления и взаимодействия с ним: - **Сгенерировать:** Это основной рабочий процесс. Используйте его для генерации проекта совместимого с Xcode. - **Построить:** Этот рабочий процесс не только генерирует Xcode проект, но и использует `xcodebuild` для его компиляции. - **Тестировать:** Рабочий процесс выполняющийся аналогично сборке и использующий `xcodebuild` для тестирования сгенерированного проекта. ## Испытания с проектами Xcode {#challenges-with-xcode-projects} По мере роста Xcode-проектов, **организации могут столкнуться с ухудшением производительности** из-за нескольких факторов, включая ненадежные инкрементальные сборки, частая очистка глобального кэша Xcode'а разработчиками, сталкивающимися с проблемами, и хрупкими конфигурациями проекта. Для поддержания быстрого развития функционала, организации, как правило, изучают различные стратегии. Некоторые организации предпочитают обойти компилятор путём абстрагирования платформы с использованием динамические выполняемых сред на основе JavaScript, к примеру [React Native](https://reactnative.dev/). Хотя этот подход может быть эффективным, он [усложняет доступ к нативным функциям платформы](https://shopify.engineering/building-app-clip-react-native). Другие организации выбирают **модуляризацию кодовой базы**, которая помогает установить явные границы, упрощая работу с кодом и улучшая время сборки. Однако, формат проектов Xcode не предназначен для модуляризации, что приводит к появлению неявных конфигураций, которые мало кто может понять и которые часто конфликтуют. Это приводит к [Фактору автобуса](https://ru.wikipedia.org/wiki/%D0%A4%D0%B0%D0%Ba%D1%82%D0%Be%D1%80_%D0%B0%D0%B2%D1%82%D0%Be%D0%B1%D1%83%D1%81%D0%B0), и, хотя инкрементальные сборки могут улучшаться, разработчикам все еще может понадобиться очищать кэш сборки Xcode'а (Derived Data) при ошибках сборки. Для решения этой проблемы некоторые организации **отказываются от системы сборки Xcode** и переходят к альтернативам, таким как [Buck](https://buck.build/) или [Bazel](https://bazel.build/). Однако это связано с [высокой сложностью и необходимостью обслуживания](https://bazel.build/migrate/xcode). ## Альтернативы {#alternatives} ### Менеджер пакетов Swift {#swift-package-manager} В то время как Менеджер Пакетов Swift (SPM) фокусируется главным образом на зависимостях, Tuist предлагает другой подход. С помощью Tuist, вы не просто задаете пакеты для интеграции с SPM; вы формируете ваши проекты, используя привычные понятия, такие как project, workspace, target, и scheme. ### XcodeGen {#xcodegen} [XcodeGen](https://github.com/yonaskolb/XcodeGen) - это специализированный генератор проекта, предназначенный для уменьшения конфликтов в Xcode проектах с множеством разработчиков и упрощения некоторых сложностей внутренней работы Xcode'а. Однако, проекты определяются с использованием сериализуемых форматов, таких как [YAML](https://yaml.org/). В отличие от Swift, это не позволяет разработчикам использовать абстракции или проверять без дополнительных инструменты. Хотя XcodeGen предоставляет способ сопоставлять зависимости с внутренними представлениями для проверки и оптимизации, он все еще раскрывает разработчикам нюансы Xcode. Это может сделать XcodeGen подходящей основой для [создания инструментов](https://github.com/MobileNativeFoundation/rules_xcodeproj), как видно в сообществе Bazel, но это не оптимально для всеохватывающей эволюции проекта, которая направлена на поддержание здоровой и продуктивной среды. ### Bazel {#bazel} [Bazel](https://bazel.build) — это продвинутая система сборки, известная своими функциями удаленного кэширования, которая набирает популярность в сообществе Swift в основном из-за этой возможности. Однако, учитывая ограниченную расширяемости Xcode и его системы сборки, замена на Bazel требует значительных усилий и поддержки. Лишь немногие компании, имеющие большие ресурсы, могут позволить эти накладные расходы, что видно из выборочного списка фирм, вкладывающих значительные средства в интеграцию Bazel с Xcode. Интересно, что сообщество создало [инструмент](https://github.com/MobileNativeFoundation/rules_xcodeproj), который использует Bazel и XcodeGen для создания Xcode-проекта. Это приводит к запутанной цепочке преобразований: от Bazel файлов к XcodeGen YAML и, наконец, к Xcode-проекту. Такая многослойность часто осложняет поиск проблем, делая их более трудными для решения. --- URL: "/ru/guides/develop/insights" LLMS_URL: "/ru/guides/develop/insights.md" title: "Insights" titleTemplate: ":title · Develop · Guides · Tuist" description: "Get insights into your projects to maintain a product developer environment." --- # Insights {#insights} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project Работа над большими проектами не должна чувствоваться как рутина. In fact, it should be as enjoyable as working on a project you started just two weeks ago. One of the reasons it is not is because as the project grows, the developer experience suffers. The build times increase and tests become slow and flaky. It's often easy to overlook these issues until it gets to a point where they become unbearable – however, at that point, it's difficult to address them. Tuist Insights provides you with the tools to monitor the health of your project and maintain a productive developer environment as your project scales. In other words, Tuist Insights helps you to anwer questions such as: - Has the build time significantly increased in the last week? - Have my tests become slower? Which ones? > [!NOTE] > Tuist Insights are in early development. ## Builds {#builds} While you probably have some metrics for the performance of CI workflows, you might not have the same visibility into the local development environment. However, local build times are one of the most important factors that contribute to the developer experience. To start tracking local build times, you can leverage the `tuist inspect build` command by adding it to your scheme's post-action: ![Post-action for inspecting builds](/images/guides/develop/insights/inspect-build-scheme-post-action.png) In case you're using [Mise](https://mise.jdx.dev/), your script will need to activate `tuist` in the post-action environment: ```sh # -C ensures that Mise loads the configuration from the Mise configuration # file in the project's root directory. eval "$($HOME/.local/bin/mise activate -C $SRCROOT bash --shims)" tuist inspect build ``` Your local builds are now tracked as long as you are logged in to your Tuist account. You can now access your build times in the Tuist dashboard and see how they evolve over time: > [!TIP] > To quickly access the dashboard, run `tuist project show --web` from the CLI. ![Dashboard with build insights](/images/guides/develop/insights/builds-dashboard.png) ## Сгенерированные проекты {#generated-projects} > [!NOTE] > Auto-generated schemes automatically include the `tuist inspect build` post-action. > > If you are not interested in tracking build insights in your auto-generated schemes, disable them using the buildInsightsDisabled generation option. If you are using generated projects, you can set up a custom build post-action using a custom scheme, such as: ```swift let project = Project( name: "MyProject", targets: [ // Your targets ], schemes: [ .scheme( name: "MyApp", shared: true, buildAction: .buildAction(targets: ["MyApp"]), testAction: .testAction(targets: ["MyAppTests"]), runAction: .runAction(configuration: "Debug"), postActions: [ .postAction( name: "Inspect Build", scriptText: """ eval \"$($HOME/.local/bin/mise activate -C $SRCROOT bash --shims)\" tuist inspect build """ ) ] ) ] ) ``` If you're not using Mise, your script can be simplified to just: ```swift .postAction( name: "Inspect Build", script: "tuist inspect build", execution: .always ) ``` ## Continuous integration {#continuous-integration} To track build times also on the CI, you will need to ensure that your CI is authenticated. Additionally, you will either need to: - Use the `tuist xcodebuild` command when invoking `xcodebuild` actions. - Add `-resultBundlePath` to your `xcodebuild` invocation. When `xcodebuild` builds your project without `-resultBundlePath`, the `.xcactivitylog` file is not generated. But the `tuist inspect build` post-action requires that file to be generated to analyze your build. --- URL: "/ru/guides/develop/cache" LLMS_URL: "/ru/guides/develop/cache.md" title: "Cache" titleTemplate: ":title · Develop · Guides · Tuist" description: "Optimize your build times by caching compiled binaries and sharing them across different environments." --- # Cache {#cache} > [!IMPORTANT] REQUIREMENTS > > - A generated project > - A Tuist account and project Xcode's build system provides [incremental builds](https://en.wikipedia.org/wiki/Incremental_build_model), enhancing efficiency under normal circumstances. However, this feature falls short in [Continuous Integration (CI) environments](https://en.wikipedia.org/wiki/Continuous_integration), where data essential for incremental builds is not shared across different builds. Additionally, **developers often reset this data locally to troubleshoot complex compilation problems**, leading to more frequent clean builds. This results in teams spending excessive time waiting for local builds to finish or for Continuous Integration pipelines to provide feedback on pull requests. Furthermore, the frequent context switching in such an environment compounds this unproductiveness. Tuist addresses these challenges effectively with its caching feature. This tool optimizes the build process by caching compiled binaries, significantly reducing build times both in local development and CI environments. This approach not only accelerates feedback loops but also minimizes the need for context switching, ultimately boosting productivity. ## Warming {#warming} Tuist efficiently utilizes hashes for each target in the dependency graph to detect changes. Utilizing this data, it builds and assigns unique identifiers to binaries derived from these targets. At the time of graph generation, Tuist then seamlessly substitutes the original targets with their corresponding binary versions. This operation, known as _"warming,"_ produces binaries for local use or for sharing with teammates and CI environments via Tuist. The process of warming the cache is straightforward and can be initiated with a simple command: ```bash tuist cache ``` The command re-uses binaries to speed up the process. ## Usage {#usage} By default, when Tuist commands necessitate project generation, they automatically substitute dependencies with their binary equivalents from the cache, if available. Additionally, if you specify a list of targets to focus on, Tuist will also replace any dependent targets with their cached binaries, provided they are available. For those who prefer a different approach, there is an option to opt out of this behavior entirely by using a specific flag: ::: code-group ```bash [Project generation] tuist generate # Only dependencies tuist generate Search # Dependencies + Search dependencies tuist generate Search Settings # Dependencies, and Search and Settings dependencies tuist generate --no-binary-cache # No cache at all ``` ```bash [Testing] tuist test ``` ::: > [!WARNING] > Binary caching is a feature designed for development workflows such as running the app on a simulator or device, or running tests. It is not intended for release builds. When archiving the app, generate a project with the sources by using the `--no-binary-cache` flag. ## Supported products {#supported-products} Only the following target products are cacheable by Tuist: - Frameworks (static and dynamic) that don't depend on [XCTest](https://developer.apple.com/documentation/xctest) - Bundles - Swift Macros We are working on supporting libraries and targets that depend on XCTest. > [!NOTE] UPSTREAM DEPENDENCIES > When a target is non-cacheable it makes the upstream targets non-cacheable too. For example, if you have the dependency graph `A > B`, where A depends on B, if B is non-cacheable, A will also be non-cacheable. ## Efficiency {#efficiency} The level of efficiency that can be achieved with binary caching depends strongly on the graph structure. To achieve the best results, we recommend the following: 1. Avoid very nested dependency graphs. The shallower the graph, the better. 2. Define dependencies with protocol/interface targets instead of implementation ones, and dependency-inject implementations from the top-most targets. 3. Split frequently-modified targets into smaller ones whose likelihood of change is lower. The above suggestions are part of the The Modular Architecture, which we propose as a way to structure your projects to maximize the benefits not only of binary caching but also of Xcode's capabilities. ## Recommended setup {#recommended-setup} We recommend having a CI job that **runs in every commit in the main branch** to warm the cache. This will ensure the cache always contains binaries for the changes in `main` so local and CI branch build incrementally upon them. > [!TIP] CACHE WARMING USES BINARIES > The `tuist cache` command also makes use of the binary cache to speed up the warming. The following are some examples of common workflows: ### A developer starts to work on a new feature {#a-developer-starts-to-work-on-a-new-feature} 1. They create a new branch from `main`. 2. They run `tuist generate`. 3. Tuist pulls the most recent binaries from `main` and generates the project with them. ### A developer pushes changes upstream {#a-developer-pushes-changes-upstream} 1. The CI pipeline will run `tuist build` or `tuist test` to build or test the project. 2. The workflow will pull the most recent binaries from `main` and generate the project with them. 3. It will then build or test the project incrementally. ## Troubleshooting {#troubleshooting} ### It doesn't use binaries for my targets {#it-doesnt-use-binaries-for-my-targets} Ensure that the hashes are deterministic across environments and runs. This might happen if the project has references to the environment, for example through absolute paths. You can use the `diff` command to compare the projects generated by two consecutive invocations of `tuist generate` or across environments or runs. Also make sure that the target doesn't depend either directly or indirectly on a non-cacheable target. --- URL: "/ru/guides/develop/build" LLMS_URL: "/ru/guides/develop/build.md" title: "Build" titleTemplate: ":title · Develop · Guides · Tuist" description: "Learn how to use Tuist to build your projects efficiently." --- # Build {#build} Projects are usually built through a build-system-provided CLI (e.g. `xcodebuild`). Tuist wraps them to improve the user experience and integrate the workflows with the platform to provide optimizations and analytics. You might wonder what's the value of using `tuist build` over generating the project with `tuist generate` (if needed) and building it with the platform-specific CLI. Here are some reasons: - **Single command:** `tuist build` ensures the project is generated if needed before compiling the project. - **Beautified output:** Tuist enriches the output using tools like [xcbeautify](https://github.com/cpisciotta/xcbeautify) that make the output more user-friendly. - Cache: It optimizes the build by deterministically reusing the build artifacts from a remote cache. - **Analytics:** It collects and reports metrics that are correlated with other data points to provide you with actionable information to make informed decisions. ## Usage {#usage} `tuist build` generates the project if needed, and then build it using the platform-specific build tool. We support the use of the `--` terminator to forward all subsequent arguments directly to the underlying build tool. This is useful when you need to pass arguments that are not supported by `tuist build` but are supported by the underlying build tool. ::: code-group ```bash [Build a scheme] tuist build MyScheme ``` ```bash [Build a specific configuration] tuist build MyScheme -- -configuration Debug ``` ```bash [Build all schemes without binary cache] tuist build --no-binary-cache ``` ::: --- URL: "/ru/guides/automate/continuous-integration" LLMS_URL: "/ru/guides/automate/continuous-integration.md" title: "Непрерывная интеграция (CI)" titleTemplate: ":title · Разработка · Руководства · Tuist" description: "Узнайте, как использовать Tuist в ваших рабочих процессах CI." --- # Непрерывная интеграция (CI) {#continuous-integration-ci} Вы можете использовать Tuist в окружениях [непрерывной интеграции](https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%BF%D1%80%D0%B5%D1%80%D1%8B%D0%B2%D0%BD%D0%B0%D1%8F_%D0%B8%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B0%D1%86%D0%B8%D1%8F). В следующих разделах приведены примеры того, как это можно сделать на различных платформах CI. ## Примеры {#examples} Чтобы запускать Tuist команды в ваших рабочих процессах CI, вам нужно установить Tuist в вашей среде CI. ### Xcode Cloud {#xcode-cloud} В [Xcode Cloud](https://developer.apple.com/xcode-cloud/), который использует Xcode проекты, вам нужно будет добавить [post-clone](https://developer.apple.com/documentation/xcode/writing-custom-build-scripts#Create-a-custom-build-script) скрипт для установки Tuist и запуска необходимых команд, например `tuist generate`: :::code-group ```bash [Mise] #!/bin/sh curl https://mise.jdx.dev/install.sh | sh mise install # Installs the version from .mise.toml # Runs the version of Tuist indicated in the .mise.toml file {#runs-the-version-of-tuist-indicated-in-the-misetoml-file} mise exec -- tuist generate ``` ```bash [Homebrew] #!/bin/sh brew install --formula tuist@x.y.z tuist generate ``` ::: ### Codemagic {#codemagic} В [Codemagic](https://codemagic.io) вы можете добавить дополнительный шаг в рабочий процесс для установки Tuist: ::: code-group ```yaml [Mise] workflows: lint: name: Build max_build_duration: 30 environment: xcode: 15.0.1 scripts: - name: Install Mise script: | curl https://mise.jdx.dev/install.sh | sh mise install # Installs the version from .mise.toml - name: Build script: mise exec -- tuist build ``` ```yaml [Homebrew] workflows: lint: name: Build max_build_duration: 30 environment: xcode: 15.0.1 scripts: - name: Install Tuist script: | brew install --formula tuist@x.y.z - name: Build script: tuist build ``` ::: ### GitHub Actions {#github-actions} В [GitHub Actions](https://docs.github.com/en/actions) можно добавить дополнительный шаг для установки Tuist, а в случае управления установкой Mise можно использовать [mise-action](https://github.com/jdx/mise-action), который абстрагирует установку Mise и Tuist: ::: code-group ```yaml [Mise] name: Build Application on: pull_request: branches: - main push: branches: - main jobs: build: runs-on: macos-latest steps: - uses: actions/checkout@v3 - uses: jdx/mise-action@v2 - run: tuist build ``` ```yaml [Homebrew] name: test on: pull_request: branches: - main push: branches: - main jobs: lint: runs-on: macos-latest steps: - uses: actions/checkout@v3 - run: brew install --formula tuist@x.y.z - run: tuist build ``` ::: :::tip Мы рекомендуем использовать `mise use --pin` в ваших проектах, чтобы закрепить версию Tuist в разных окружениях. Команда создаст файл `.tool-versions`, содержащий версию Tuist. ::: ## Аутентификация {#authentication} При использовании серверных функций, таких как cache, вам понадобится способ аутентификации запросов, идущих с ваших рабочих процессов CI на сервер. Для этого можно сгенерировать токен, привязанный к проекту, выполнив следующую команду: ```bash tuist project tokens create my-handle/MyApp ``` Команда создаст токен для проекта с полным названием `my-account/my-project`. Установите значение переменной окружения `TUIST_CONFIG_TOKEN` в вашей среде CI, так что бы она не была раскрыта. > [!IMPORTANT] ОБНАРУЖЕНИЕ СРЕДЫ CI > Tuist использует токен только в том случае, если обнаруживает, что работает в среде CI. Если ваше окружение CI не обнаружено, вы можете принудительно использовать токен, установив переменную окружения `CI` в значение `1`. --- URL: "/ru/guides/ai/mcp" LLMS_URL: "/ru/guides/ai/mcp.md" title: "Model Context Protocol (MCP)" titleTemplate: ":title · AI · Guides · Tuist" description: "Learn how to use Tuist's MCP server to have a language-based interface for your app development environment." --- # Model Context Protocol (MCP) [Model Context Protocol (MCP)](https://www.claudemcp.com) is a standard proposed by [Claude](https://claude.ai) for LLMs to interact with development environments. You can think of it as the USB-C of LLMs. Like shipping containers, which made cargo and transportation more interoperable, or protocols like TCP, which decoupled the application layer from the transport layer, MCP makes LLM-powered applications such as [Claude](https://claude.ai/) and editors like [Zed](https://zed.dev) or [Cursor](https://www.cursor.com) interoperable with other domains. Tuist provides a local server through its CLI so that you can interact with your **app development environment**. By connecting your client apps to it, you can use language to interact with your projects. In this page you'll learn about how to set it up and its capabilities. > [!NOTE] > Tuist MCP server uses Xcode's most-recent projects as the source of truth for projects you want to interact with. ## Set it up ### [Claude](https://claude.ai) If you are using [Claude desktop](https://claude.ai/download), you can run the tuist mcp setup claude command to configure your Claude environment. Alternatively, can manually edit the file at `~/Library/Application\ Support/Claude/claude_desktop_config.json`, and add the Tuist MCP server: :::code-group ```json [Global Tuist installation (e.g. Homebrew)] { "mcpServers": { "tuist": { "command": "tuist", "args": ["mcp", "start"] } } } ``` ```json [Mise installation] { "mcpServers": { "tuist": { "command": "mise", "args": ["x", "tuist@latest", "--", "tuist", "mcp", "start"] // Or tuist@x.y.z to fix the version } } } ``` ::: ## Capabilities In the following sections you'll learn about the capabilities of the Tuist MCP server. ### Ресурсы #### Recent projects and workspaces Tuist keeps a record of the Xcode projects and workspaces you’ve recently worked with, giving your application access to their dependency graphs for powerful insights. You can query this data to uncover details about your project structure and relationships, such as: - What are the direct and transitive dependencies of a specific target? - Which target has the most source files, and how many does it include? - What are all the static products (e.g., static libraries or frameworks) in the graph? - Can you list all targets, sorted alphabetically, along with their names and product types (e.g., app, framework, unit test)? - Which targets depend on a particular framework or external dependency? - What’s the total number of source files across all targets in the project? - Are there any circular dependencies between targets, and if so, where? - Which targets use a specific resource (e.g., an image or plist file)? - What’s the deepest dependency chain in the graph, and which targets are involved? - Can you show me all the test targets and their associated app or framework targets? - Which targets have the longest build times based on recent interactions? - What are the differences in dependencies between two specific targets? - Are there any unused source files or resources in the project? - Which targets share common dependencies, and what are they? With Tuist, you can dig into your Xcode projects like never before, making it easier to understand, optimize, and manage even the most complex setups! --- URL: "/ru/contributors/translate" LLMS_URL: "/ru/contributors/translate.md" title: "Translate" titleTemplate: ":title · Участникам проекта · Tuist" description: "This document describes the principles that guide the development of Tuist." --- # Translate {#translate} Languages can be barriers to understanding. We want to make sure that Tuist is accessible to as many people as possible. If you speak a language that Tuist doesn't support, you can help us by translating the various surfaces of Tuist. Since maintaining translations is a continuous effort, we add languages as we see contributors willing to help us maintain them. The following languages are currently supported: - English - Korean - Japanese - Russian > [!TIP] REQUEST A NEW LANGUAGE > If you believe Tuist would benefit from supporting a new language, please create a new [topic in the community forum](https://community.tuist.io/c/general/4) to discuss it with the community. ## How to translate We use [Crowdin](https://crowdin.com/) to manage the translations. First, go to the project that you want to contribute to: - [Documentation](https://crowdin.com/project/tuist-documentation) - [Website](https://crowdin.com/project/tuist-documentation) You'll need an account to start translating. You can sign in with GitHub. Once you have access, you can start translating. You'll see the list of resources that are available for translation. When you click on a resource, the editor will open, and you'll see a split view with the resource in the source language on the left and the translation on the right. Translate the text on the right and save your changes. As translations are updated, Crowdin will push them automatically to the right repository opening a pull request, which the maintainers will review and merge. > [!IMPORTANT] DON'T MODIFY THE RESOURCES IN THE TARGET LANGUAGE > Crowdin segments the files to bind source and target languages. If you modify the source language, you'll break the binding, and the reconciliation might yield unexpected results. ## Guidelines The following are the guidelines we follow when translating. ### Custom containers and GitHub alerts When translating [custom containers](https://vitepress.dev/guide/markdown#custom-containers) or [GitHub Alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts), only translate the title and the content **but not the type of alert**. :::details Example with GitHub Alert ````markdown > [!WARNING] 루트 변수 > 매니페스트의 루트에 있어야 하는 변수는... // Instead of > [!주의] 루트 변수 > 매니페스트의 루트에 있어야 하는 변수는... ``` ::: ::: details Example with custom container ```markdown ::: warning 루트 변수\ 매니페스트의 루트에 있어야 하는 변수는... ::: # Instead of ::: 주의 루트 변수\ 매니페스트의 루트에 있어야 하는 변수는... ::: ```` ::: ### Heading titles When translating headings, only translate tht title but not the id. For example, when translating the following heading: ```markdown # Add dependencies {#add-dependencies} ``` It should be translated as (note the id is not translated): ```markdown # 의존성 추가하기 {#add-dependencies} ``` --- URL: "/ru/contributors/principles" LLMS_URL: "/ru/contributors/principles.md" title: "Principles" titleTemplate: ":title · Участникам проекта · Tuist" description: "This document describes the principles that guide the development of Tuist." --- # Principles {#principles} This page describes principles that are pillars to the design and development of Tuist. They evolve with the project and are meant to ensure a sustainable growth that is well-aligned with the project foundation. ## Default to conventions {#default-to-conventions} One of the reasons why Tuist exists is because Xcode is weak in conventions and that leads to complex projects that are hard to scale up and maintain. For that reason, Tuist takes a different approach by defaulting to simple and thoroughly designed conventions. **Developers can opt-out from the conventions, but that’s a conscious decision that doesn’t feel natural.** For example, there’s a convention for defining dependencies between targets by using the provided public interface. By doing that, Tuist ensures that the projects are generated with the right configurations for the linking to work. Developers have the option to define the dependencies through build settings, but they’d be doing it implicitly and therefore breaking Tuist features such as `tuist graph` or `tuist cache` that rely on some conventions being followed. The reason why we default to conventions is that the more decision we can make on behalf of the developers, the more focus they’ll have crafting features for their apps. When we are left with no conventions like it’s the case in many projects, we have to make decisions that will end up not being consistent with other decisions and as a consequence, there’ll be an accidental complexity that will be hard to manage. ## Manifests are the source of truth {#manifests-are-the-source-of-truth} Having many layers of configurations and contracts between them results in a project setup that is hard to reason about and maintain. Think for a second on an average project. The definition of the project lives in the `.xcodeproj` directories, the CLI in scripts (e.g `Fastfiles`), and the CI logic in pipelines. Those are three layers with contracts between them that we need to maintain. _How often have you been in a situation where you changed something in your projects, and then a week later you realized that the release scripts broke?_ We can simplify this by having a single source of truth, the manifest files. Those files provide Tuist with the information that it needs to generate Xcode projects that developers can use to edit their files. Moreover, it allows having standard commands for building projects from a local or CI environment. **Tuist should own the complexity and expose a simple, safe, and enjoyable interface to describe their projects as explicitly as possible.** ## Make the implicit explicit {#make-the-implicit-explicit} Xcode supports implicit configurations. A good example of that is inferring the implicitly defined dependencies. While implicitness is fine for small projects, where configurations are simple, as projects get larger it might cause slowness or odd behaviors. Tuist should provide explicit APIs for implicit Xcode behaviors. It should also support defining Xcode implicitness but implemented in such a way that encourages developers to opt for the explicit approach. Supporting Xcode implicitness and intricacies facilitates the adoption of Tuist, after which teams can take some time to get rid of the implicitness. The definition of dependencies is a good example of that. While developers can define dependencies through build settings and phases, Tuist provides a beautiful API that encourages its adoption. **Designing the API to be explicit allows Tuist to run some checks and optimizations on the projects that otherwise wouldn’t be possible.** Moreover, it enables features like `tuist graph`, which exports a representation of the dependency graph, or `tuist cache`, which caches all the targets as binaries. > [!TIP] > We should treat each request to port features from Xcode as an opportunity to simplify concepts with simple and explicit APIs. ## Keep it simple {#keep-it-simple} One of the main challenges when scaling Xcode projects comes from the fact that **Xcode exposes a lot of complexity to the users.** Due to that, teams have a high bus factor and only a few people in the team understand the project and the errors that the build system throws. That’s a bad situation to be in because the team relies on a few people. Xcode is a great tool, but so many years of improvements, new platforms, and programming languages, are reflected on their surface, which struggled to remain simple. Tuist should take the opportunity to keep things simple because working on simple things is fun and motivates us. No one wants to spend time trying to debug an error that happens at the very end of the compilation process, or understanding why they are not able to run the app on their devices. Xcode delegates the tasks to its underlying build system and in some cases it does a very poor job translating errors into actionable items. Have you ever got a _“framework X not found”_ error and you didn’t know what to do? Imagine if we got a list of potential root causes for the bug. ## Start from the developer’s experience {#start-from-the-developers-experience} Part of the reason why there is a lack of innovation around Xcode, or put differently, not as much as in other programming environments, is because **we often start analyzing problems from existing solutions.** As a consequence, most of the solutions that we find nowadays revolve around the same ideas and workflows. While it’s good to include existing solutions in the equations, we should not let them constrain our creativity. We like to think as [Tom Preston](https://tom.preston-werner.com/) puts it in [this podcast](https://tom.preston-werner.com/): _“Most things can be achieved, whatever you have in your head you can probably pull off with code as long as is possible within the constrains of the universe”._ If **we imagine how we’d like the developer experience to be**, it’s just a matter of time to pull it off — by starting to analyze the problems from the developer experience gives us a unique point of view that will lead us to solutions that users will love to use. We might feel tempted to follow what everyone is doing, even if that means sticking with the inconveniences that everyone continues to complain about. Let’s not do that. How do I imagine archiving my app? How would I love code signing to be? What processes can I help streamline with Tuist? For example, adding support for [Fastlane](https://fastlane.tools/) is a solution to a problem that we need to understand first. We can get to the root of the problem by asking “why” questions. Once we narrow down where the motivation comes from, we can think of how Tuist can help them best. Maybe the solution is integrating with Fastlane, but it’s important we don’t disregard other equally valid solutions that we can put on the table before making trade-offs. ## Errors can and will happen {#errors-can-and-will-happen} We, developers, have an inherent temptation to disregard that errors can happen. As a result, we design and test software only considering the ideal scenario. Swift, its type system, and a well-architected code might help prevent some errors, but not all of them because some are out of our control. We can’t assume the user will always have an internet connection, or that the system commands will return successfully. The environments in which Tuist runs are not sandboxes that we control, and hence we need to make an effort to understand how they might change and impact Tuist. Poorly handled errors result in bad user experience, and users might lose trust in the project. We want users to enjoy every single piece of Tuist, even the way we present errors to them. We should put ourselves in the shoes of users and imagine what we’d expect the error to tell us. If the programming language is the communication channel through which errors propagate, and the users are the destination of the errors, they should be written in the same language that the target (users) speak. They should include enough information to know what happened and hide the information that is not relevant. Also, they should be actionable by telling users what steps they can take to recover from them. And last but not least, our test cases should contemplate failing scenarios. Not only they ensure that we are handling errors as we are supposed to, but prevent future developers from breaking that logic. --- URL: "/ru/contributors/issue-reporting" LLMS_URL: "/ru/contributors/issue-reporting.md" title: "Issue reporting" titleTemplate: ":title · Участникам проекта · Tuist" description: "Learn how to contribute to Tuist by reporting bugs" --- # Issue reporting {#issue-reporting} As user of Tuist, you might come across bugs or unexpected behaviors. If you do, we encourage you to report them so that we can fix them. ## GitHub issues is our ticketing platform {#github-issues-is-our-ticketing-platform} Issues should be reported on GitHub as [GitHub issues](https://github.com/tuist/tuist/issues) and not on Slack or other platforms. GitHub is better for tracing and managing issues, is closer to the codebase, and allows us to track the progress of the issue. Moreover, it encourages a long-form description of the problem, which forces the reporter to think about the problem and provide more context. ## Context is crucial {#context-is-crucial} An issue without enough context will be deemed incomplete and the author will be asked for additional context. If not provided, the issue will be closed. Think about it this way: the more context you provide, the easier it is for us to understand the problem and fix it. So if you want your issue to be fixed, provide as much context as possible. Try to answer the following questions: - What were you trying to do? - How does your graph look? - What version of Tuist are you using? - Is this blocking you? We also require you to provide a minimal **reproducible project**. ## Reproducible project {#reproducible-project} ### What is a reproducible project? {#what-is-a-reproducible-project} A reproducible project is a small Tuist project to demonstrate a problem - often this problem is caused by a bug in Tuist. Your reproducible project should contain the bare minimum features needed to clearly demonstrate the bug. ### Why should you create a reproducible test case? {#why-should-you-create-a-reproducible-test-case} A reproducible projects lets us isolate the cause of a problem, which is the first step towards fixing it! The most important part of any bug report is to describe the exact steps needed to reproduce the bug. A reproducible project is a great way to share a specific environment that causes a bug. Your reproducible project is the best way to help people that want to help you. ### Steps to create a reproducible project {#steps-to-create-a-reproducible-project} - Create a new git repository. - Initialize a project using `tuist init` in the repository directory. - Add the code needed to recreate the error you’ve seen. - Publish the code (your GitHub account is a good place to do this) and then link to it when creating an issue. ### Benefits of reproducible projects {#benefits-of-reproducible-projects} - **Smaller surface area:** By removing everything but the error, you don’t have to dig to find the bug. - **No need to publish secret code:** You might not be able to publish your main site (for many reasons). Remaking a small part of it as a reproducible test case allows you to publicly demonstrate a problem without exposing any secret code. - **Proof of the bug:** Sometimes a bug is caused by some combination of settings on your machine. A reproducible test case allows contributors to pull down your build and test it on their machines as well. This helps verify and narrow down the cause of a problem. - **Get help with fixing your bug:** If someone else can reproduce your problem, they often have a good chance of fixing the problem. It’s almost impossible to fix a bug without first being able to reproduce it. --- URL: "/ru/contributors/get-started" LLMS_URL: "/ru/contributors/get-started.md" title: "Начало работы" titleTemplate: ":title · Участникам проекта · Tuist" description: "Начните вносить свой вклад в Tuist c помощью этого руководства." --- # Начало работы {#get-started} Если у вас есть опыт создания приложений для платформ Apple, таких как iOS, добавление кода в Tuist не показаться сильно иным. Есть два отличия, по сравнению с разработкой приложений, которые стоит упомянуть: - **Взаимодействие с командным интерфейсом происходит через терминал.** Пользователь запускает Tuist, который выполняет желаемую задачу и затем завершается успешно или со статус-кодом. В процессе выполнения пользователь может получать уведомления с помощью отправки выходной информацию в стандартный поток вывода или в стандартный поток ошибок. Нет графического интерфейса или жестов, только намерения пользователя. - **Нет цикла, который держит процесс в ожидании ввода пользователя**, как это происходит в приложениях iOS, когда приложение получает системные события или действия пользователя. Командный интерфейс запускаются в собственном процессе и закрывается после завершения работы. Асинхронное выполнение может быть осуществлено с использованием системного API, такаго как [DispatchQueue](https://developer.apple.com/documentation/dispatch/dispatchqueue) или [structured concurrency](https://developer.apple.com/tutorials/app-dev-training/managing-structured-concurrency), но необходимо убедиться, что процесс запущен пока выполняется асинхронная работа. В противном случае процесс прекратит асинхронную работу. Если у вас нет опыта работы с Swift, мы рекомендуем [официальную документацию Apple](https://docs.swift.org/swift-book/) для ознакомления с языком и наиболее часто используемыми сущностями библиотеки `Foundation`. ## Минимальные требования {#minimum-requirements} Минимальные требования для внесения вклада в Tuist: - macOS 14.0+ - Xcode 16.3+ ## Настройте проект локально {#set-up-the-project-locally} Чтобы начать работу над проектом, можно выполнить следующие действия: - Склонируйте репозиторий выполнив: `git clone git@github.com:tuist/tuist.git` - [Установите](https://mise.jdx.dev/getting-started.html) Mise, чтобы подготовить среду разработки - Выполните `mise install` для установки системных зависимостей, необходимых Tuist - Выполните `tuist install` для установки внешних зависимостей, необходимых Tuist - (Необязательно) Запустите `tuist auth login`, чтобы получить доступ к Tuist Cache - Выполните `tuist generate`, чтобы сгенерировать Xcode-проект Tuist с помощью самого Tuist **После генерации проект откроется автоматически**. Если вам нужно открыть проект снова, без генерации - выполните `open Tuist.xcworkspace` (или используйте Finder). > [!NOTE] XED . > Если вы попробуете открыть проект используя `xed .` - он откроет пакет, а не проект созданный Tuist. Мы рекомендуем использовать проект сгенерированный самим Tuist чтобы "прочувствовать плоды собственного труда". ## Редактирование проекта {#edit-the-project} Если вам необходимо изменить проект, например, для добавления зависимостей или корректировки target, вы можете использовать команду `tuist edit`. Команда редко используется, но лучше помнить о том, что она существует. ## Запуск Tuist {#run-tuist} ### Из Xcode {#from-xcode} Чтобы запустить `tuist` из сгенерированного проекта Xcode - отредактируйте схему `tuist` и установите аргументы, которые вы хотели бы передать в команду. Например, чтобы выполнить команду `tuist generate`, вы можете задать аргументы `generate --no-open`, чтобы проект не открылся автоматически после генерации. ![Пример конфигурации схемы для запуска команды generate с Tuist](/images/contributors/scheme-arguments.png) Вы также должны установить рабочий каталог в корень генерируемого проекта. Вы можете сделать это либо с помощью аргумента `--path`, который принимается всеми командами, либо с помощью настройки рабочего каталога в схеме, как показано ниже: ![Пример настройки рабочего каталога для запуска Tuist](/images/contributors/scheme-working-directory.png) > [!WARNING] СБОРКА PROJECTDESCRIPTION > Работа команды `tuist` зависит от наличия фреймворка `ProjectDescription` в каталоге собранных продуктов. Если `tuist` не может быть выполнена, потому что команда не может найти фреймворк `ProjectDescription`, постройте сначала схему `Tuist-Workspace`. ### Из терминала {#from-the-terminal} Вы можете выполнить `tuist` с помощью самого Tuist, используя его команду `run`: ```bash tuist run tuist generate --path /path/to/project --no-open ``` Как альтернатива - вы можете запустить его напрямую через Менеджер Пакетов Swift: ```bash swift build --product ProjectDescription swift run tuist generate --path /path/to/project --no-open ``` --- URL: "/ru/contributors/code-reviews" LLMS_URL: "/ru/contributors/code-reviews.md" title: "Код ревью" titleTemplate: ":title · Участникам проекта · Tuist" description: "Узнайте, как внести вклад в Tuist, проводя ревью на пулл-реквесты" --- # Код ревью {#code-reviews} Просмотр пулл-реквестов — это распространенный способ внесения вклада. Несмотря на то, что непрерывная интеграция (CI) гарантирует выполнение кода в соответствии с его задачами, этого всё же недостаточно. Существуют аспекты вклада, которые невозможно автоматизировать: дизайн, структура и архитектура кода, качество тестов или опечатки. Следующие разделы представляют различные аспекты процесса код ревью. ## Читаемость {#readability} Ясно ли код выражает свои намерения? **Если вам нужно потратить много времени на то, чтобы понять, что делает код, его реализацию следует улучшить.** Предложите разделить код на более мелкие абстракции, которые проще понять. В качестве альтернативы, и как крайнюю меру, они могут добавить комментарий, объясняющий логику кода. Спросите себя, сможете ли вы понять этот код в ближайшем будущем без какого-либо контекста, например, описания пулл-реквеста. ## Маленькие пулл-реквесты {#small-pull-requests} Большие пулл-реквесты сложно рецензировать, и легче упустить детали. Если пулл-реквест становится слишком большим и неуправляемым, предложите автору разбить его на более мелкие части. > [!NOTE] Исключения > Существуют некоторые сценарии, когда разбить pull request невозможно, например, когда изменения тесно связаны и не могут быть разделены. В таких случаях автор должен предоставить четкое объяснение изменений и причин, стоящих за ними. ## Согласованность {#consistency} Важно, чтобы изменения были согласованы с остальной частью проекта. Несоответствия усложняют обслуживание, поэтому их следует избегать. Если в проекте уже есть подход к выводу сообщений или показу ошибок, нам следует придерживаться его. Если автор не согласен со стандартами проекта, предложите ему открыть проблему в разделе "Issues", чтобы мы могли обсудить их более подробно. ## Тесты {#tests} Тесты позволяют быть уверенным в написанном коде. Код в пулл-реквестах должен быть протестирован, и все тесты должны пройти успешно. Хороший тест — это тот, который стабильно дает один и тот же результат, а также является легким для понимания и сопровождения. Ревьюеры проводят большую часть времени, рассматривая код реализации, но тесты не менее важны, потому что они тоже являются кодом. ## Изменения, нарушающие обратную совместимость {#breaking-changes} Изменения, нарушающие обратную совместимость, создают плохой пользовательский опыт для пользователей Tuist. Пулл-реквесты в проект должны избегать введения изменений, нарушающих обратную совместимость, если это не строго необходимо. Существует множество возможностей языка, которые мы можем использовать для развития интерфейса Tuist, не прибегая к изменениям, нарушающим обратную совместимость. Не всегда очевидно, нарушает ли изменение обратную совместимость. Один из способов проверить, является ли изменение нарушающим обратную совместимость, — это запустить Tuist на тестовых проектах в каталоге fixtures. Это требует от нас поставить себя на место пользователя и представить, как изменения повлияют на его работу. --- URL: "/ru/contributors/cli/logging" LLMS_URL: "/ru/contributors/cli/logging.md" title: "Логирование" titleTemplate: ":title · CLI · Contributors · Tuist" description: "Узнайте, как внести вклад в Tuist, проводя ревью на пулл-реквесты" --- # Логирование {#logging} CLI использует интерфейс [swift-log](https://github.com/apple/swift-log) интерфейс для логирования. Пакет абстрагирует детали реализации логирования, позволяя CLI быть независимым от её исполнения. Логер внедряется с помощью зависимости [swift-service-context](https://github.com/apple/swift-service-context) и может быть доступен из любого места с помощью: ```bash ServiceContext.current?.logger ``` > [!ПРИМЕЧАНИЕ] > `swift-service-context` передает экземпляр с помощью [локальных переменных задачи](https://developer.apple.com/documentation/swift/tasklocal), которые не распространяют значение при использовании `Dispatch`, поэтому если вы запускаете асинхронный код с помощью `Dispatch`, вам нужно будет получить экземпляр из контекста и передать его в асинхронную операцию. ## Что логировать {#what-to-log} Логи не являются интерфейсом CLI. Они являются инструментом для диагностики проблем по мере их возникновения. Поэтому чем больше информации вы предоставите, тем лучше. При создании новых функций поставьте себя на место разработчика, столкнувшегося с неожиданным поведением, и подумайте, какая информация будет ему полезна. Убедитесь, что вы используете правильный [уровень лога](https://www.swift.org/documentation/server/guides/libraries/log-levels.html). В противном случае разработчики не смогут отфильтровать шум. --- URL: "/ru/cli/shell-completions" LLMS_URL: "/ru/cli/shell-completions.md" title: "Автозавершения Shell" titleTemplate: ":title · Интерфейс командной строки (CLI) · Tuist" description: "Узнайте, как настроить оболочку для автоматического завершения команд Tuist." --- # Автозавершения Shell Если у вас Tuist **установлен глобально** (например, через Homebrew), вы можете установить автодополнения для Bash и Zsh для автоматического заполнения команд и опций. :::warning ЧТО ЕСТЬ ГЛОБАЛЬНАЯ УСТАНОВКА Глобальная установка — это установка, которая доступна в переменной среды `$PATH` в вашей командной оболочке. Это означает, что вы можете выполнить `tuist` из любой директории в вашем терминале. Это метод установки по умолчанию для Homebrew. ::: #### Zsh {#zsh} Если у вас установлен [oh-my-zsh](https://ohmyz.sh/), у вас уже есть директория для скриптов автодополнения — `.oh-my-zsh/completions`. Скопируйте ваш новый скрипт автодополнений в новый файл с именем `_tuist` в ту директорию: ```bash tuist --generate-completion-script > ~/.oh-my-zsh/completions/_tuist ``` Без `oh-my-zsh`, вам нужно добавить путь до скрипта автодополнений в ваш путь к функциям и включить автозагрузку. Сначала добавьте эти строки в `~/.zshrc`: ```bash fpath=(~/.zsh/completion $fpath) autoload -U compinit compinit ``` Затем создайте каталог в папке `~/.zsh/completion` и скопируйте скрипт автодополнений в эту новую директорию, в файл с именем `_tuist`. ```bash tuist --generate-completion-script > ~/.zsh/completion/_tuist ``` #### Bash {#bash} Если у вас установлен [bash-completion](https://github.com/scop/bash-completion), вы можете просто скопировать скрипт автодополнений в файл `/usr/local/etc/bash_completion.d/_tuist`: ```bash tuist --generate-completion-script > /usr/local/etc/bash_completion.d/_tuist ``` Без bash-completion, вам нужно выполнить `source` для скрипта автодополнений напрямую. Скопируйте скрипт в директорию `~/.bash_completions/` и, затем, добавьте следующую строку в `~/.bash_profile` или `~/.bashrc`: ```bash source ~/.bash_completions/example.bash ``` #### Fish {#fish} Если вы используете [fish оболочку](https://fishshell.com), вы можете скопировать ваш новый скрипт автодополнений в `~/.config/fish/completions/tuist.fish`: ```bash mkdir -p ~/.config/fish/completions tuist --generate-completion-script > ~/.config/fish/completions/tuist.fish ``` --- URL: "/ru/cli/logging" LLMS_URL: "/ru/cli/logging.md" title: "Логирование" titleTemplate: ":title · Интерфейс командной строки (CLI) · Tuist" description: "Узнайте, как включить и настроить логирование в Tuist." --- # Логирование {#logging} CLI, внутри, логирует сообщение, чтобы помочь вам диагностировать проблемы. ## Поиск проблем с помощью логов {#diagnose-issues-using-logs} Если вызов команды не дает желаемых результатов, вы можете диагностировать проблему, просмотрев логи. CLI направляет логи в [OSLog](https://developer.apple.com/documentation/os/oslog) и в файловую систему. При каждом запуске он создает лог файл в `$XDG_STATE_HOME/tuist/logs/{uuid}. og`, где `$XDG_STATE_HOME` принимает значение `~/.local/state`, если переменная окружения не установлена. По умолчанию, CLI выводит путь логов когда исполнение неожиданно завершается. Если это не так, то логи могут быть найдены в указанном выше пути (то есть в самом последнем лог-файле). > [!ВАЖНО] > Конфиденциальная информация не редактируется, поэтому будьте острожно при публикации логов. ### Непрерывная интеграция (CI) {#diagnose-issues-using-logs-ci} В CI, где окружения являются одноразовыми, вам может потребоваться настроить ваш конвейер CI для экспорта логов Tuist. Экспорт артефактов является общей возможностью CI-служб, и их конфигурации зависят от используемой вами службы. Например, в GitHub Actions вы можете использовать действие `actions/upload-artifact` для выгрузки логов в качестве артефакта: ```yaml name: Node CI on: [push] env: XDG_STATE_HOME: /tmp jobs: build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 # ... other steps - run: tuist generate # ... do something with the project - name: Export Tuist logs uses: actions/upload-artifact@v4 with: name: tuist-logs path: /tmp/tuist/logs/*.log ``` --- URL: "/ru/cli/[command]" LLMS_URL: "/ru/cli/[command].md" editLink: false titleTemplate: ":title · Интерфейс командной строки (CLI) · Tuist" --- --- URL: "/pt/server/on-premise/metrics" LLMS_URL: "/pt/server/on-premise/metrics.md" title: "Metrics" titleTemplate: ":title | On-premise | Server | Tuist" description: "Optimize your build times by caching compiled binaries and sharing them across different environments." --- # Metrics {#metrics} You can ingest metrics gathered by the Tuist server using [Prometheus](https://prometheus.io/) and a visualization tool such as [Grafana](https://grafana.com/) to create a custom dashboard tailored to your needs. The Prometheus metrics are served via the `/metrics` endpoint on port 9091. The Prometheus' [scrape_interval](https://prometheus.io/docs/introduction/first_steps/#configuring-prometheus) should be set as less than 10_000 seconds (we recommend keeping the default of 15 seconds). ## Elixir metrics {#elixir-metrics} By default we include metrics of the Elixir runtime, BEAM, Elixir, and some of the libraries we use. The following are some of the metrics you can expect to see: - [Application](https://hexdocs.pm/prom_ex/PromEx.Plugins.Application.html) - [BEAM](https://hexdocs.pm/prom_ex/PromEx.Plugins.Beam.html) - [Phoenix](https://hexdocs.pm/prom_ex/PromEx.Plugins.Phoenix.html) - [Phoenix LiveView](https://hexdocs.pm/prom_ex/PromEx.Plugins.PhoenixLiveView.html) - [Ecto](https://hexdocs.pm/prom_ex/PromEx.Plugins.Ecto.html) - [Oban](https://hexdocs.pm/prom_ex/PromEx.Plugins.Oban.html) We recommend checking those pages to know which metrics are available and how to use them. ## Runs metrics {#runs-metrics} A set of metrics related to Tuist Runs. ### `tuist_runs_total` (counter) {#tuist_runs_total-counter} The total number of Tuist Runs. #### Tags {#tuist-runs-total-tags} | Tag | Description | | -------- | ------------------------------------------------------------------------------------------- | | `name` | The name of the `tuist` command that was run, such as `build`, `test`, etc. | | `is_ci` | A boolean indicating if the executor was a CI or a developer's machine. | | `status` | `0` in case of `success`, `1` in case of `failure`. | ### `tuist_runs_duration_milliseconds` (histogram) {#tuist_runs_duration_milliseconds-histogram} The total duration of each tuist run in milliseconds. #### Tags {#tuist-runs-duration-miliseconds-tags} | Tag | Description | | -------- | ------------------------------------------------------------------------------------------- | | `name` | The name of the `tuist` command that was run, such as `build`, `test`, etc. | | `is_ci` | A boolean indicating if the executor was a CI or a developer's machine. | | `status` | `0` in case of `success`, `1` in case of `failure`. | ## Cache metrics {#cache-metrics} A set of metrics related to the Tuist Cache. ### `tuist_cache_events_total` (counter) {#tuist_cache_events_total-counter} The total number of binary cache events. #### Tags {#tuist-cache-events-total-tags} | Tag | Description | | ------------ | ---------------------------------------------------------------------- | | `event_type` | Can be either of `local_hit`, `remote_hit`, or `miss`. | ### `tuist_cache_uploads_total` (counter) {#tuist_cache_uploads_total-counter} The number of uploads to the binary cache. ### `tuist_cache_uploaded_bytes` (sum) {#tuist_cache_uploaded_bytes-sum} The number of bytes uploaded to the binary cache. ### `tuist_cache_downloads_total` (counter) {#tuist_cache_downloads_total-counter} The number of downloads to the binary cache. ### `tuist_cache_downloaded_bytes` (sum) {#tuist_cache_downloaded_bytes-sum} The number of bytes downloaded from the binary cache. --- ## Previews metrics {#previews-metrics} A set of metrics related to the previews feature. ### `tuist_previews_uploads_total` (sum) {#tuist_previews_uploads_total-counter} The total number of previews uploaded. ### `tuist_previews_downloads_total` (sum) {#tuist_previews_downloads_total-counter} The total number of previews downloaded. --- ## Storage metrics {#storage-metrics} A set of metrics related to the storage of artifacts in a remote storage (e.g. s3). > [!TIP] > These metrics are useful to understand the performance of the storage operations and to identify potential bottlenecks. ### `tuist_storage_get_object_size_size_bytes` (histogram) {#tuist_storage_get_object_size_size_bytes-histogram} The size (in bytes) of an object fetched from the remote storage. #### Tags {#tuist-storage-get-object-size-size-bytes-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_size_duration_miliseconds` (histogram) {#tuist_storage_get_object_size_duration_miliseconds-histogram} The duration (in milliseconds) of fetching an object size from the remote storage. #### Tags {#tuist-storage-get-object-size-duration-miliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_size_count` (counter) {#tuist_storage_get_object_size_count-counter} The number of times an object size was fetched from the remote storage. #### Tags {#tuist-storage-get-object-size-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_delete_all_objects_duration_milliseconds` (histogram) {#tuist_storage_delete_all_objects_duration_milliseconds-histogram} The duration (in milliseconds) of deleting all objects from the remote storage. #### Tags {#tuist-storage-delete-all-objects-duration-milliseconds-tags} | Tag | Description | | -------------- | -------------------------------------------------------------------------------- | | `project_slug` | The project slug of the project whose objects are being deleted. | ### `tuist_storage_delete_all_objects_count` (counter) {#tuist_storage_delete_all_objects_count-counter} The number of times all project objects were deleted from the remote storage. #### Tags {#tuist-storage-delete-all-objects-count-tags} | Tag | Description | | -------------- | -------------------------------------------------------------------------------- | | `project_slug` | The project slug of the project whose objects are being deleted. | ### `tuist_storage_multipart_start_upload_duration_milliseconds` (histogram) {#tuist_storage_multipart_start_upload_duration_milliseconds-histogram} The duration (in milliseconds) of starting an upload to the remote storage. #### Tags {#tuist-storage-multipart-start-upload-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_multipart_start_upload_duration_count` (counter) {#tuist_storage_multipart_start_upload_duration_count-counter} The number of times an upload was started to the remote storage. #### Tags {#tuist-storage-multipart-start-upload-duration-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_as_string_duration_milliseconds` (histogram) {#tuist_storage_get_object_as_string_duration_milliseconds-histogram} The duration (in milliseconds) of fetching an object as a string from the remote storage. #### Tags {#tuist-storage-get-object-as-string-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_as_string_count` (count) {#tuist_storage_get_object_as_string_count-count} The number of times an object was fetched as a string from the remote storage. #### Tags {#tuist-storage-get-object-as-string-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_check_object_existence_duration_milliseconds` (histogram) {#tuist_storage_check_object_existence_duration_milliseconds-histogram} The duration (in milliseconds) of checking the existence of an object in the remote storage. #### Tags {#tuist-storage-check-object-existence-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_check_object_existence_count` (count) {#tuist_storage_check_object_existence_count-count} The number of times the existence of an object was checked in the remote storage. #### Tags {#tuist-storage-check-object-existence-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_generate_download_presigned_url_duration_milliseconds` (histogram) {#tuist_storage_generate_download_presigned_url_duration_milliseconds-histogram} The duration (in milliseconds) of generating a download presigned URL for an object in the remote storage. #### Tags {#tuist-storage-generate-download-presigned-url-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_generate_download_presigned_url_count` (count) {#tuist_storage_generate_download_presigned_url_count-count} The number of times a download presigned URL was generated for an object in the remote storage. #### Tags {#tuist-storage-generate-download-presigned-url-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_multipart_generate_upload_part_presigned_url_duration_milliseconds` (histogram) {#tuist_storage_multipart_generate_upload_part_presigned_url_duration_milliseconds-histogram} The duration (in milliseconds) of generating a part upload presigned URL for an object in the remote storage. #### Tags {#tuist-storage-multipart-generate-upload-part-presigned-url-duration-milliseconds-tags} | Tag | Description | | ------------- | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `part_number` | The part number of the object being uploaded. | | `upload_id` | The upload ID of the multipart upload. | ### `tuist_storage_multipart_generate_upload_part_presigned_url_count` (count) {#tuist_storage_multipart_generate_upload_part_presigned_url_count-count} The number of times a part upload presigned URL was generated for an object in the remote storage. #### Tags {#tuist-storage-multipart-generate-upload-part-presigned-url-count-tags} | Tag | Description | | ------------- | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `part_number` | The part number of the object being uploaded. | | `upload_id` | The upload ID of the multipart upload. | ### `tuist_storage_multipart_complete_upload_duration_milliseconds` (histogram) {#tuist_storage_multipart_complete_upload_duration_milliseconds-histogram} The duration (in milliseconds) of completing an upload to the remote storage. #### Tags {#tuist-storage-multipart-complete-upload-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `upload_id` | The upload ID of the multipart upload. | ### `tuist_storage_multipart_complete_upload_count` (count) {#tuist_storage_multipart_complete_upload_count-count} The total number of times an upload was completed to the remote storage. #### Tags {#tuist-storage-multipart-complete-upload-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `upload_id` | The upload ID of the multipart upload. | --- ## Projects metrics {#projects-metrics} A set of metrics related to the projects. ### `tuist_projects_total` (last_value) {#tuist_projects_total-last_value} The total number of projects. --- ## Accounts metrics {#accounts-metrics} A set of metrics related to accounts (users and organizations). ### `tuist_accounts_organizations_total` (last_value) {#tuist_accounts_organizations_total-last_value} The total number of organizations. ### `tuist_accounts_users_total` (last_value) {#tuist_accounts_users_total-last_value} The total number of users. ## Database metrics {#database-metrics} A set of metrics related to the database connection. ### `tuist_repo_pool_checkout_queue_length` (last_value) {#tuist_repo_pool_checkout_queue_length-last_value} The number of database queries that are sitting in a queue waiting to be assigned to a database connection. ### `tuist_repo_pool_ready_conn_count` (last_value) {#tuist_repo_pool_ready_conn_count-last_value} The number of database connections that are ready to be assigned to a database query. ### `tuist_repo_pool_db_connection_connected` (counter) {#tuist_repo_pool_db_connection_connected-counter} The number of connections that have been established to the database. ### `tuist_repo_pool_db_connection_disconnected` (counter) {#tuist_repo_pool_db_connection_disconnected-counter} The number of connections that have been disconnected from the database. ## HTTP metrics {#http-metrics} A set of metrics related to Tuist's interactions with other services via HTTP. ### `tuist_http_request_count` (counter) {#tuist_http_request_count-last_value} The number of outgoing HTTP requests. ### `tuist_http_request_duration_nanosecond_sum` (sum) {#tuist_http_request_duration_nanosecond_sum-last_value} The sum of the duration of the outgoing requests (including the time that they spent waiting to be assigned to a connection). ### `tuist_http_request_duration_nanosecond_bucket` (distribution) {#tuist_http_request_duration_nanosecond_bucket-distribution} The distribution of the duration of outgoing requests (including the time that they spent waiting to be assigned to a connection). ### `tuist_http_queue_count` (counter) {#tuist_http_queue_count-counter} The number of requests that have been retrieved from the pool. ### `tuist_http_queue_duration_nanoseconds_sum` (sum) {#tuist_http_queue_duration_nanoseconds_sum-sum} The time it takes to retrieve a connection from the pool. ### `tuist_http_queue_idle_time_nanoseconds_sum` (sum) {#tuist_http_queue_idle_time_nanoseconds_sum-sum} The time a connection has been idle waiting to be retrieved. ### `tuist_http_queue_duration_nanoseconds_bucket` (distribution) {#tuist_http_queue_duration_nanoseconds_bucket-distribution} The time it takes to retrieve a connection from the pool. ### `tuist_http_queue_idle_time_nanoseconds_bucket` (distribution) {#tuist_http_queue_idle_time_nanoseconds_bucket-distribution} The time a connection has been idle waiting to be retrieved. ### `tuist_http_connection_count` (counter) {#tuist_http_connection_count-counter} The number of connections that have been established. ### `tuist_http_connection_duration_nanoseconds_sum` (sum) {#tuist_http_connection_duration_nanoseconds_sum-sum} The time it takes to establish a connection against a host. ### `tuist_http_connection_duration_nanoseconds_bucket` (distribution) {#tuist_http_connection_duration_nanoseconds_bucket-distribution} The distribution of the time it takes to establish a connection against a host. ### `tuist_http_send_count` (counter) {#tuist_http_send_count-counter} The number of requests that have been sent once assigned to a connection from the pool. ### `tuist_http_send_duration_nanoseconds_sum` (sum) {#tuist_http_send_duration_nanoseconds_sum-sum} The time that it takes for requests to complete once assigned to a connection from the pool. ### `tuist_http_send_duration_nanoseconds_bucket` (distribution) {#tuist_http_send_duration_nanoseconds_bucket-distribution} The distribution of the time that it takes for requests to complete once assigned to a connection from the pool. ### `tuist_http_receive_count` (counter) {#tuist_http_receive_count-counter} The number of responses that have been received from sent requests. ### `tuist_http_receive_duration_nanoseconds_sum` (sum) {#tuist_http_receive_duration_nanoseconds_sum-sum} The time spent receiving responses. ### `tuist_http_receive_duration_nanoseconds_bucket` (distribution) {#tuist_http_receive_duration_nanoseconds_bucket-distribution} The distribution of the time spent receiving responses. ### `tuist_http_queue_available_connections` (last_value) {#tuist_http_queue_available_connections-last_value} The number of connections available in the queue. ### `tuist_http_queue_in_use_connections` (last_value) {#tuist_http_queue_in_use_connections-last_value} The number of queue connections that are in use. --- URL: "/pt/server/on-premise/install" LLMS_URL: "/pt/server/on-premise/install.md" title: "Installation" titleTemplate: ":title | On-premise | Server | Tuist" description: "Learn how to install Tuist on your infrastructure." --- # On-premise installation {#onpremise-installation} We offer a self-hosted version of the Tuist server for organizations that require more control over their infrastructure. This version allows you to host Tuist on your own infrastructure, ensuring that your data remains secure and private. > [!IMPORTANT] ENTERPRISE CUSTOMERS ONLY > The on-premise version of Tuist is available only for organizations on the Enterprise plan. If you are interested in this version, please reach out to [contact@tuist.dev](mailto:contact@tuist.dev). ## Release cadence {#release-cadence} The Tuist server is **released every Monday** and the version name follows the convention name `{MAJOR}.YY.MM.DD`. The date component is used to warn the CLI user if their hosted version is 60 days older than the release date of the CLI. It's crucial that on-premise organizations keep up with Tuist updates to ensure their developers benefit from the most recent improvements and that we can drop deprecated features with the confidence that we are not breaking any of the on-premise setups. The major component of the CLI is used to flag breaking changes in the Tuist server that will require coordination with the on-premise users. You should not expect us to use it, and in case we needed, rest asure we'll work with you in making the transition smooth. > [!NOTE] RELEASE NOTES > You'll be given access to a `tuist/registry` repository associated with the registry where images are published. Every new released will be published in that repository as a GitHub release and will contain release notes to inform you about what changes come with it. ## Runtime requirements {#runtime-requirements} This section outlines the requirements for hosting the Tuist server on your infrastructure. ### Running Docker-virtualized images {#running-dockervirtualized-images} We distribute the server as a [Docker](https://www.docker.com/) image via [GitHub’s Container Registry](https://docs.github.com/pt/packages/working-with-a-github-packages-registry/working-with-the-container-registry). To run it, your infrastructure must support running Docker images. Note that most infrastructure providers support it because it’s become the standard container for distributing and running software in production environments. ### Postgres database {#postgres-database} In addition to running the Docker images, you’ll need a [Postgres database](https://www.postgresql.org/) to store relational data. Most infrastructure providers include Posgres databases in their offering (e.g., [AWS](https://aws.amazon.com/rds/postgresql/) & [Google Cloud](https://cloud.google.com/sql/docs/postgres)). For performant analytics, we use a [Timescale Postgres extension](https://www.timescale.com/). You need to make sure that TimescaleDB is installed on the machine running the Postgres database. Follow the installation instructions [here](https://docs.timescale.com/self-hosted/latest/install/) to learn more. If you are unable to install the Timescale extension, you can set up your own dashboard using the Prometheus metrics. > [!INFO] MIGRATIONS > The Docker image's entrypoint automatically runs any pending schema migrations before starting the service. ### Storage {#storage} You’ll also need a solution to store files (e.g. framework and library binaries). Currently we support any storage that's S3-compliant. ## Configuration {#configuration} The configuration of the service is done at runtime through environment variables. Given the sensitive nature of these variables, we advise encrypting and storing them in secure password management solutions. Rest assured, Tuist handles these variables with utmost care, ensuring they are never displayed in logs. > [!NOTE] LAUNCH CHECKS > The necessary variables are verified at startup. If any are missing, the launch will fail and the error message will detail the absent variables. ### License configuration {#license-configuration} As an on-premise user, you'll receive a license key that you'll need to expose as an environment variable. This key is used to validate the license and ensure that the service is running within the terms of the agreement. | Environment variable | Description | Required | Default | Example | | -------------------- | -------------------------------------------------------------- | -------- | ------- | -------- | | `TUIST_LICENSE` | The license provided after signing the service level agreement | Yes | | `******` | > [!IMPORTANT] EXPIRATION DATE > Licenses have an expiration date. Users will receive a warning while using Tuist commands that interact with the server if the license expires in less than 30 days. If you are interested in renewing your license, please reach out to [contact@tuist.dev](mailto:contact@tuist.dev). ### Base environment configuration {#base-environment-configuration} | Environment variable | Description | Required | Default | Example | | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------ | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | | `TUIST_APP_URL` | The base URL to access the instance from the Internet | Yes | | https://cloud.tuist.io | | | `TUIST_SECRET_KEY_BASE` | The key to use to encrypt information (e.g. sessions in a cookie) | Yes | | | `c5786d9f869239cbddeca645575349a570ffebb332b64400c37256e1c9cb7ec831345d03dc0188edd129d09580d8cbf3ceaf17768e2048c037d9c31da5dcacfa` | | `TUIST_SECRET_KEY_PASSWORD` | Pepper to generate hashed passwords | No | `$TUIST_SECRET_KEY_BASE` | | | | `TUIST_SECRET_KEY_TOKENS` | Secret key to generate random tokens | No | `$TUIST_SECRET_KEY_BASE` | | | | `TUIST_USE_IPV6` | When `1` it configures the app to use IPv6 addresses | No | `0` | `1` | | | `TUIST_LOG_LEVEL` | The log level to use for the app | No | `info` | [Log levels](https://hexdocs.pm/logger/1.12.3/Logger.html#module-levels) | | | `TUIST_GITHUB_APP_PRIVATE_KEY` | The private key used for the GitHub app to unlock extra functionality such as posting automatic PR comments | No | `-----BEGIN RSA...` | | | | `TUIST_OPS_USER_HANDLES` | A comma-separated list of user handles that have access to the operations URLs | No | | `user1,user2` | | ### Database configuration {#database-configuration} The following environment variables are used to configure the database connection: | Environment variable | Description | Required | Default | Example | | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | ---------------------------------------------------------------------- | | `DATABASE_URL` | The URL to access the Postgres database. Note that the URL should contain the authentication information | Yes | | `postgres://username:password@cloud.us-east-2.aws.test.com/production` | | `TUIST_USE_SSL_FOR_DATABASE` | When true, it uses [SSL](https://en.wikipedia.org/wiki/Transport_Layer_Security) to connect to the database | No | `1` | `1` | | `TUIST_DATABASE_POOL_SIZE` | The number of connections to keep open in the connection pool | No | `10` | `10` | | `TUIST_DATABASE_QUEUE_TARGET` | The interval (in miliseconds) for checking if all the connections checked out from the pool took more than the queue interval [(More information)](https://hexdocs.pm/db_connection/DBConnection.html#start_link/2-queue-config) | No | `300` | `300` | | `TUIST_DATABASE_QUEUE_INTERVAL` | The threshold time (in miliseconds) in the queue that the pool uses to determine if it should start dropping new connections [(More information)](https://hexdocs.pm/db_connection/DBConnection.html#start_link/2-queue-config) | No | `1000` | `1000` | ### Authentication environment configuration {#authentication-environment-configuration} We facilitate authentication through [identity providers (IdP)](https://en.wikipedia.org/wiki/Identity_provider). To utilize this, ensure all necessary environment variables for the chosen provider are present in the server's environment. **Missing variables** will result in Tuist bypassing that provider. #### GitHub {#github} We recommend authenticating using a [GitHub App](https://docs.github.com/pt/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps) but you can also use the [OAuth App](https://docs.github.com/pt/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app). Make sure to include all essential environment variables specified by GitHub in the server environment. Absent variables will cause Tuist to overlook the GitHub authentication. To properly set up the GitHub app: - In the GitHub app's general settings: - Copy the `Client ID` and set it as `TUIST_GITHUB_APP_CLIENT_ID` - Create and copy a new `client secret` and set it as `TUIST_GITHUB_APP_CLIENT_SECRET` - Set the `Callback URL` as `http://YOUR_APP_URL/users/auth/github/callback`. `YOUR_APP_URL` can also be your server's IP address. - In the `Permissions and events`'s `Account permissions` section, set the `Email addresses` permission to `Read-only`. You'll then need to expose the following environment variables in the environment where the Tuist server runs: | Environment variable | Description | Required | Default | Example | | -------------------------------- | --------------------------------------- | -------- | ------- | ------------------------------------------ | | `TUIST_GITHUB_APP_CLIENT_ID` | The client ID of the GitHub application | Yes | | `Iv1.a629723000043722` | | `TUIST_GITHUB_APP_CLIENT_SECRET` | The client secret of the application | Yes | | `232f972951033b89799b0fd24566a04d83f44ccc` | #### Google {#google} You can set up authentication with Google using [OAuth 2](https://developers.google.com/identity/protocols/oauth2). For that, you'll need to create a new credential of type OAuth client ID. When creating the credentials, select "Web Application" as application type, name it `Tuist`, and set the redirect URI to `{base_url}/users/auth/google/callback` where `base_url` is the URL your hosted-service is running at. Once you create the app, copy the client ID and secret and set them as environment variables `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` respectively. > [!NOTE] CONSENT SCREEN SCOPES > You might need to create a consent screen. When you do so, make sure to add the `userinfo.email` and `openid` scopes and mark the app as internal. #### Okta {#okta} You can enable authentication with Okta through the [OAuth 2.0](https://oauth.net/2/) protocol. You'll have to [create an app](https://developer.okta.com/docs/en/guides/implement-oauth-for-okta/main/#create-an-oauth-2-0-app-in-okta) on Okta with the following configuration: - **App integration name:** `Tuist` - **Grant type:** Enable _Authorization Code_ for _Client acting on behalf of a user_ - **Sign-in redirect URL:** `{url}/users/auth/okta/callback` where `url` is the public URL your service is accessed through. - **Assignments:** This configuration will depend on your security team requirements. Once the app is created you'll need to set the following environment variables: | Environment variable | Description | Required | Default | Example | | -------------------------- | ---------------------------------------------- | -------- | ------- | --------------------------- | | `TUIST_OKTA_SITE` | The URL of your Okta organization | Yes | | `https://your-org.okta.com` | | `TUIST_OKTA_CLIENT_ID` | The client ID to authenticate against Okta | Yes | | | | `TUIST_OKTA_CLIENT_SECRET` | The client secret to authenticate against Okta | Yes | | | ### Storage environment configuration {#storage-environment-configuration} Tuist needs storage to house artifacts uploaded through the API. It's **essential to configure one of the supported storage solutions** for Tuist to operate effectively. #### S3-compliant storages {#s3compliant-storages} You can use any S3-compliant storage provider to store artifacts. The following environment variables are required to authenticate and configure the integration with the storage provider: | Environment variable | Description | Required | Default | Example | | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | ------------------------------------------ | | `TUIST_ACCESS_KEY_ID` or `AWS_ACCESS_KEY_ID` | The access key ID to authenticate against the storage provider | Yes | | `AKIAIOSFOD` | | `TUIST_SECRET_ACCESS_KEY` or `AWS_SECRET_ACCESS_KEY` | The secret access key to authenticate against the storage provider | Yes | | `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` | | `TUIST_S3_REGION` or `AWS_REGION` | The region where the bucket is located | Yes | | `us-west-2` | | `TUIST_S3_ENDPOINT` or `AWS_ENDPOINT` | The endpoint of the storage provider | Yes | | `https://s3.us-west-2.amazonaws.com` | | `TUIST_S3_BUCKET_NAME` | The name of the bucket where the artifacts will be stored | Yes | | `tuist-artifacts` | | `TUIST_S3_REQUEST_TIMEOUT` | The timeout (in seconds) for requests to the storage provider | No | `30` | `30` | | `TUIST_S3_POOL_TIMEOUT` | The timeout (in seconds) for the connection pool to the storage provider | No | `5` | `5` | | `TUIST_S3_POOL_COUNT` | The number of pools to use for connections to the storage provider | No | `1` | `1` | | `TUIST_S3_PROTOCOL` | The protocol to use when connecting to the storage provider (`http1` or `http2`) | No | `http2` | `http2` | | `TUIST_S3_VIRTUAL_HOST` | Whether the URL should be constructed with the bucket name as a sub-domain (virtual host). | No | No | `1` | > [!NOTE] AWS authentication with Web Identity Token from environment variables > If your storage provider is AWS and you'd like to authenticate using a web identity token, you can set the environment variable `TUIST_S3_AUTHENTICATION_METHOD` to `aws_web_identity_token_from_env_vars`, and Tuist will use that method using the conventional AWS environment variables. #### Google Cloud Storage {#google-cloud-storage} For Google Cloud Storage, follow [these docs](https://cloud.google.com/storage/docs/authentication/managing-hmackeys) to get the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` pair. The `AWS_ENDPOINT` should be set to `https://storage.googleapis.com`. Other environment variables are the same as for any other S3-compliant storage. ### Git platform configuration {#git-platform-configuration} Tuist can integrate with Git platforms to provide extra features such as automatically posting comments in your pull requests. #### GitHub {#platform-github} You will need to [create a GitHub app](https://docs.github.com/pt/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps). You can reuse the one you created for authentication, unless you created an OAuth GitHub app. In the `Permissions and events`'s `Repository permissions` section, you will need to additionally set the `Pull requests` permission to `Read and write`. On top of the `TUIST_GITHUB_APP_CLIENT_ID` and `TUIST_GITHUB_APP_CLIENT_SECRET`, you will need the following environment variables: | Environment variable | Description | Required | Default | Example | | ------------------------------ | ----------------------------------------- | -------- | ------- | ------------------------------------ | | `TUIST_GITHUB_APP_PRIVATE_KEY` | The private key of the GitHub application | Yes | | `-----BEGIN RSA PRIVATE KEY-----...` | ## Deployment {#deployment} On-premise users are granted access to the repository located at [tuist/registry](https://github.com/cloud/registry) which has a linked container registry for pulling images. Currently, the container registry allows authentication only as an individual user. Therefore, users with repository access must generate a **personal access token** within the Tuist organization, ensuring they have the necessary permissions to read packages. After submission, we will promptly approve this token. > [!IMPORTANT] USER VS ORGANIZATION-SCOPED TOKENS > Using a personal access token presents a challenge because it's associated with an individual who might eventually depart from the enterprise organization. GitHub recognizes this limitation and is actively developing a solution to allow GitHub apps to authenticate with app-generated tokens. ### Pulling the Docker image {#pulling-the-docker-image} After generating the token, you can retrieve the image by executing the following command: ```bash echo $TOKEN | docker login ghcr.io -u USERNAME --password-stdin docker pull ghcr.io/tuist/tuist:latest ``` ### Deploying the Docker image {#deploying-the-docker-image} The deployment process for the Docker image will differ based on your chosen cloud provider and your organization's continuous deployment approach. Since most cloud solutions and tools, like [Kubernetes](https://kubernetes.io/), utilize Docker images as fundamental units, the examples in this section should align well with your existing setup. We recommend establishing a deployment pipeline that that runs **every Tuesday**, pulling and deploying fresh images. This ensures you consistently benefit from the latest improvements. > [!IMPORTANT] > If your deployment pipeline needs to validate that the server is up and running, you can send a `GET` HTTP request to `/ready` and assert a `200` status code in the response. #### Fly {#fly} To deploy the app on [Fly](https://fly.io/), you'll require a `fly.toml` configuration file. Consider generating it dynamically within your Continuous Deployment (CD) pipeline. Below is a reference example for your use: ```toml app = "tuist" primary_region = "fra" kill_signal = "SIGINT" kill_timeout = "5s" [experimental] auto_rollback = true [env] # Your environment configuration goes here # Or exposed through Fly secrets [processes] app = "/usr/local/bin/hivemind /app/Procfile" [[services]] protocol = "tcp" internal_port = 8080 auto_stop_machines = false auto_start_machines = false processes = ["app"] http_options = { h2_backend = true } [[services.ports]] port = 80 handlers = ["http"] force_https = true [[services.ports]] port = 443 handlers = ["tls", "http"] [services.concurrency] type = "connections" hard_limit = 100 soft_limit = 80 [[services.http_checks]] interval = 10000 grace_period = "10s" method = "get" path = "/ready" protocol = "http" timeout = 2000 tls_skip_verify = false [services.http_checks.headers] [[statics]] guest_path = "/app/public" url_prefix = "/" ``` Then you can run `fly launch --local-only --no-deploy` to launch the app. On subsequent deploys, instead of running `fly launch --local-only`, you will need to run `fly deploy --local-only`. Fly.io doesn't allow to pull private Docker images, which is why we need to use the `--local-only` flag. ### Docker Compose {#docker-compose} Below is an example of a `docker-compose.yml` file that you can use as a reference to deploy the service: ```yaml version: '3.8' services: db: image: timescale/timescaledb-ha:pg16 restart: always environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - PGDATA=/var/lib/postgresql/data/pgdata ports: - '5432:5432' volumes: - db:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 pgweb: container_name: pgweb restart: always image: sosedoff/pgweb ports: - "8081:8081" links: - db:db environment: PGWEB_DATABASE_URL: postgres://postgres:postgres@db:5432/postgres?sslmode=disable depends_on: - db tuist: image: ghcr.io/tuist/tuist:latest container_name: tuist depends_on: - db ports: - "80:80" - "8080:8080" - "443:443" expose: - "80" - "8080" - "443:443" environment: # Base Tuist Env - https://docs.tuist.io/pt/guides/dashboard/on-premise/install#base-environment-configuration TUIST_USE_SSL_FOR_DATABASE: "0" TUIST_LICENSE: # ... DATABASE_URL: postgres://postgres:postgres@db:5432/postgres?sslmode=disable TUIST_APP_URL: https://localhost:8080 TUIST_SECRET_KEY_BASE: # ... WEB_CONCURRENCY: 80 # Auth - one method # GitHub Auth - https://docs.tuist.io/pt/guides/dashboard/on-premise/install#github TUIST_GITHUB_OAUTH_ID: TUIST_GITHUB_APP_CLIENT_SECRET: # Okta Auth - https://docs.tuist.io/pt/guides/dashboard/on-premise/install#okta TUIST_OKTA_SITE: TUIST_OKTA_CLIENT_ID: TUIST_OKTA_CLIENT_SECRET: TUIST_OKTA_AUTHORIZE_URL: # Optional TUIST_OKTA_TOKEN_URL: # Optional TUIST_OKTA_USER_INFO_URL: # Optional TUIST_OKTA_EVENT_HOOK_SECRET: # Optional # Storage AWS_ACCESS_KEY_ID: # ... AWS_SECRET_ACCESS_KEY: # ... AWS_S3_REGION: # ... AWS_ENDPOINT: # https://amazonaws.com TUIST_S3_BUCKET_NAME: # ... # Other volumes: db: driver: local ``` ## Operations {#operations} Tuist provides a set of utilities under `/ops/` that you can use to manage your instance. > [!IMPORTANT] Authorization > Only people whose handles are listed in the `TUIST_OPS_USER_HANDLES` environment variable can access the `/ops/` endpoints. - **Errors (`/ops/errors`):** You can view unexpected errors that ocurred in the application. This is useful for debugging and understanding what went wrong and we might ask you to share this information with us if you're facing issues. - **Dashboard (`/ops/dashboard`):** You can view a dashboard that provides insights into the application's performance and health (e.g. memory consumption, processes running, number of requests). This dashboard can be quite useful to understand if the hardware you're using is enough to handle the load. --- URL: "/pt/server/introduction/why-a-server" LLMS_URL: "/pt/server/introduction/why-a-server.md" title: "Why a server?" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn why Tuist has a server and how it can help scale your app development." --- # Why a server? {#why-a-server} At a certain scale, optimizing a project and developers' interactions with them require access to data that changes over time, and integrations with other internet services where teams collaborate. This is only possible with **a server that can store data in a database, process it asynchonously, and integrate it with other services.** While the role of a server is common in other ecosystems, it's not that common in app development. Teams leaned heavily on open source solutions that leveraged the capabilities of CI services to approximate the capabilities of a server. However, as the complexity of the projects and the number of developers working on them increased, the limitations of these solutions became more evident. We believe teams shouldn't have to worry about setting up and maintaining a server to scale their projects. That's why we built a server that Tuist and [Xcode projects](https://developer.apple.com/documentation/xcode/creating-an-xcode-project-for-an-app) can integrate with to scale their projects and teams. > [!TIP] GIVING YOUR PROJECTS AND WORKFLOWS SUPERPOWERS > A way of thinking about the server is as a superpower that you can give to your projects and workflows. > Some superpowers like binary caching require you to have a Tuist project but others just work with vanilla Xcode projects. --- URL: "/pt/server/introduction/integrations" LLMS_URL: "/pt/server/introduction/integrations.md" title: "Integrations" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn how to connect Tuist to other tools and services." --- # Integrations {#integrations} We strongly believe we should meet developers where they are, and let's be honest, developers spend time outside of their coding environments, such as reviewing pull request on [GitHub](https://github.com) or communicating with their team on [Slack](https://slack.com). That's why we've built integrations with popular tools and services to make it easier for you to use Tuist in your workflows. This page lists the integrations we currently support. ## Git platforms {#git-platforms} Git repositories are the centerpiece of the vast majority of software projects out there. We integrate with your Git platform to provide Tuist insights right in your pull requests or to save you some configuration such as syncing your default branch. ### GitHub {#github} Install the [Tuist GitHub app](https://github.com/marketplace/tuist). Once installed, you will need to tell Tuist the URL of your repository, such as: ```sh tuist project update tuist/tuist --repository-url https://github.com/tuist/tuist ``` --- URL: "/pt/server/introduction/authentication" LLMS_URL: "/pt/server/introduction/authentication.md" title: "Authentication" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn how to authenticate with the Tuist server from the CLI." --- # Authentication {#authentication} To interact with the server, the CLI needs to authenticate the requests using [bearer authentication](https://swagger.io/docs/specification/authentication/bearer-authentication/). The CLI supports authenticating as a user or as a project. ## As a user {#as-a-user} When using the CLI locally on your machine, we recommend authenticating as a user. To authenticate as a user, you need to run the following command: ```bash tuist auth login ``` The command will take you through a web-based authentication flow. Once you authenticate, the CLI will store a long-lived refresh token and a short-lived access token under `~/.config/tuist/credentials`. Each file in the directory represents the domain you authenticated against, which by default should be `cloud.tuist.io.json`. The information stored in that directory is sensitive, so **make sure to keep it safe**. The CLI will automatically look up the credentials when making requests to the server. If the access token is expired, the CLI will use the refresh token to get a new access token. ### Organization SSO {#organization-sso} If you have a Google Workspace organization and you want any developer who signs in with the same Google hosted domain to be added to your Tuist organization, you can set it up with: ```bash tuist organization update sso my-organization --provider google --organization-id my-google-domain.com ``` For on-premise customers that have Okta set up, you can get the same behavior as for Google by running: ```bash tuist organization update sso my-organization --provider okta --organization-id my-okta-domain.com ``` > [!IMPORTANT] > You must be authenticated with Google using an email tied to the organization whose domain you are setting up. ## As a project {#as-a-project} In non-interactive environments like continuous integrations', you can't authenticate through an interactive flow. For those environments, we recommend authenticating as a project by using a project-scoped token: ```bash tuist project tokens create ``` The CLI expects the token to be defined as the environment variable `TUIST_CONFIG_TOKEN`, and the `CI=1` environment variable to be set. The CLI will use the token to authenticate the requests. > [!IMPORTANT] LIMITED SCOPE > The permissions of the project-scoped token are limited to the actions that we consider safe for projects to perform from a CI environment. We plan to document the permissions that the token has in the future. --- URL: "/pt/server/introduction/accounts-and-projects" LLMS_URL: "/pt/server/introduction/accounts-and-projects.md" title: "Accounts and projects" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn how to create and manage accounts and projects in Tuist." --- # Accounts and projects {#accounts-and-projects} ## Accounts {#accounts} To use the server, you'll need an account. There are two types of accounts: - **Personal account:** Those accounts are created automaticaly when you sign up and are identified by a handle that's obtained either from the identity provider (e.g. GitHub) or the first part of the email address. - **Organization account:** Those accounts are manually created and are identified by a handle that's defined by the developer. Organizations allow inviting other members to collaborate on projects. If you are familiar with [GitHub](https://github.com), the concept is similar to theirs, where you can have personal and organization accounts, and they are identified by a _handle_ that's used when constructing URLs. > [!NOTE] CLI-FIRST > Most operations to manage accounts and projects are done through the CLI. We are working on a web interface that will make it easier to manage accounts and projects. You can manage the organization through the subcommands under `tuist organization`. To create a new organization account, run: ```bash tuist organization create {account-handle} ``` ## Projects {#projects} Your projects, either Tuist's or raw Xcode's, need to be integrated with your account through a remote project. Continuing with the comparison with GitHub, it's like having a local and a remote repository where you push your changes. You can use the `tuist project` to create and manage projects. Projects are identified by a full handle, which is the result of concatenating the organization handle and the project handle. For example, if you have an organization with the handle `tuist`, and a project with the handle `tuist`, the full handle would be `tuist/tuist`. The binding between the local and the remote project is done through the configuration file. If you don't have any, create it at `Tuist.swift` and add the following content: ```swift let tuist = Tuist(fullHandle: "{account-handle}/{project-handle}") // e.g. tuist/tuist ``` > [!IMPORTANT] TUIST PROJECT-ONLY FEATURES > Note that there are some features like binary caching that require you having a Tuist project. If you are using raw Xcode projects, you won't be able to use those features. Your project's URL is constructed by using the full handle. For example, Tuist's dashboard, which is public, is accessible at [cloud.tuist.io/tuist/tuist](https://cloud.tuist.io/tuist/tuist), where `tuist/tuist` is the project's full handle. --- URL: "/pt/references/project-description/[identifier]" LLMS_URL: "/pt/references/project-description/[identifier].md" editLink: false titleTemplate: ":title · Project Description · References · Tuist" --- --- URL: "/pt/references/migrations/from-v3-to-v4" LLMS_URL: "/pt/references/migrations/from-v3-to-v4.md" title: "From v3 to v4" titleTemplate: ":title · Migrations · References · Tuist" description: "This page documents how to migrate the Tuist CLI from the version 3 to version 4." --- # From Tuist v3 to v4 {#from-tuist-v3-to-v4} With the release of [Tuist 4](https://github.com/tuist/tuist/releases/tag/4.0.0), we took the opportunity to introduce some breaking changes to the project, which we believed would make the project easier to use and maintain in the long run. This document outlines the changes you will need to make to your project to upgrade from Tuist 3 to Tuist 4. ### Dropped version management through `tuistenv` {#dropped-version-management-through-tuistenv} Prior to Tuist 4, the installation script installed a tool, `tuistenv`, that would get renamed to `tuist` at installation time. The tool would take care of installing and activating versions of Tuist ensuring determinism across environments. With the aim of reducing the feature surface of Tuist, we decided to drop `tuistenv` in favor of [Mise](https://mise.jdx.dev/), a tool that does the same job but is more flexible and can be used across different tools. If you were using `tuistenv`, you'll have to uninstall the current version of Tuist by running `curl -Ls https://uninstall.tuist.io | bash` and then install it using the installation method of your choice. We strongly recommend the usage of Mise because it's able to install and activate versions deterministically across environments. ::: code-group ```bash [Uninstall tuistenv] curl -Ls https://uninstall.tuist.io | bash ``` ::: > [!IMPORTANT] MISE IN CI ENVIRONMENTS AND XCODE PROJECTS > If you decide to embrace the determinism that Mise brings across the board, we recommend checking out the documentation for how to use Mise in [CI environments](https://mise.jdx.dev/continuous-integration.html) and [Xcode projects](https://mise.jdx.dev/ide-integration.html#xcode). > [!NOTE] HOMEBREW IS SUPPORTED > Note that you can still install Tuist using Homebrew, which is a popular package manager for macOS. You can find the instructions on how to install Tuist using Homebrew in the installation guide. ### Dropped `init` constructors from `ProjectDescription` models {#dropped-init-constructors-from-projectdescription-models} With the aim of improving the readability and expressiveness of the APIs, we decided to remove the `init` constructors from all the `ProjectDescription` models. Every model now provides a static constructor that you can use to create instances of the models. If you were using the `init` constructors, you'll have to update your project to use the static constructors instead. > [!TIP] NAMING CONVENTION > The naming convention that we follow is to use the name of the model as the name of the static constructor. For example, the static constructor for the `Target` model is `Target.target`. ### Renamed `--no-cache` to `--no-binary-cache` {#renamed-nocache-to-nobinarycache} Because the `--no-cache` flag was ambiguous, we decided to rename it to `--no-binary-cache` to make it clear that it refers to the binary cache. If you were using the `--no-cache` flag, you'll have to update your project to use the `--no-binary-cache` flag instead. ### Renamed `tuist fetch` to `tuist install` {#renamed-tuist-fetch-to-tuist-install} We renamed the `tuist fetch` command to `tuist install` to align with the industry convention. If you were using the `tuist fetch` command, you'll have to update your project to use the `tuist install` command instead. ### [Adopt `Package.swift` as the DSL for dependencies](https://github.com/tuist/tuist/pull/5862) {#adopt-packageswift-as-the-dsl-for-dependencieshttpsgithubcomtuisttuistpull5862} Before Tuist 4, you could define dependencies in a `Dependencies.swift` file. This proprietary format broke the support in tools like [Dependabot](https://github.com/dependabot) or [Renovatebot](https://github.com/renovatebot/renovate) to automatically update dependencies. Moreover, it introduced unnecessary indirections for users. Therefore, we decided to embrace `Package.swift` as the only way to define dependencies in Tuist. If you were using the `Dependencies.swift` file, you'll have to move the content from your `Tuist/Dependencies.swift` to a `Package.swift` at the root, and use the `#if TUIST` directive to configure the integration. You can read more about how to integrate Swift Package dependencies here ### Renamed `tuist cache warm` to `tuist cache` {#renamed-tuist-cache-warm-to-tuist-cache} For brevity, we decided to rename the `tuist cache warm` command to `tuist cache`. If you were using the `tuist cache warm` command, you'll have to update your project to use the `tuist cache` command instead. ### Renamed `tuist cache print-hashes` to `tuist cache --print-hashes` {#renamed-tuist-cache-printhashes-to-tuist-cache-printhashes} We decided to rename the `tuist cache print-hashes` command to `tuist cache --print-hashes` to make it clear that it's a flag of the `tuist cache` command. If you were using the `tuist cache print-hashes` command, you'll have to update your project to use the `tuist cache --print-hashes` flag instead. ### Removed caching profiles {#removed-caching-profiles} Before Tuist 4, you could define caching profiles in `Tuist/Config.swift` which contained a configuration for the cache. We decided to remove this feature because it could lead to confusion when using it in the generation process with a profile other than the one that was used to generate the project. Moreover, it could lead to users using a debug profile to build a release version of the app, which could lead to unexpected results. In its place, we introduced the `--configuration` option, which you can use to specify the configuration you want to use when generating the project. If you were using caching profiles, you'll have to update your project to use the `--configuration` option instead. ### Removed `--skip-cache` in favor of arguments {#removed-skipcache-in-favor-of-arguments} We removed the flag `--skip-cache` from the `generate` command in favor of controlling for which targets the binary cache should be skipped by using the arguments. If you were using the `--skip-cache` flag, you'll have to update your project to use the arguments instead. ::: code-group ```bash [Before] tuist generate --skip-cache Foo ``` ```bash [After] tuist generate Foo ``` ::: ### [Dropped signing capabilities](https://github.com/tuist/tuist/pull/5716) {#dropped-signing-capabilitieshttpsgithubcomtuisttuistpull5716} Signing is already solved by community tooling like [Fastlane](https://fastlane.tools/) and Xcode itself, which do a much better job at that. We felt that signing was an stretch goal for Tuist, and that it was better to focus on the core features of the project. If you were using Tuist signing capabilities, which consisted of encrypting the certificates and profiles in the repository and installing them in the right places at generation time, you might want to replicate that logic in your own scripts that run before project generation. In particular: - A script that decrypts the certificates and profiles using a key either stored in the file-system or in an environment variable, and installs certificates in the keychain, and the provisioning profiles in the directory `~/Library/MobileDevice/Provisioning\ Profiles`. - A script that can take an existing profiles and certificates and encrypt them. > [!TIP] SIGNING REQUIREMENTS > Signing requires the right certificates to be present in the keychain and the provisioning profiles to be present in the directory `~/Library/MobileDevice/Provisioning\ Profiles`. You can use the `security` command-line tool to install certificates in the keychain and the `cp` command to copy the provisioning profiles to the right directory. ### Dropped Carthage integration via `Dependencies.swift` {#dropped-carthage-integration-via-dependenciesswift} Before Tuist 4, Carthage dependencies could be defined in a `Dependencies.swift` file, which users could then fetch by running `tuist fetch`. We also felt that this was a stretch goal for Tuist, specially considering a future where Swift Package Manager would be the preferred way to manage dependencies. If you were using Carthage dependencies, you'll have to use `Carthage` directly to pull the pre-compiled frameworks and XCFrameworks into Carthage's standard directory, and then reference those binaries from your tagets using the `TargetDependency.xcframework` and `TargetDependency.framework` cases. > [!NOTE] CARTHAGE IS STILL SUPPORTED > Some users understood that we dropped Carthage support. We didn't. The contract between Tuist and Carthage's output is to system-stored frameworks and XCFrameworks. The only thing that changed is who is responsible for fetching the dependencies. It used to be Tuist through Carthage, now it's Carthage. ### Dropped the `TargetDependency.packagePlugin` API {#dropped-the-targetdependencypackageplugin-api} Before Tuist 4, you could define a package plugin dependency using the `TargetDependency.packagePlugin` case. After seeing the Swift Package Manager introducing new package types, we decided to iterate on the API towards something that would be more flexible and future-proof. If you were using `TargetDependency.packagePlugin`, you'll have to use `TargetDependency.package` instead, and pass the type of package you want to use as an argument. ### [Dropped deprecated APIs](https://github.com/tuist/tuist/pull/5560) {#dropped-deprecated-apishttpsgithubcomtuisttuistpull5560} We removed the APIs that were marked as deprecated in Tuist 3. If you were using any of the deprecated APIs, you'll have to update your project to use the new APIs. --- URL: "/pt/references/examples/[example]" LLMS_URL: "/pt/references/examples/[example].md" editLink: false titleTemplate: ":title · Examples · References · Tuist" --- Check out example --- URL: "/pt" LLMS_URL: "/pt.md" title: "What is Tuist?" description: "Extend your Apple native tooling to better apps at scale." --- # From idea to the store We are the only **integrated extension of Apple's native toolchain to build better apps faster.**
## Installation Install Tuist and run `tuist init` to get started: ::: code-group ```bash [Homebrew] brew tap tuist/tuist brew install --formula tuist tuist init ``` ```bash [Mise] mise x tuist@latest -- tuist init ``` ::: Check out our installation guide for more details. ## Discover more Try out Tuist in minutes and learn how to get the most out of Tuist. ## Watch our latest talks Explore our team's presentations. Stay informed and gain expertise. ## Join the community See the source code, connect with others, and get connected. --- URL: "/pt/guides/tuist/about" LLMS_URL: "/pt/guides/tuist/about.md" title: "About Tuist" titleTemplate: ":title · Guides · Tuist" description: "Extend your Apple native tooling to better apps at scale." --- # About Tuist {#about-tuist} In the world of app development, particularly for platforms like Apple's, organizations often encounter **productivity roadblocks.** These can include sluggish compilation times, unreliable tests, and intricate automation workflows that drain resources. Traditionally, companies address these issues by forming dedicated platform teams. These specialists maintain codebase health and integrity, freeing other developers to focus on feature creation. However, this approach can be expensive and risky, as the departure of key team members can severely impact productivity. ## What {#what} **Tuist is a toolchain designed to accelerate and enhance app development.** We integrate seamlessly with official tools and systems, meeting developers in familiar territory. By shouldering the burden of tool and system integration, we enable teams to channel their energy into feature development and improving the overall developer experience. In essence, Tuist serves as your virtual platform team. We're with you every step of the way - from the spark of an app idea to its user launch - tackling challenges as they arise. Tuist is comprised of a [CLI](https://github.com/tuist/tuist), which is the main entry point for developers, and a server that the CLI integrates with to persist state and integrate with other publicly available services. ## Why {#why} Why choose Tuist? Here are compelling reasons: ### Simplify 🌱 {#simplify} As projects grow and span multiple platforms, modularization becomes crucial. Tuist streamlines this complexity, offering tools to optimize and better understand your project's structure. **Further reading:** Projects ### Optimize workflows 🚀 {#optimize-workflows} Leveraging project information, Tuist enhances efficiency through selective test execution and deterministic binary reuse across builds. **Further reading:** Cache, Selective testing, Registry, and Previews ### Foster healthy project evolution 📈 {#foster-healthy-project-evolution} We provide insights into your project's dynamics and expert guidance for informed decision-making. This approach prevents the frustration and productivity loss associated with unhealthy projects, which can lead to developer attrition and missed business goals. **Further reading:** Server ### Break down silos 💜 {#break-down-silos} Unlike platform-specific ecosystems (e.g., Xcode's contained environment), Tuist offers web-centric experiences and integrates seamlessly with popular tools like Slack, Prometheus, and GitHub, enhancing cross-tool collaboration. **Further reading:** Projects --- If you want to know more about Tuist, the project, and the company, you can check out our [handbook](https://handbook.tuist.io/), which contains detailed information about our vision, values, and the team behind Tuist. --- URL: "/pt/guides/share/previews" LLMS_URL: "/pt/guides/share/previews.md" title: "Previews" titleTemplate: ":title · Share · Guides · Tuist" description: "Learn how to generate and share previews of your apps with anyone." --- # Previews {#previews} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project When building an app, you may want to share it with others to get feedback. Traditionally, this is something that teams do by building, signing, and pushing their apps to platforms like Apple's [TestFlight](https://developer.apple.com/testflight/). However, this process can be cumbersome and slow, especially when you're just looking for quick feedback from a colleague or a friend. To make this process more streamlined, Tuist provides a way to generate and share previews of your apps with anyone. > [!IMPORTANT] DEVICE BUILDS NEED TO BE SIGNED > When building for device, it is currently your responsibility to ensure the app is signed correctly. We plan to streamline this in the future. :::code-group ```bash [Tuist Project] tuist build App # Build the app for the simulator tuist build App -- -destination 'generic/platform=iOS' # Build the app for the device tuist share App ``` ```bash [Xcode Project] xcodebuild -scheme App -project App.xcodeproj -configuration Debug # Build the app for the simulator xcodebuild -scheme App -project App.xcodeproj -configuration Debug -destination 'generic/platform=iOS' # Build the app for the device tuist share App --configuration Debug --platforms iOS tuist share App.ipa # Share an existing .ipa file ``` ::: The command will generate a link that you can share with anyone to run the app – either on a simulator or an actual device. All they'll need to do is to run the command below: ```bash tuist run {url} tuist run --device "My iPhone" {url} # Run the app on a specific device ``` When sharing an `.ipa` file, you can download the app directly from the mobile device using the Preview link. The links to `.ipa` previews are by default _public_. In the future, you will have an option to make them private, so that the recipient of the link would need to authenticate with their Tuist account to download the app. `tuist run` also enables you to run a latest preview based on a specifier such as `latest`, branch name, or a specific commit hash: ```bash tuist run App@latest # Runs latest App preview associated with the project's default branch tuist run App@my-feature-branch # Runs latest App preview associated with a given branch tuist run App@00dde7f56b1b8795a26b8085a781fb3715e834be # Runs latest App preview associated with a given git commit sha ``` > [!IMPORTANT] PREVIEWS' VISIBILITY > Only people with access to the organization the project belongs to can access the previews. We plan to add support for expiring links. ## Tuist macOS app {#tuist-macos-app}

Tuist

Download
To make running Tuist Previews even easier, we developed a Tuist macOS menu bar app. Instead of running Previews via the Tuist CLI, you can [download](https://tuist.dev/download) the macOS app. You can also install the app by running `brew install --cask tuist/tuist/tuist`. When you now click on "Run" in the Preview page, the macOS app will automatically launch it on your currently selected device. > [!IMPORTANT] REQUIREMENTS > To download Previews, you need to first authenticate with the `tuist auth login` command. > In the future, you will be able to authenticate directly in the app. > > Additionally, you need to have Xcode locally installed. ## Pull/merge request comments {#pullmerge-request-comments} > [!IMPORTANT] INTEGRATION WITH GIT PLATFORM REQUIRED > To get automatic pull/merge request comments, integrate your remote project with a Git platform. Testing new functionality should be a part of any code review. But having to build an app locally adds unnecessary friction, often leading to developers skipping testing functionality on their device at all. But _what if each pull request contained a link to the build that would automatically run the app on a device you selected in the Tuist macOS app?_ Once your Tuist project is connected with your Git platform such as [GitHub](https://github.com), add a `tuist share MyApp` to your CI workflow. Tuist will then post a Preview link directly in your pull requests: ![GitHub app comment with a Tuist Preview link](/images/guides/share/github-app-with-preview.png) ## README badge {#readme-badge} To make Tuist Previews more visible in your repository, you can add a badge to your `README` file that points to the latest Tuist Preview: [![Tuist Preview](https://tuist.dev/Dimillian/IcySky/previews/latest/badge.svg)](https://tuist.dev/Dimillian/IcySky/previews/latest) To add the badge to your `README`, use the following markdown and replace the account and project handles with your own: ``` [![Tuist Preview](https://tuist.dev/{account-handle}/{project-handle}/previews/latest/badge.svg)](https://tuist.dev/{account-handle}/{project-handle}/previews/latest) ``` ## Automations {#automations} You can use the `--json` flag to get a JSON output from the `tuist share` command: ``` tuist share --json ``` The JSON output is useful to create custom automations, such as posting a Slack message using your CI provider. The JSON contains a `url` key with the full preview link and a `qrCodeURL` key with the URL to the QR code image to make it easier to download previews from a real device. An example of a JSON output is below: ```json { "id": 1234567890, "url": "https://cloud.tuist.io/preview/1234567890", "qrCodeURL": "https://cloud.tuist.io/preview/1234567890/qr-code.svg" } ``` --- URL: "/pt/guides/quick-start/install-tuist" LLMS_URL: "/pt/guides/quick-start/install-tuist.md" title: "Install Tuist" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "Learn how to install Tuist in your environment." --- # Install Tuist {#install-tuist} The Tuist CLI consists of an executable, dynamic frameworks, and a set of resources (for example, templates). Although you could manually build Tuist from [the sources](https://github.com/tuist/tuist), **we recommend using one of the following installation methods to ensure a valid installation.** ### Mise {#recommended-mise} :::info Mise is a recommended alternative to [Homebrew](https://brew.sh) if you are a team or organization that needs to ensure deterministic versions of tools across different environments. ::: You can install Tuist through any of the following commands: ```bash mise install tuist # Install the current version specified in .tool-versions/.mise.toml mise install tuist@x.y.z # Install a specific version number mise install tuist@3 # Install a fuzzy version number ``` Note that unlike tools like Homebrew, which install and activate a single version of the tool globally, **Mise requires the activation of a version** either globally or scoped to a project. This is done by running `mise use`: ```bash mise use tuist@x.y.z # Use tuist-x.y.z in the current project mise use tuist@latest # Use the latest tuist in the current directory mise use -g tuist@x.y.z # Use tuist-x.y.z as the global default mise use -g tuist@system # Use the system's tuist as the global default ``` ### Homebrew {#recommended-homebrew} You can install Tuist using [Homebrew](https://brew.sh) and [our formulas](https://github.com/tuist/homebrew-tuist): ```bash brew tap tuist/tuist brew install --formula tuist brew install --formula tuist@x.y.z ``` :::tip VERIFYING THE AUTHENTICITY OF THE BINARIES ```bash curl -fsSL "https://docs.tuist.dev/verify.sh" | bash ``` ::: --- URL: "/pt/guides/quick-start/get-started" LLMS_URL: "/pt/guides/quick-start/get-started.md" title: "Get started" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "Learn how to install Tuist in your environment." --- # Get started {#get-started} The easiest way to get started with Tuist in any directory or in the directory of your Xcode project or workspace: ::: code-group ```bash [Mise] mise x tuist@latest -- tuist init ``` ```bash [Global Tuist (Homebrew)] tuist init ``` ::: The command will walk you through the steps to create a generated project or integrate an existing Xcode project or workspace. It helps you connect your setup to the remote server, giving you access to features like selective testing, previews, and the registry. > [!NOTE] MIGRATE AN EXISTING PROJECT > If you want to migrate an existing project to generated projects to improve the developer experience and take advantage of our cache, check out our migration guide. --- URL: "/pt/guides/quick-start/gather-insights" LLMS_URL: "/pt/guides/quick-start/gather-insights.md" title: "Gather insights" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "Learn how to gather insights about your project." --- # Gather insights {#gather-insights} Tuist can integrate with a server to extend its capabilities. One of those capabilities is gathering insights about your project and builds. All you need is to have an account with a project in the server. First of all, you'll need to authenticate by running: ```bash tuist auth login ``` ## Create a project {#create-a-project} You can then create a project by running: ```bash tuist project create my-handle/MyApp # Tuist project my-handle/MyApp was successfully created 🎉 {#tuist-project-myhandlemyapp-was-successfully-created-} ``` Copy `my-handle/MyApp`, which represents the full handle of the project. ## Connect projects {#connect-projects} After creating the project on the server, you'll have to connect it to your local project. Run `tuist edit` and edit the `Tuist.swift` file to include the full handle of the project: ```swift import ProjectDescription let tuist = Tuist(fullHandle: "my-handle/MyApp") ``` Voilà! You're now ready to gather insights about your project and builds. Run `tuist test` to run the tests reporting the results to the server. > [!NOTE] > Tuist enqueues the results locally and tries to send them without blocking the command. Therefore, they might not be sent immediately after the command finishes. In CI, the results are sent immediately. ![An image that shows a list of runs in the server](/images/guides/quick-start/runs.png) Having data from your projects and builds is crucial in making informed decisions. Tuist will continue to extend its capabilities, and you'll benefit from them without having to change your project configuration. Magic, right? 🪄 --- URL: "/pt/guides/quick-start/add-dependencies" LLMS_URL: "/pt/guides/quick-start/add-dependencies.md" title: "Add dependencies" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "Learn how to add dependencies to your first Swift project" --- # Add dependencies {#add-dependencies} It's common for projects to depend on third-party libraries to provide additional functionality. To do so, run the following command to have the best experience editing your project: ```bash tuist edit ``` An Xcode project will open containing your project files. Edit the `Package.swift` and add the ```swift // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies .package(url: "https://github.com/onevcat/Kingfisher", .upToNextMajor(from: "7.12.0")) // [!code ++] ] ) ``` Then edit the application target in your project to declare `Kingfisher` as a dependency: ```swift import ProjectDescription let project = Project( name: "MyApp", targets: [ .target( name: "MyApp", destinations: .iOS, product: .app, bundleId: "io.tuist.MyApp", infoPlist: .extendingDefault( with: [ "UILaunchStoryboardName": "LaunchScreen.storyboard", ] ), sources: ["MyApp/Sources/**"], resources: ["MyApp/Resources/**"], dependencies: [ .external(name: "Kingfisher") // [!code ++] ] ), .target( name: "MyAppTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyAppTests", infoPlist: .default, sources: ["MyApp/Tests/**"], resources: [], dependencies: [.target(name: "MyApp")] ), ] ) ``` Then run `tuist install` to resolve and pull the dependencies using the [Swift Package Manager](https://www.swift.org/documentation/package-manager/). > [!NOTE] SPM AS A DEPENDENCY RESOLVER > Tuist recommended approach to dependencies uses the Swift Package Manager (SPM) only to resolve dependencies. Tuist then converts them into Xcode projects and targets for maximum configurability and control. ## Visualize the project {#visualize-the-project} You can visualize the project structure by running: ```bash tuist graph ``` The command will output and open a `graph.png` file in the project's directory: ![Project graph](/images/guides/quick-start/graph.png) ## Use the dependency {#use-the-dependency} Run `tuist generate` to open the project in Xcode, and make the following changes to the `ContentView.swift` file: ```swift import SwiftUI import Kingfisher // [!code ++] public struct ContentView: View { public init() {} public var body: some View { Text("Hello, World!") // [!code --] .padding() // [!code --] KFImage(URL(string: "https://cloud.tuist.io/images/tuist_logo_32x32@2x.png")!) // [!code ++] } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } ``` Run the app from Xcode, and you should see the image loaded from the URL. --- URL: "/pt/guides/develop/test" LLMS_URL: "/pt/guides/develop/test.md" title: "tuist test" titleTemplate: ":title · Develop · Guides · Tuist" description: "Learn how to run tests efficiently with Tuist." --- # Test {#test} Tuist provides a command, `tuist test` to generate the project if needed, and then run the tests with the the platform-specific build tool (e.g. `xcodebuild` for Apple platforms). You might wonder what's the value of using `tuist test` over generating the project with `tuist generate` and running the tests with the platform-specific build tool. - **Single command:** `tuist test` ensures the project is generated if needed before compiling the project. - **Beautified output:** Tuist enriches the output using tools like [xcbeautify](https://github.com/cpisciotta/xcbeautify) that make the output more user-friendly. - Cache: It optimizes the build by deterministically reusing the build artifacts from a remote cache. - Smart runner: It runs only the tests that need to be run, saving time and resources. - Flakiness: Prevent, detect, and fix flaky tests. ## Usage {#usage} To run the tests of a project, you can use the `tuist test` command. This command will generate the project if needed, and then run the tests using the platform-specific build tool. We support the use of the `--` terminator to forward all subsequent arguments directly to the build tool. ::: code-group ```bash [Running scheme tests] tuist test MyScheme ``` ```bash [Running all tests without binary cache] tuist test --no-binary-cache ``` ```bash [Running all tests without selective testing] tuist test --no-selective-testing ``` ::: ## Pull/merge request comments {#pullmerge-request-comments} > [!IMPORTANT] REQUIREMENTS > To get automatic pull/merge request comments, integrate your remote project with a Git platform. When running tests in your CI environments we can correlate the test results with the pull/merge request that triggered the CI build. This allows us to post a comment on the pull/merge request with the test results. ![GitHub App example](/images/contributors/scheme-arguments.png) --- URL: "/pt/guides/develop/selective-testing/xcodebuild" LLMS_URL: "/pt/guides/develop/selective-testing/xcodebuild.md" title: "xcodebuild" titleTemplate: ":title · Selective testing · Develop · Guides · Tuist" description: "Learn how to leverage selective testing with `xcodebuild`." --- # xcodebuild {#xcodebuild} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project To run tests selectively using `xcodebuild`, you can prepend your `xcodebuild` command with `tuist` – for example, `tuist xcodebuild test -scheme App`. The command hashes your project and on success, it persists the hashes to determine what has changed in future runs. In future runs `tuist xcodebuild test` transparently uses the hashes to filter down the tests to run only the ones that have changed since the last successful test run. For example, assuming the following dependency graph: - `FeatureA` has tests `FeatureATests`, and depends on `Core` - `FeatureB` has tests `FeatureBTests`, and depends on `Core` - `Core` has tests `CoreTests` `tuist xcodebuild test` will behave as such: | Action | Description | Internal state | | ---------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | `tuist xcodebuild test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The hashes of `FeatureATests`, `FeatureBTests` and `CoreTests` are persisted | | `FeatureA` is updated | The developer modifies the code of a target | Same as before | | `tuist xcodebuild test` invocation | Runs the tests in `FeatureATests` because it hash has changed | The new hash of `FeatureATests` is persisted | | `Core` is updated | The developer modifies the code of a target | Same as before | | `tuist xcodebuild test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The new hash of `FeatureATests` `FeatureBTests`, and `CoreTests` are persisted | To use `tuist xcodebuild test` on your CI, follow the instructions in the Continuous integration guide. --- URL: "/pt/guides/develop/selective-testing/generated-project" LLMS_URL: "/pt/guides/develop/selective-testing/generated-project.md" title: "Generated project" titleTemplate: ":title · Selective testing · Develop · Guides · Tuist" description: "Learn how to leverage selective testing with a generated project." --- # Generated project {#generated-project} > [!IMPORTANT] REQUIREMENTS > > - A generated project > - A Tuist account and project To run tests selectively with your generated project, use the `tuist test` command. The command hashes your Xcode project the same way it does for warming the cache, and on success, it persists the hashes on to determine what has changed in future runs. In future runs `tuist test` transparently uses the hashes to filter down the tests to run only the ones that have changed since the last successful test run. For example, assuming the following dependency graph: - `FeatureA` has tests `FeatureATests`, and depends on `Core` - `FeatureB` has tests `FeatureBTests`, and depends on `Core` - `Core` has tests `CoreTests` `tuist test` will behave as such: | Action | Description | Internal state | | ----------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | `tuist test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The hashes of `FeatureATests`, `FeatureBTests` and `CoreTests` are persisted | | `FeatureA` is updated | The developer modifies the code of a target | Same as before | | `tuist test` invocation | Runs the tests in `FeatureATests` because it hash has changed | The new hash of `FeatureATests` is persisted | | `Core` is updated | The developer modifies the code of a target | Same as before | | `tuist test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The new hash of `FeatureATests` `FeatureBTests`, and `CoreTests` are persisted | `tuist test` integrates directly with binary caching to use as many binaries from your local or remote storage to improve the build time when running your test suite. The combination of selective testing with binary caching can dramatically reduce the time it takes to run tests on your CI. ## UI Tests {#ui-tests} Tuist supports selective testing of UI tests. However, Tuist needs to know the destination in advance. Only if you specify the `destination` parameter, Tuist will run the UI tests selectively, such as: ```sh tuist test --device 'iPhone 14 Pro' # or tuist test -- -destination 'name=iPhone 14 Pro' # or tuist test -- -destination 'id=SIMULATOR_ID' ``` --- URL: "/pt/guides/develop/selective-testing" LLMS_URL: "/pt/guides/develop/selective-testing.md" title: "Selective testing" titleTemplate: ":title · Develop · Guides · Tuist" description: "Use selective testing to run only the tests that have changed since the last successful test run." --- # Selective testing {#selective-testing} As your project grows, so does the amount of your tests. For a long time, running all tests on every PR or push to `main` takes tens of seconds. But this solution does not scale to thousands of tests your team might have. On every test run on the CI, you most likely re-run all the tests, regardless of the changes. Tuist's selective testing helps you to drastically speed up running the tests themselves by running only the tests that have changed since the last successful test run based on our hashing algorithm. Selective testing works with `xcodebuild`, which supports any Xcode project, or if you generate your projects with Tuist, you can use the `tuist test` command instead that provides some extra convenience such as integration with the binary cache. To get started with selective testing, follow the instructions based on your project setup: - xcodebuild - Generated project > [!WARNING] MODULE VS FILE-LEVEL GRANULARITY > Due to the impossibility of detecting the in-code dependencies between tests and sources, the maximum granularity of selective testing is at the target level. Therefore, we recommend keeping your targets small and focused to maximize the benefits of selective testing. ## Pull/merge request comments {#pullmerge-request-comments} > [!IMPORTANT] INTEGRATION WITH GIT PLATFORM REQUIRED > To get automatic pull/merge request comments, integrate your Tuist project with a Git platform. Once your Tuist project is connected with your Git platform such as [GitHub](https://github.com), and you start using `tuist xcodebuild test` or `tuist test` as part of your CI wortkflow, Tuist will post a comment directly in your pull/merge requests, including which tests were run and which skipped: ![GitHub app comment with a Tuist Preview link](/images/guides/develop/github-app-comment.png) --- URL: "/pt/guides/develop/registry/xcodeproj-integration" LLMS_URL: "/pt/guides/develop/registry/xcodeproj-integration.md" title: "Generated project with the XcodeProj-based package integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in a generated Xcode project with the XcodeProj-based package integration." --- # Generated project with the XcodeProj-based package integration {#generated-project-with-xcodeproj-based-integration} When using the XcodeProj-based integration, you can use the `--replace-scm-with-registry` flag to resolve dependencies from the registry if they are available. Add it to the `installOptions` in your `Tuist.swift` file: ```swift import ProjectDescription let tuist = Tuist( fullHandle: "{account-handle}/{project-handle}", project: .tuist( installOptions: .options(passthroughSwiftPackageManagerArguments: ["--replace-scm-with-registry"]) ) ) ``` If you want to ensure that the registry is used every time you resolve dependencies, you will need to update `dependencies` in your `Tuist/Package.swift` file to use the registry identifier instead of a URL. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `swift-composable-architecture` package, do the following: ```diff dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") + .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ] ``` --- URL: "/pt/guides/develop/registry/xcode-project" LLMS_URL: "/pt/guides/develop/registry/xcode-project.md" title: "Xcode project" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in an Xcode project." --- # Xcode project {#xcode-project} To add packages using the registry in your Xcode project, use the default Xcode UI. You can search for packages in the registry by clicking on the `+` button in the `Package Dependencies` tab in Xcode. If the package is available in the registry, you will see the `tuist.dev` registry in the top right: ![Adding package dependencies](/images/guides/develop/build/registry/registry-add-package.png) > [!NOTE] > Xcode currently doesn't support automatically replacing source control packages with their registry equivalents. You will need to manually remove the source control package and add the registry package to speed up the resolution. --- URL: "/pt/guides/develop/registry/swift-package" LLMS_URL: "/pt/guides/develop/registry/swift-package.md" title: "Swift package" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in a Swift package." --- # Swift package {#swift-package} If you are working on a Swift package, you can use the `--replace-scm-with-registry` flag to resolve dependencies from the registry if they are available: ```bash swift package --replace-scm-with-registry resolve ``` If you want to ensure that the registry is used every time you resolve dependencies, you will need to update `dependencies` in your `Package.swift` file to use the registry identifier instead of a URL. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `swift-composable-architecture` package, do the following: ```diff dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") + .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ] ``` --- URL: "/pt/guides/develop/registry/generated-project" LLMS_URL: "/pt/guides/develop/registry/generated-project.md" title: "Generated project with the Xcode package integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in a generated Xcode project with the Xcode package integration." --- # Generated project with the Xcode package integration {#generated-project-with-xcode-based-integration} If you are using the Xcode's default integration of packages with Tuist Projects, you need to use the registry identifier instead of a URL when adding a package: ```swift import ProjectDescription let project = Project( name: "MyProject", packages: [ // Source control resolution // .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") // Registry resolution .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ], .target( name: "App", product: .app, bundleId: "io.tuist.App", dependencies: [ .package(product: "ComposableArchitecture"), ] ) ) ``` --- URL: "/pt/guides/develop/registry/continuous-integration" LLMS_URL: "/pt/guides/develop/registry/continuous-integration.md" title: "Continuous integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in continuous integration." --- # Continuous Integration (CI) {#continuous-integration-ci} To use the registry on your CI, you need to ensure that you have logged in to the registry by running `tuist registry login` as part of your workflow. > [!NOTE] ONLY XCODE INTEGRATION > Creating a new pre-unlocked keychain is required only if you are using the Xcode integration of packages. Since the registry credentials are stored in a keychain, you need to ensure the keychain can be accessed in the CI environment. Note some CI providers or automation tools like [Fastlane](https://fastlane.tools/) already create a temporary keychain or provide a built-in way how to create one. However, you can also create one by creating a custom step with the following code: ```bash TMP_DIRECTORY=$(mktemp -d) KEYCHAIN_PATH=$TMP_DIRECTORY/keychain.keychain KEYCHAIN_PASSWORD=$(uuidgen) security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security default-keychain -s $KEYCHAIN_PATH security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH ``` `tuist registry login` will then store the credentials in the default keychain. Ensure that your default keychain is created and unlocked _before_ `tuist registry login` is run. Additionally, you need to ensure the `TUIST_CONFIG_TOKEN` environment variable is set. You can create one by following the documentation here. An example workflow for GitHub Actions could then look like this: ```yaml name: Build jobs: build: steps: - # Your set up steps... - name: Create keychain run: | TMP_DIRECTORY=$(mktemp -d) KEYCHAIN_PATH=$TMP_DIRECTORY/keychain.keychain KEYCHAIN_PASSWORD=$(uuidgen) security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security default-keychain -s $KEYCHAIN_PATH security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH - name: Log in to the Tuist Registry env: TUIST_CONFIG_TOKEN: ${{ secrets.TUIST_CONFIG_TOKEN }} run: tuist registry login - # Your build steps ``` ### Incremental resolution across environments {#incremental-resolution-across-environments} Clean/cold resolutions are slightly faster with our registry, and you can experience even greater improvements if you persist the resolved dependencies across CI builds. Note that thanks to the registry, the size of the directory that you need to store and restore is much smaller than without the registry, taking significantly less time. To cache dependencies when using the default Xcode package integration, the best way is to specify a custom `-clonedSourcePackagesDirPath` when resolving dependencies via `xcodebuild`, such as: ```sh xcodebuild -resolvePackageDependencies -clonedSourcePackagesDirPath .build ``` Additionally, you will need to find a path of the `Package.resolved`. You can grab the path by running `ls **/Package.resolved`. The path should look something like `App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved`. For Swift packages and the XcodeProj-based integration, we can use the default `.build` directory located either in the root of the project or in the `Tuist` directory. Make sure the path is correct when setting up your pipeline. Here's an example workflow for GitHub Actions for resolving and caching dependencies when using the default Xcode package integration: ```yaml - name: Restore cache id: cache-restore uses: actions/cache/restore@v4 with: path: .build key: ${{ runner.os }}-${{ hashFiles('App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} restore-keys: .build - name: Resolve dependencies if: steps.cache-restore.outputs.cache-hit != 'true' run: xcodebuild -resolvePackageDependencies -clonedSourcePackagesDirPath .build - name: Save cache id: cache-save uses: actions/cache/save@v4 with: path: .build key: ${{ steps.cache-restore.outputs.cache-primary-key }} ``` --- URL: "/pt/guides/develop/registry" LLMS_URL: "/pt/guides/develop/registry.md" title: "Registry" titleTemplate: ":title · Develop · Guides · Tuist" description: "Optimize your Swift package resolution times by leveraging the Tuist Registry." --- # Registry {#registry} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project As the number of dependencies grows, so does the time to resolve them. While other package managers like [CocoaPods](https://cocoapods.org/) or [npm](https://www.npmjs.com/) are centralized, Swift Package Manager is not. Because of that, SwiftPM needs to resolve dependencies by doing a deep clone of each repository, which can be time-consuming. To address this, Tuist provides an implementation of the [Package Registry](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/PackageRegistryUsage.md), so you can download only the commit you _actually need_. The packages in the registry are based on the [Swift Package Index](https://swiftpackageindex.com/) – if you can find a package there, the package is also available in the Tuist Registry. Additionally, the packages are distributed across the globe using an edge storage for minimum latency when resolving them. ## Usage {#usage} To set up and log in to the registry, run the following command in your project's directory: ```bash tuist registry setup ``` This command generates a registry configuration files and logs you in to the registry. To ensure the rest of your team can access the registry, ensure the generated files is committed and that your team members run the following command to log in: ```bash tuist registry login ``` Now you can access the registry! To resolve dependencies from the registry instead of from source control, continue reading based on your project setup: - Xcode project - Generated project with the Xcode package integration - Generated project with the XcodeProj-based package integration - Swift package To set up the registry on the CI, follow this guide: Continuous integration. ### Package registry identifiers {#package-registry-identifiers} If you want to ensure that the registry is used every time you resolve dependencies, you will need to update `dependencies` in your `Package.swift` file to use the registry identifier instead of a URL. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `swift-composable-architecture` package, do the following: > [!NOTE] > The identifier can't contain more than one dot. If the repository name contains a dot, it's replaced with an underscore. > For example, the `https://github.com/groue/GRDB.swift` package would have the registry identifier `groue.GRDB_swift`. --- URL: "/pt/guides/develop/projects/tma-architecture" LLMS_URL: "/pt/guides/develop/projects/tma-architecture.md" title: "The Modular Architecture (TMA)" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about The Modular Architecture (TMA) and how to structure your projects using it." --- # The Modular Architecture (TMA) {#the-modular-architecture-tma} TMA is an architectural approach to structure Apple OS applications to enable scalability, optimize build and test cycles, and ensure good practices in your team. Its core idea is to build your apps by building independent features that are interconnected using clear and concise APIs. These guidelines introduce the principles of the architecture, helping you identify and organize your application features in different layers. It also introduces tips, tools, and advice if you decide to use this architecture. > [!INFO] µFEATURES > This architecture was previously known as µFeatures. We've renamed it to The Modular Architecture (TMA) to better reflect its purpose and the principles behind it. ## Core principle {#core-principle} Developers should be able to **build, test, and try** their features fast, independently of the main app, and while ensuring Xcode features like UI previews, code completion, and debugging work reliably. ## What is a module {#what-is-a-module} A module represents an application feature and is a combination of the following five targets (where target referts to an Xcode target): - **Source:** Contains the feature source code (Swift, Objective-C, C++, JavaScript...) and its resources (images, fonts, storyboards, xibs). - **Interface:** It's a companion target that contains the public interface and models of the feature. - **Tests:** Contains the feature unit and integration tests. - **Testing:** Provides testing data that can be used in tests and the example app. It also provides mocks for module classes and protocols that can be used by other features as we'll see later. - **Example:** Contains an example app that developers can use to try out the feature under certain conditions (different languages, screen sizes, settings). We recommend following a naming convention for targets, something that you can enforce in your project thanks to Tuist's DSL. | Target | Dependencies | Content | | ------------------ | --------------------------- | --------------------------- | | `Feature` | `FeatureInterface` | Source code and resources | | `FeatureInterface` | - | Public interface and models | | `FeatureTests` | `Feature`, `FeatureTesting` | Unit and integration tests | | `FeatureTesting` | `FeatureInterface` | Testing data and mocks | | `FeatureExample` | `FeatureTesting`, `Feature` | Example app | > [!TIP] UI Previews > `Feature` can use `FeatureTesting` as a Development Asset to allow for UI previews > [!IMPORTANT] COMPILER DIRECTIVES INSTEAD OF TESTING TARGETS > Alternatively, you can use compiler directives to include test data and mocks in the `Feature` or `FeatureInterface` targets when compiling for `Debug`. You simplify the graph, but you'll end up compiling code that you won't need for running the app. ## Why a module {#why-a-module} ### Clear and concise APIs {#clear-and-concise-apis} When all the app source code lives in the same target it is very easy to build implicit dependencies in code and end up with the so well-known spaghetti code. Everything is strongly coupled, the state is sometimes unpredictable, and introducing new changes become a nightmare. When we define features in independent targets we need to design public APIs as part of our feature implementation. We need to decide what should be public, how our feature should be consumed, what should remain private. We have more control over how we want our feature clients to use the feature and we can enforce good practices by designing safe APIs. ### Small modules {#small-modules} [Divide and conquer](https://en.wikipedia.org/wiki/Divide_and_conquer). Working in small modules allows you to have more focus and test and try the feature in isolation. Moreover, development cycles are much faster since we have a more selective compilation, compiling only the components that are necessary to get our feature working. The compilation of the whole app is only necessary at the very end of our work, when we need to integrate the feature into the app. ### Reusability {#reusability} Reusing code across apps and other products like extensions is encouraged using frameworks or libraries. By building modules reusing them is pretty straightforward. We can build an iMessage extension, a Today Extension, or a watchOS application by just combining existing modules and adding _(when necessary)_ platform-specific UI layers. ## Dependencies {#dependencies} When a module depends on another module, it declares a dependency against its interface target. The benefit of this is two-fold. It prevents the implementation of a module to be coupled to the implementation of another module, and it speeds up clean builds because they only have to compile the implementation of our feature, and the interfaces of direct and transitive dependencies. This approach is inspired by SwiftRock's idea of [Reducing iOS Build Times by using Interface Modules](https://swiftrocks.com/reducing-ios-build-times-by-using-interface-targets). Depending on interfaces requires apps to build the graph of implementations at runtime, and dependency-inject it into the modules that need it. Although TMA is non-opinionated about how to do this, we recommend using dependency-injection solutions or patterns or solutions that don't add built-time indirections or use platform APIs that were not designed for this purpose. ## Product types {#product-types} When building a module, you can choose between **libraries and frameworks**, and **static and dynamic linking** for the targets. Without Tuist, making this decision is a bit more complex because you need to configure the dependency graph manually. However, thanks to Tuist Projects, this is no longer a problem. We recommend using dynamic libraries or frameworks during development using bundle accessors to decouple the bundle-accessing logic from the library or framework nature of the target. This is key for fast compilation times and to ensure [SwiftUI Previews](https://developer.apple.com/documentation/swiftui/previews-in-xcode) work reliably. And static libraries or frameworks for the release builds to ensure the app boots fast. You can leverage dynamic configuration to change the product type at generation-time: ```bash # You'll have to read the value of the variable from the manifest {#youll-have-to-read-the-value-of-the-variable-from-the-manifest} # and use it to change the linking type {#and-use-it-to-change-the-linking-type} TUIST_PRODUCT_TYPE=static-library tuist generate ``` ```swift // You can place this in your manifest files or helpers // and use the returned value when instantiating targets. func productType() -> Product { if case let .string(productType) = Environment.productType { return productType == "static-library" ? .staticLibrary : .framework } else { return .framework } } ``` > [!IMPORTANT] MERGEABLE LIBRARIES > Apple attempted to alleviate the cumbersomeness of switching between static and dynamic libraries by introducing [mergeable libraries](https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries). However, that introduces build-time non-determinism that makes your build non-reproducible and harder to optimize so we don't recommend using it. ## Code {#code} TMA is non-opinionated about the code architecture and patterns for your modules. However, we'd like to share some tips based on our experience: - **Leveraging the compiler is great.** Over-leveraging the compiler might end up being non-productive and cause some Xcode features like previews to work unreliably. We recommend using the compiler to enforce good practices and catch errors early, but not to the point that it makes the code harder to read and maintain. - **Use Swift Macros sparingly.** They can be very powerful but can also make the code harder to read and maintain. - **Embrace the platform and the language, don't abstract them.** Trying to come up with ellaborated abstraction layers might end up being counterproductive. The platform and the language are powerful enough to build great apps without the need for additional abstraction layers. Use good programming and design patterns as a reference to build your features. ## Resources {#resources} - [Building µFeatures](https://speakerdeck.com/pepibumur/building-ufeatures) - [Framework Oriented Programming](https://speakerdeck.com/pepibumur/framework-oriented-programming-mobilization-dot-pl) - [A Journey into frameworks and Swift](https://speakerdeck.com/pepibumur/a-journey-into-frameworks-and-swift) - [Leveraging frameworks to speed up our development on iOS - Part 1](https://developers.soundcloud.com/blog/leveraging-frameworks-to-speed-up-our-development-on-ios-part-1) - [Library Oriented Programming](https://academy.realm.io/posts/justin-spahr-summers-library-oriented-programming/) - [Building Modern Frameworks](https://developer.apple.com/videos/play/wwdc2014/416/) - [The Unofficial Guide to xcconfig files](https://pewpewthespells.com/blog/xcconfig_guide.html) - [Static and Dynamic Libraries](https://pewpewthespells.com/blog/static_and_dynamic_libraries.html) --- URL: "/pt/guides/develop/projects/templates" LLMS_URL: "/pt/guides/develop/projects/templates.md" title: "Templates" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to create and use templates in Tuist to generate code in your projects." --- # Templates {#templates} In projects with an established architecture, developers might want to bootstrap new components or features that are consistent with the project. With `tuist scaffold` you can generate files from a template. You can define your own templates or use the ones that are vendored with Tuist. These are some scenarios where scaffolding might be useful: - Create a new feature that follows a given architecture: `tuist scaffold viper --name MyFeature`. - Create new projects: `tuist scaffold feature-project --name Home` > [!NOTE] NON-OPINIONATED > Tuist is not opinionated about the content of your templates, and what you use them for. They are only required to be in a specific directory. ## Defining a template {#defining-a-template} To define templates, you can run `tuist edit` and then create a directory called `name_of_template` under `Tuist/Templates` that represents your template. So if you are creating a template called `framework`, you should create a new directory `framework` at `Tuist/Templates` with a manifest file called `framework.swift` that could look like this: To define templates, you can run `tuist edit` and then create a directory called `name_of_template` under `Tuist/Templates` that represents your template. To define templates, you can run `tuist edit` and then create a directory called `name_of_template` under `Tuist/Templates` that represents your template. ```swift import ProjectDescription let nameAttribute: Template.Attribute = .required("name") let template = Template( description: "Custom template", attributes: [ nameAttribute, .optional("platform", default: "ios"), ], items: [ .string( path: "Project.swift", contents: "My template contents of name \(nameAttribute)" ), .file( path: "generated/Up.swift", templatePath: "generate.stencil" ), .directory( path: "destinationFolder", sourcePath: "sourceFolder" ), ] ) ``` ## Using a template {#using-a-template} After defining the template, we can use it from the `scaffold` command: ```bash tuist scaffold name_of_template --name Name --platform macos ``` > [!NOTE] > Since platform is an optional argument, we can also call the command without the `--platform macos` argument. If `.string` and `.files` don't provide enough flexibility, you can leverage the [Stencil](https://stencil.fuller.li/en/latest/) templating language via the `.file` case. Besides that, you can also use additional filters defined here. Using string interpolation, `\(nameAttribute)` above would resolve to `{{ name }}`. If you'd like to use Stencil filters in the template definition, you can use that interpolation manually and add any filters you like. For example, you might use `{ { name | lowercase } }` instead of `\(nameAttribute)` to get the lowercased value of the name attribute. You can also use `.directory` which gives the possibility to copy entire folders to a given path. > [!TIP] PROJECT DESCRIPTION HELPERS > Templates support the use of project description helpers to reuse code across templates. --- URL: "/pt/guides/develop/projects/synthesized-files" LLMS_URL: "/pt/guides/develop/projects/synthesized-files.md" title: "Synthesized files" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about synthesized files in Tuist projects." --- # Synthesized files {#synthesized-files} Tuist can generate files and code at generation-time to bring some convenience to managing and working with Xcode projects. In this page you'll learn about this functionality, and how you can use it in your projects. ## Target resources {#target-resources} Xcode projects support adding resources to targets. However, they present teams with a few challenges, specially when working with a modular project where sources and resources are often moved around: - **Inconsistent runtime access**: Where the resources end up in the final product and how you access them depends on the target product. For example, if your target represents an application, the resources are copied to the application bundle. This leads to code accessing the resources that makes assumptions on the bundle structure, which is not ideal because it makes the code harder to reason about and the resources to move around. - **Products that don't support resources**: There are certain products like static libraries that are not bundles and therefore don't support resources. Because of that, you either have to resort to a different product type, for example frameworks, that might add some overhead on your project or app. For example, static frameworks will be linked statically to the final product, and a build phase is required to only copy the resources to the final product. Or dynamic frameworks, where Xcode will copy both the binary and the resources into the final product, but it'll increase the startup time of your app because the framework needs to be loaded dynamically. - **Prone to runtime errors**: Resources are identified by their name and extension (strings). Therefore, a typo in any of those will lead to a runtime error when trying to access the resource. This is not ideal because it's not caught at compile time and might lead to crashes in release. Tuist solves the problems above by **synthesizing a unified interface to access bundles and resources** that abstracts away the implementation details. > [!IMPORTANT] RECOMMENDED > Even though accessing resources through the Tuist-synthesized interface is not mandatory, we recommend it because it makes the code easier to reason about and the resources to move around. ## Resources {#resources} Tuist provides interfaces to declare the content of files such as `Info.plist` or entitlements in Swift. This is useful to ensure consistency across targets and projects, and leverage the compiler to catch issues at compile time. You can also come up with your own abstractions to model the content and share it across targets and projects. When your project is generated, Tuist will synthesize the content of those files and write them into the `Derived` directory relative to the directory containing the project that defines them. > [!TIP] GITIGNORE THE DERIVED DIRECTORY > We recommend adding the `Derived` directory to the `.gitignore` file of your project. ## Bundle accessors {#bundle-accessors} Tuist synthesizes an interface to access the bundle that contains the target resources. ### Swift {#swift} The target will contain an extension of the `Bundle` type that exposes the bundle: ```swift let bundle = Bundle.module ``` ### Objective-C {#objectivec} In Objective-C, you'll get an interface `{Target}Resources` to access the bundle: ```objc NSBundle *bundle = [MyFeatureResources bundle]; ``` > [!TIP] SUPPORTING RESOURCES IN LIBRARIES THROUGH BUNDLES > If a target product, for example a library, doesn't support resources, Tuist will include the resources in a target of product type `bundle` ensuring that it ends up in the final product and that the interface points to the right bundle. ## Resource accessors {#resource-accessors} Resources are identified by their name and extension using strings. This is not ideal because it's not caught at compile time and might lead to crashes in release. To prevent that, Tuist integrates [SwiftGen](https://github.com/SwiftGen/SwiftGen) into the project generation process to synthesize an interface to access the resources. Thanks to that, you can confidently access the resources leveraging the compiler to catch any issues. Tuist includes [templates](https://github.com/tuist/tuist/tree/main/Sources/TuistGenerator/Templates) to synthesize accessors for the following resource types by default: | Resource type | Synthesized file | | ----------------- | ------------------------ | | Images and colors | `Assets+{Target}.swift` | | Strings | `Strings+{Target}.swift` | | Plists | `{NameOfPlist}.swift` | | Fonts | `Fonts+{Target}.swift` | | Files | `Files+{Target}.swift` | > Note: You can disable the synthesizing of resource accessors on a per-project basis by passing the `disableSynthesizedResourceAccessors` option to the project options. #### Custom templates {#custom-templates} If you want to provide your own templates to synthesize accessors to other resource types, which must be supported by [SwiftGen](https://github.com/SwiftGen/SwiftGen), you can create them at `Tuist/ResourceSynthesizers/{name}.stencil`, where the name is the camel-case version of the resource. | Resource | Template name | | ---------------- | -------------------------- | | strings | `Strings.stencil` | | assets | `Assets.stencil` | | plists | `Plists.stencil` | | fonts | `Fonts.stencil` | | coreData | `CoreData.stencil` | | interfaceBuilder | `InterfaceBuilder.stencil` | | json | `JSON.stencil` | | yaml | `YAML.stencil` | | files | `Files.stencil` | If you want to configure the list of resource types to synthesize accessors for, you can use the `Project.resourceSynthesizers` property passing the list of resource synthesizers you want to use: ```swift let project = Project(resourceSynthesizers: [.string(), .fonts()]) ``` > [!NOTE] REFERENCE > You can check out [this fixture](https://github.com/tuist/tuist/tree/main/fixtures/ios_app_with_templates) to see an example of how to use custom templates to synthesize accessors to resources. --- URL: "/pt/guides/develop/projects/plugins" LLMS_URL: "/pt/guides/develop/projects/plugins.md" title: "Plugins" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to create and use plugins in Tuist to extend its functionality." --- # Plugins {#plugins} Plugins are a tool to share and reuse Tuist artifacts across multiple projects. The following artifacts are supported: - Project description helpers across multiple projects. - Templates across multiple projects. - Tasks across multiple projects. - Resource accessor template across multiple projects Note that plugins are designed to be a simple way to extend Tuist's functionality. Therefore there are **some limitations to consider**: - A plugin cannot depend on another plugin. - A plugin cannot depend on third-party Swift packages - A plugin cannot use project description helpers from the project that uses the plugin. If you need more flexibility, consider suggesting a feature for the tool or building your own solution upon Tuist's generation framework, [`TuistGenerator`](https://github.com/tuist/tuist/tree/main/Sources/TuistGenerator). ## Plugin types {#plugin-types} ### Project description helper plugin {#project-description-helper-plugin} A project description helper plugin is represented by a directory containing a `Plugin.swift` manifest file that declares the plugin's name and a `ProjectDescriptionHelpers` directory containing the helper Swift files. ::: code-group ```bash [Plugin.swift] import ProjectDescription let plugin = Plugin(name: "MyPlugin") ``` ```bash [Directory structure] . ├── ... ├── Plugin.swift ├── ProjectDescriptionHelpers └── ... ``` ::: ### Resource accessor templates plugin {#resource-accessor-templates-plugin} If you need to share synthesized resource accessors you can use this type of plugin. The plugin is represented by a directory containing a `Plugin.swift` manifest file that declares the plugin's name and a `ResourceSynthesizers` directory containing the resource accessor template files. ::: code-group ```bash [Plugin.swift] import ProjectDescription let plugin = Plugin(name: "MyPlugin") ``` ```bash [Directory structure] . ├── ... ├── Plugin.swift ├── ResourceSynthesizers ├───── Strings.stencil ├───── Plists.stencil ├───── CustomTemplate.stencil └── ... ``` ::: The name of the template is the [camel case](https://en.wikipedia.org/wiki/Camel_case) version of the resource type: | Resource type | Template file name | | ----------------- | ---------------------------------------- | | Strings | Strings.stencil | | Assets | Assets.stencil | | Property Lists | Plists.stencil | | Fonts | Fonts.stencil | | Core Data | CoreData.stencil | | Interface Builder | InterfaceBuilder.stencil | | JSON | JSON.stencil | | YAML | YAML.stencil | When defining the resource synthesizers in the project, you can specify the plugin name to use the templates from the plugin: ```swift let project = Project(resourceSynthesizers: [.strings(plugin: "MyPlugin")]) ``` ### Task plugin {#task-plugin-badge-typewarning-textdeprecated-} > [!WARNING] DEPRECATED > Task plugins are deprecated. Check out [this blog post](https://tuist.dev/blog/2025/04/15/automation-in-swift-projects) if you are looking for an automation solution for your project. Tasks are `$PATH`-exposed executables that are invocable through the `tuist` command if they follow the naming convention `tuist-`. In earlier versions, Tuist provided some weak conventions and tools under `tuist plugin` to `build`, `run`, `test` and `archive` tasks represented by executables in Swift Packages, but we have deprecated this feature since it increases the maintenance burden and complexity of the tool. If you were using Tuist for distributing tasks, we recommend building your - You can continue using the `ProjectAutomation.xcframework` distributed with every Tuist release to have access to the project graph from your logic with `let graph = try Tuist.graph()`. The command uses sytem process to run the `tuist` command, and return the in-memory representation of the project graph. - To distribute tasks, we recommend including the a fat binary that supports the `arm64` and `x86_64` in GitHub releases, and using [Mise](https://mise.jdx.dev) as an installation tool. To instruct Mise on how to install your tool, you'll need a plugin repository. You can use [Tuist's](https://github.com/asdf-community/asdf-tuist) as a reference. - If you name your tool `tuist-{xxx}` and users can install it by running `mise install`, they can run it either invoking it directly, or through `tuist xxx`. > [!NOTE] THE FUTURE OF PROJECTAUTOMATION > We plan to consolidate the models of `ProjectAutomation` and `XcodeGraph` into a single backward-compatible framework that exposes the entirity of the project graph to the user. Moreover, we'll extract the generation logic into a new layer, `XcodeGraph` that you can also use from your own CLI. Think of it as building your own Tuist. ## Using plugins {#using-plugins} To use a plugin, you'll have to add it to your project's `Tuist.swift` manifest file: ```swift import ProjectDescription let tuist = Tuist( project: .tuist(plugins: [ .local(path: "/Plugins/MyPlugin") ]) ) ``` If you want to reuse a plugin across projects that live in different repositories, you can push your plugin to a Git repository and reference it in the `Tuist.swift` file: ```swift import ProjectDescription let tuist = Tuist( project: .tuist(plugins: [ .git(url: "https://url/to/plugin.git", tag: "1.0.0"), .git(url: "https://url/to/plugin.git", sha: "e34c5ba") ]) ) ``` After adding the plugins, `tuist install` will fetch the plugins in a global cache directory. > [!NOTE] NO VERSION RESOLUTION > As you might have noted, we don't provide version resolution for plugins. We recommend using Git tags or SHAs to ensure reproducibility. > [!TIP] PROJECT DESCRIPTION HELPERS PLUGINS > When using a project description helpers plugin, the name of the module that contains the helpers is the name of the plugin > > ```swift > import ProjectDescription > import MyTuistPlugin > let project = Project.app(name: "MyCoolApp", platform: .iOS) > ``` --- URL: "/pt/guides/develop/projects/manifests" LLMS_URL: "/pt/guides/develop/projects/manifests.md" title: "Manifests" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the manifest files that Tuist uses to define projects and workspaces and configure the generation process." --- # Manifests {#manifests} Tuist defaults to Swift files as the primary way to define projects and workspaces and configure the generation process. These files are referred to as **manifest files** throughout the documentation. The decision of using Swift was inspired by the [Swift Package Manager](https://www.swift.org/documentation/package-manager/), which also uses Swift files to define packages. Thanks to the usage of Swift, we can leverage the compiler to validate the correctness of the content and reuse code across different manifest files, and Xcode to provide a first-class editing experience thanks to the syntax highlighting, auto-completion, and validation. > [!NOTE] CACHING > Since manifest files are Swift files that need to be compiled, Tuist caches the compilation results to speed up the parsing process. Therefore, you'll notice that the first time you run Tuist, it might take a bit longer to generate the project. Subsequent runs will be faster. ## Project.swift {#projectswift} The `Project.swift` manifest declares an Xcode project. The project gets generated in the same directory where the manifest file is located with the name indicated in the `name` property. ```swift // Project.swift let project = Project( name: "App", targets: [ // .... ] ) ``` > [!WARNING] ROOT VARIABLES > The only variable that should be at the root of the manifest is `let project = Project(...)`. If you need to reuse code across various parts of the manifest, you can use Swift functions. ## Workspace.swift {#workspaceswift} By default, Tuist generates an [Xcode Workspace](https://developer.apple.com/documentation/xcode/projects-and-workspaces) containing the project being generated and the projects of its dependencies. If for any reason you'd like to customize the workspace to add additional projects or include files and groups, you can do so by defining a `Workspace.swift` manifest. ```swift // Workspace.swift import ProjectDescription let workspace = Workspace( name: "App-Workspace", projects: [ "./App", // Path to directory containing the Project.swift file ] ) ``` > [!NOTE] > Tuist will resolve the dependency graph and include the projects of the dependencies in the workspace. You don't need to include them manually. This is necessary for the build system to resolve the dependencies correctly. ### Multi or mono-project {#multi-or-monoproject} A question that often comes up is whether to use a single project or multiple projects in a workspace. In a world without Tuist where a mono-project setup would lead to frequent Git conflicts the usage of workspaces is encouraged. However, since we don't recommend including the Tuist-generated Xcode projects in the Git repository, Git conflicts are not an issue. Therefore, the decision of using a single project or multiple projects in a workspace is up to you. In the Tuist project we lean on mono-projects because the cold generation time is faster (fewer manifest files to compile) and we leverage project description helpers as a unit of encapsulation. However, you might want to use Xcode projects as a unit of encapsulation to represent different domains of your application, which aligns more closely with the Xcode's recommended project structure. ## Tuist.swift {#tuistswift} Tuist provides sensible defaults to simplify project configuration. However, you can customize the configuration by defining a `Tuist.swift` at the root of the project, which is used by Tuist to determine the root of the project. ```swift import ProjectDescription let tuist = Tuist( project: .tuist(generationOptions: .options(enforceExplicitDependencies: true)) ) ``` --- URL: "/pt/guides/develop/projects/inspect/implicit-dependencies" LLMS_URL: "/pt/guides/develop/projects/inspect/implicit-dependencies.md" title: "Implicit imports" titleTemplate: ":title · Inspect · Projects · Develop · Guides · Tuist" description: "Learn how to use Tuist to find implicit imports." --- # Implicit imports {#implicit-imports} To alleviate the complexity of maintaining an Xcode project graph with raw Xcode project, Apple designed the build system in a way that allows dependencies to be implicitly defined. This means that a product, for example an app, can depend on a framework, even without declaring the dependency explicitly. At a small scale, this is is fine, but as the project graph grows in complexity, the implicitness might manifest as unreliable incremental builds or editor-based features such as previews or code completion. The problem is that you can't prevent implicit dependencies from happening. Any developer can add an `import` statement to their Swift code, and the implicit dependency will be created. This is where Tuist comes in. Tuist provides a command to inspect the implicit dependencies by statically analyzing the code in your project. The following command will output the implicit dependencies of your project: ```bash tuist inspect implicit-imports ``` If the command detects any implicit imports, it exits with an exit code other than zero. > [!TIP] VALIDATE IN CI > We strongly recommend to run this command as part of your continuous integration command every time new code is pushed upstream. > [!IMPORTANT] NOT ALL IMPLICIT CASES ARE DETECTED > Since Tuist relies on static code analysis to detect implicit dependencies, it might not catch all cases. For example, Tuist is unable to understand conditional imports through compiler directives in code. --- URL: "/pt/guides/develop/projects/hashing" LLMS_URL: "/pt/guides/develop/projects/hashing.md" title: "Hashing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about Tuist's hashing logic upon which features like binary caching and selective testing are built." --- # Hashing {#hashing} Features like caching or smart test execution require a way to determine whether a target has changed. Tuist calculates a hash for each target in the dependency graph to determine if a target has changed. The hash is calculated based on the following attributes: - The target's attributes (e.g., name, platform, product, etc.) - The target's files - The hash of the target's dependencies ### Cache attributes {#cache-attributes} Additionally, when calculating the hash for caching, we also hash the following attributes. #### Swift version {#swift-version} We hash the Swift version obtained from running the command `/usr/bin/xcrun swift --version` to prevent compilation errors due to Swift version mismatches between the targets and the binaries. > [!NOTE] MODULE STABILITY > Previous versions of binary caching relied on the `BUILD_LIBRARY_FOR_DISTRIBUTION` build setting to enable [module stability](https://www.swift.org/blog/library-evolution#enabling-library-evolution-support) and enable using binaries with any compiler version. However, it caused compilation issues in projects with targets that don't support module stability. Generated binaries are bound to the Swift version used to compile them, and the Swift version must match the one used to compile the project. #### Configuration {#configuration} The idea behind the flag `-configuration` was to ensure debug binaries were not used in release builds and viceversa. However, we are still missing a mechanism to remove the other configurations from the projects to prevent them from being used. ## Debugging {#debugging} If you notice non-deterministic behaviors when using the caching across environments or invocations, it might be related to differences across the environments or a bug in the hashing logic. We recommend following these steps to debug the issue: 1. Ensure the same [configuration](#configuration) and [Swift version](#swift-version) is used across environments. 2. Check if there are differences between the Xcode projects generated by two consecutive invocations of `tuist generate` or across environments. You can use the `diff` command to compare the projects. The generated projects might include **absolute paths** causing the hashing logic to be non-deterministic. > [!NOTE] BETTER DEBUGGING EXPERIENCE PLANNED > Improving our debugging experience is in our roadmap. The print-hashes command, which lacks the context to understand the differences, will be replaced by a more user-friendly command that uses a tree-like structure to show the differences between the hashes. --- URL: "/pt/guides/develop/projects/editing" LLMS_URL: "/pt/guides/develop/projects/editing.md" title: "Editing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to use Tuist's edit workflow to declare your project leveraging Xcode's build system and editor capabilities." --- # Editing {#editing} Unlike traditional Xcode projects or Swift Packages, where changes are done through Xcode's UI, Tuist-managed projects are defined in Swift code contained in **manifest files**. If you're familiar with Swift Packages and the `Package.swift` file, the approach is very similar. You could edit these files using any text editor, but we recommend to use Tuist-provided workflow for that, `tuist edit`. The workflow creates an Xcode project that contains all manifest files and allows you to edit and compile them. Thanks to using Xcode, you get all the benefits of **code completion, syntax highlighting, and error checking**. ## Edit the project {#edit-the-project} To edit your project, you can run the following command in a Tuist project directory or a sub-directory: ```bash tuist edit ``` The command creates an Xcode project in a global directory and opens it in Xcode. The project includes a `Manifests` directory that you can build to ensure all your manifests are valid. > [!INFO] GLOB-RESOLVED MANIFESTS > `tuist edit` resolves the manifests to be included by using the glob `**/{Manifest}.swift` from the project's root directory (the one containing the `Tuist.swift` file). Make sure there's a valid `Tuist.swift` at the root of the project. ## Edit and generate workflow {#edit-and-generate-workflow} As you might have noticed, the editing can't be done from the generated Xcode project. That's by design to prevent the generated project from having a dependency on Tuist, ensuring you can move from Tuist in the future with little effort. When iterating on a project, we recommend running `tuist edit` from a terminal session to get an Xcode project to edit the project, and use another terminal session to run `tuist generate`. --- URL: "/pt/guides/develop/projects/dynamic-configuration" LLMS_URL: "/pt/guides/develop/projects/dynamic-configuration.md" title: "Dynamic configuration" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how how to use environment variables to dynamically configure your project." --- # Dynamic configuration {#dynamic-configuration} There are certain scenarios where you might need to dynamically configure your project at generation time. For example, you might want to change the name of the app, the bundle identifier, or the deployment target based on the environment where the project is being generated. Tuist supports that via environment variables, which can be accessed from the manifest files. ## Configuration through environment variables {#configuration-through-environment-variables} Tuist allows passing configuration through environment variables that can be accessed from the manifest files. For example: ```bash TUIST_APP_NAME=MyApp tuist generate ``` If you want to pass multiple environment variables just separate them with a space. For example: ```bash TUIST_APP_NAME=MyApp TUIST_APP_LOCALE=pl tuist generate ``` ## Reading the environment variables from manifests {#reading-the-environment-variables-from-manifests} Variables can be accessed using the `Environment` type. Any variables following the convention `TUIST_XXX` defined in the environment or passed to Tuist when running commands will be accessible using the `Environment` type. The following example shows how we access the `TUIST_APP_NAME` variable: ```swift func appName() -> String { if case let .string(environmentAppName) = Environment.appName { return environmentAppName } else { return "MyApp" } } ``` Accessing variables returns an instance of type `Environment.Value?` which can take any of the following values: | Case | Description | | ----------------- | ----------------------------------------------------------- | | `.string(String)` | Used when the variable represents a string. | You can also retrieve the string or boolean `Environment` variable using either of the helper methods defined below, these methods require a default value to be passed to ensure the user gets consistent results each time. This avoids the need to define the function appName() defined above. ::: code-group ```swift [String] Environment.appName.getString(default: "TuistServer") ``` ```swift [Boolean] Environment.isCI.getBoolean(default: false) ``` ::: --- URL: "/pt/guides/develop/projects/directory-structure" LLMS_URL: "/pt/guides/develop/projects/directory-structure.md" title: "Directory structure" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the structure of Tuist projects and how to organize them." --- # Directory structure {#directory-structure} Although Tuist projects are commonly used to supersede Xcode projects, they are not limited to this use case. Tuist projects are also used to generate other types of projects, such as SPM packages, templates, plugins, and tasks. This document describes the structure of Tuist projects and how to organize them. In later sections, we'll cover how to define templates, plugins, and tasks. ## Standard Tuist projects {#standard-tuist-projects} Tuist projects are **the most common type of project generated by Tuist.** They are used to build apps, frameworks, and libraries among others. Unlike Xcode projects, Tuist projects are defined in Swift, which makes them more flexible and easier to maintain. Tuist projects are also more declarative, which makes them easier to understand and reason about. The following structure shows a typical Tuist project that generates an Xcode project: ```bash Tuist.swift Tuist/ Package.swift ProjectDescriptionHelpers/ Projects/ App/ Project.swift Feature/ Project.swift Workspace.swift ``` - **Tuist directory:** This directory has two purposes. First, it signals **where the root of the project is**. Second, it's the container for the following files: This allows constructing paths relative to the root of the project, and also running Tuist commands from any directory within the project. Second, it's the container for the following files: This allows constructing paths relative to the root of the project, and also running Tuist commands from any directory within the project. - **ProjectDescriptionHelpers:** This directory contains Swift code that's shared across all the manifest files. Manifest files can `import ProjectDescriptionHelpers` to use the code defined in this directory. Sharing code is useful to avoid duplications and ensure consistency across the projects. - **Package.swift:** This file contains Swift Package dependencies for Tuist to integrate them using Xcode projects and targets (like [CocoaPods](https://cococapods)) that are configurable and optimizable. Learn more here. - **Root directory**: The root directory of your project that also contains the `Tuist` directory. - Tuist.swift: This file contains configuration for Tuist that's shared across all the projects, workspaces, and environments. For example, it can be used to disable automatic generation of schemes, or to define the deployment target of the projects. - Workspace.swift: This manifest represents an Xcode workspace. It's used to group other projects and can also add additional files and schemes. - Project.swift: This manifest represents an Xcode project. It's used to define the targets that are part of the project, and their dependencies. When interacting with the above project, commands expect to find either a `Workspace.swift` or a `Project.swift` file in the working directory or the directory indicated via the `--path` flag. The manifest should be in a directory or subdirectory of a directory containing a `Tuist` directory, which represents the root of the project. > [!TIP] > Xcode workspaces allowed splitting projects into multiple Xcode projects to reduce the likelihood of merge conflicts. If that's what you were using workspaces for, you don't need them in Tuist. Tuist auto-generates a workspace containing a project and its dependencies' projects. ## Swift Package {#swift-package-badge-typewarning-textbeta-} Tuist also supports SPM package projects. If you are working on an SPM package, you shouldn't need to update anything. Tuist automatically picks up on your root `Package.swift` and all the features of Tuist work as if it was a `Project.swift` manifest. To get started, run `tuist install` and `tuist generate` in your SPM package. Your project should now have all the same schemes and files that you would see in the vanilla Xcode SPM integration. However, now you can also run `tuist cache` and have majority of your SPM dependencies and modules precompiled, making subsequent builds extremely fast. --- URL: "/pt/guides/develop/projects/dependencies" LLMS_URL: "/pt/guides/develop/projects/dependencies.md" title: "Dependencies" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to declare dependencies in your Tuist project." --- # Dependencies {#dependencies} When a project grows, it's common to split it into multiple targets to share code, define boundaries, and improve build times. Multiple targets means defining dependencies between them forming a **dependency graph**, which might include external dependencies as well. ## XcodeProj-codified graphs {#xcodeprojcodified-graphs} Due to Xcode and XcodeProj's design, the maintenance of a dependency graph can be a tedious and error-prone task. Here are some examples of the problems that you might encounter: - Because Xcode's build system outputs all the project's products into the same directory in derived data, targets might be able to import products that they shouldn't. Compilations might fail on CI, where clean builds are more common, or later on when a different configuration is used. - The transitive dynamic dependencies of a target need to be copied into any of the directories that are part of the `LD_RUNPATH_SEARCH_PATHS` build setting. If they aren't, the target won't be able to find them at runtime. This is easy to think about and set up when the graph is small, but it becomes a problem as the graph grows. - When a target links a static [XCFramework](https://developer.apple.com/documentation/xcode/creating-a-multi-platform-binary-framework-bundle), the target needs an additional build phase for Xcode to process the bundle and extract the right binary for the current platform and architecture. This build phase is not added automatically, and it's easy to forget to add it. The above are just a few examples, but there are many more that we've encountered over the years. Imagine if you required a team of engineers to maintain a dependency graph and ensure its validity. Or even worse, that the intricacies were resolved at build-time by a closed-source build system that you can't control or customize. Sounds familiar? This is the approach that Apple took with Xcode and XcodeProj and that the Swift Package Manager has inherited. We strongly believe that the dependency graph should be **explicit** and **static** because only then can it be **validated** and **optimized**. With Tuist, you focus on describing what depends on what, and we take care of the rest. The intricacies and implementation details are abstracted away from you. In the following sections you'll learn how to declare dependencies in your project. > [!TIP] GRAPH VALIDATION > Tuist validates the graph when generating the project to ensure that there are no cycles and that all the dependencies are valid. Thanks to this, any team can take part in evolving the dependency graph without worrying about breaking it. ## Local dependencies {#local-dependencies} Targets can depend on other targets in the same and different projects, and on binaries. When instantiating a `Target`, you can pass the `dependencies` argument with any of the following options: - `Target`: Declares a dependency with a target within the same project. - `Project`: Declares a dependency with a target in a different project. - `Framework`: Declares a dependency with a binary framework. - `Library`: Declares a dependency with a binary library. - `XCFramework`: Declares a dependency with a binary XCFramework. - `SDK`: Declares a dependency with a system SDK. - `XCTest`: Declares a dependency with XCTest. > [!NOTE] DEPENDENCY CONDITIONS > Every dependency type accepts a `condition` option to conditionally link the dependency based on the platform. By default, it links the dependency for all platforms the target supports. ## External dependencies {#external-dependencies} Tuist also allows you to declare external dependencies in your project. ### Swift Packages {#swift-packages} Swift Packages are our recommended way of declaring dependencies in your project. You can integrate them using Xcode's default integration mechanism or using Tuist's XcodeProj-based integration. #### Tuist's XcodeProj-based integration {#tuists-xcodeprojbased-integration} Xcode's default integration while being the most convenient one, lacks flexibility and control that's required for medium and large projects. To overcome this, Tuist offers an XcodeProj-based integration that allows you to integrate Swift Packages in your project using XcodeProj's targets. Thanks to that, we can not only give you more control over the integration but also make it compatible with workflows like caching and smart test runs. XcodeProj's integration is more likely to take more time to support new Swift Package features or handle more package configurations. However, the mapping logic between Swift Packages and XcodeProj targets is open-source and can be contributed to by the community. This is contrary to Xcode's default integration, which is closed-source and maintained by Apple. To add external dependencies, you'll have to create a `Package.swift` either under `Tuist/` or at the root of the project. ::: code-group ```swift [Tuist/Package.swift] // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription import ProjectDescriptionHelpers let packageSettings = PackageSettings( productTypes: [ "Alamofire": .framework, // default is .staticFramework ] ) #endif let package = Package( name: "PackageName", dependencies: [ .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), ], targets: [ .binaryTarget( name: "Sentry", url: "https://github.com/getsentry/sentry-cocoa/releases/download/8.40.1/Sentry.xcframework.zip", checksum: "db928e6fdc30de1aa97200576d86d467880df710cf5eeb76af23997968d7b2c7" ), ] ) ``` ::: > [!TIP] PACKAGE SETTINGS > The `PackageSettings` instance wrapped in a compiler directive allows you to configure how packages are integrated. For example, in the example above it's used to override the default product type used for packages. By default, you shouldn't need it. The `Package.swift` file is just an interface to declare external dependencies, nothing else. That's why you don't define any targets or products in the package. Once you have the dependencies defined, you can run the following command to resolve and pull the dependencies into the `Tuist/Dependencies` directory: ```bash tuist install # Resolving and fetching dependencies. {#resolving-and-fetching-dependencies} # Installing Swift Package Manager dependencies. {#installing-swift-package-manager-dependencies} ``` As you might have noticed, we take an approach similar to [CocoaPods](https://cocoapods.org)', where the resolution of dependencies is its own command. This gives control to the users over when they'd like dependencies to be resolved and updated, and allows opening the Xcode in project and have it ready to compile. This is an area where we believe the developer experience provided by Apple's integration with the Swift Package Manager degrates over time as the project grows. From your project targets you can then reference those dependencies using the `TargetDependency.external` dependency type: ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "App", organizationName: "tuist.io", targets: [ .target( name: "App", destinations: [.iPhone], product: .app, bundleId: "io.tuist.app", deploymentTargets: .iOS("13.0"), infoPlist: .default, sources: ["Targets/App/Sources/**"], dependencies: [ .external(name: "Alamofire"), // [!code ++] ] ), ] ) ``` ::: > [!NOTE] NO SCHEMES GENERATED FOR EXTERNAL PACKAGES > The **schemes** are not automatically created for Swift Package projects to keep the schemes list clean. You can create them via Xcode's UI. #### Xcode's default integration {#xcodes-default-integration} If you want to use Xcode's default integration mechanism, you can pass the list `packages` when instantiating a project: ```swift let project = Project(name: "MyProject", packages: [ .remote(url: "https://github.com/krzyzanowskim/CryptoSwift", requirement: .exact("1.8.0")) ]) ``` And then reference them from your targets: ```swift let target = .target(name: "MyTarget", dependencies: [ .package(product: "CryptoSwift", type: .runtime) ]) ``` For Swift Macros and Build Tool Plugins, you'll need to use the types `.macro` and `.plugin` respectively. > [!WARNING] SPM Build Tool Plugins > SPM build tool plugins must be declared using [Xcode's default integration](#xcode-s-default-integration) mechanism, even when using Tuist's [XcodeProj-based integration](#tuist-s-xcodeproj-based-integration) for your project dependencies. A practical application of an SPM build tool plugin is performing code linting during Xcode's "Run Build Tool Plug-ins" build phase. In a package manifest this is defined as follows: ```swift // swift-tools-version: 5.9 import PackageDescription let package = Package( name: "Framework", products: [ .library(name: "Framework", targets: ["Framework"]), ], dependencies: [ .package(url: "https://github.com/SimplyDanny/SwiftLintPlugins", .upToNextMajor(from: "0.56.1")), ], targets: [ .target( name: "Framework", plugins: [ .plugin(name: "SwiftLint", package: "SwiftLintPlugin"), ] ), ] ) ``` To generate an Xcode project with the build tool plugin intact, you must declare the package in the project manifest's `packages` array, and then include a package with type `.plugin` in a target's dependencies. ```swift import ProjectDescription let project = Project( name: "Framework", packages: [ .remote(url: "https://github.com/SimplyDanny/SwiftLintPlugins", requirement: .upToNextMajor(from: "0.56.1")), ], targets: [ .target( name: "Framework", dependencies: [ .package(product: "SwiftLintBuildToolPlugin", type: .plugin), ] ), ] ) ``` ### Carthage {#carthage} Since [Carthage](https://github.com/carthage/carthage) outputs `frameworks` or `xcframeworks`, you can run `carthage update` to output the dependencies in the `Carthage/Build` directory and then use the `.framework` or `.xcframework` target dependency type to declare the dependency in your target. You can wrap this in a script that you can run before generating the project. ```bash #!/usr/bin/env bash carthage update tuist generate ``` > [!WARNING] BUILD AND TEST > If you build and test your project through `tuist build` and `tuist test`, you will similarly need to ensure that the Carthage-resolved dependencies are present by running the `carthage update` command before `tuist build` or `tuist test` are run. ### CocoaPods {#cocoapods} [CocoaPods](https://cocoapods.org) expects an Xcode project to integrate the dependencies. You can use Tuist to generate the project, and then run `pod install` to integrate the dependencies by creating a workspace that contains your project and the Pods dependencies. You can wrap this in a script that you can run before generating the project. ```bash #!/usr/bin/env bash tuist generate pod install ``` > [!WARNING] > CocoaPods dependencies are not compatible with workflows like `build` or `test` that run `xcodebuild` right after generating the project. They are also incompatible with binary caching and selective testing since the fingerprinting logic doesn't account for the Pods dependencies. ## Static or dynamic {#static-or-dynamic} Frameworks and libraries can be linked either statically or dynamically, **a choice that has significant implications for aspects like app size and boot time**. Despite its importance, this decision is often made without much consideration. The **general rule of thumb** is that you want as many things as possible to be statically linked in release builds to achieve fast boot times, and as many things as possible to be dynamically linked in debug builds to achieve fast iteration times. The challenge with changing between static and dynamic linking in a project graph is that is not trivial in Xcode because a change has cascading effect on the entire graph (e.g. libraries can't contain resources, static frameworks don't need to be embedded). Apple tried to solve the problem with compile time solutions like Swift Package Manager's automatic decision between static and dynamic linking, or [Mergeable Libraries](https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries). However, this adds new dynamic variables to the compilation graph, adding new sources of non-determinism, and potentially causing some features like Swift Previews that rely on the compilation graph to become unreliable. Luckily, Tuist conceptually compresses the complexity associated with changing between static and dynamic and synthesizes bundle accessors that are standard across linking types. In combination with dynamic configurations via environment variables, you can pass the linking type at invocation time, and use the value in your manifests to set the product type of your targets. ```swift // Use the value returned by this function to set the product type of your targets. func productType() -> Product { if case let .string(linking) = Environment.linking { return linking == "static" ? .staticFramework : .framework } else { return .framework } } ``` Note that Tuist does not default to convenience through implicit configuration due to its costs. What this means is that we rely on you setting the linking type and any additional build settings that are sometimes required, like the [`-ObjC` linker flag](https://github.com/pointfreeco/swift-composable-architecture/discussions/1657#discussioncomment-4119184), to ensure the resulting binaries are correct. Therefore, the stance that we take is providing you with the resources, usually in the shape of documentation, to make the right decisions. > [!TIP] EXAMPLE: COMPOSABLE ARCHITECTURE > A Swift Package that many projects integrate is [Composable Architecture](https://github.com/pointfreeco/swift-composable-architecture). As described [here](https://github.com/pointfreeco/swift-composable-architecture/discussions/1657#discussioncomment-4119184) and the [troubleshooting section](#troubleshooting), you'll need to set the `OTHER_LDFLAGS` build setting to `$(inherited) -ObjC` when linking the packages statically, which is Tuist's default linking type. Alternatively, you can override the product type for the package to be dynamic. ### Scenarios {#scenarios} There are some scenarios where setting the linking entirely to static or dynamic is not feasible or a good idea. The following is a non-exhaustive list of scenarios where you might need to mix static and dynamic linking: - **Apps with extensions:** Since apps and their extensions need to share code, you might need to make those targets dynamic. Otherwise, you'll end up with the same code duplicated in both the app and the extension, causing the binary size to increase. - **Pre-compiled external dependencies:** Sometimes you are provided with pre-compiled binaries that are either static or dynamic. Static binaries can be wrapped in dynamic frameworks or libraries to be linked dynamically. When making changes to the graph, Tuist will analyze it and display a warning if it detects a "static side effect". This warning is meant to help you identify issues that might arise from linking a target statically that depends transitively on a static target through dynamic targets. These side effects often manifest as increased binary size or, in the worst cases, runtime crashes. ## Troubleshooting {#troubleshooting} ### Objective-C Dependencies {#objectivec-dependencies} When integrating Objective-C dependencies, the inclusion of certain flags on the consuming target may be necessary to avoid runtime crashes as detailed in [Apple Technical Q&A QA1490](https://developer.apple.com/library/archive/qa/qa1490/_index.html). Since the build system and Tuist have no way of inferring whether the flag is necessary or not, and since the flag comes with potentially undesirable side effects, Tuist will not automatically apply any of these flags, and because Swift Package Manager considers `-ObjC` to be included via an `.unsafeFlag` most packages cannot include it as part of their default linking settings when required. Consumers of Objective-C dependencies (or internal Objective-C targets) should apply `-ObjC` or `-force_load` flags when required by setting `OTHER_LDFLAGS` on consuming targets. ### Firebase & Other Google Libraries {#firebase-other-google-libraries} Google's open source libraries — while powerful — can be difficult to integrate within Tuist as they often use non-standard architecture and techniques in how they are built. Here are a few tips that may be necessary to follow to integrate Firebase and Google's other Apple-platform libraries: #### Ensure `-ObjC` is added to `OTHER_LDFLAGS` {#ensure-objc-is-added-to-other_ldflags} Many of Google's libraries are written in Objective-C. Because of this, any consuming target will need to include the `-ObjC` tag in its `OTHER_LDFLAGS` build setting. This can either be set in an `.xcconfig` file or manually specified in the target's settings within your Tuist manifests. An example: ```swift Target.target( ... settings: .settings( base: ["OTHER_LDFLAGS": "$(inherited) -ObjC"] ) ... ) ``` Refer to the [Objective-C Dependencies](#objective-c-dependencies) section above for more details. #### Set the product type for `FBLPromises` to dynamic framework {#set-the-product-type-for-fblpromises-to-dynamic-framework} Certain Google libraries depend on `FBLPromises`, another of Google's libraries. You may encounter a crash that mentions `FBLPromises`, looking something like this: ``` NSInvalidArgumentException. Reason: -[FBLPromise HTTPBody]: unrecognized selector sent to instance 0x600000cb2640. ``` Explicitly setting the product type of `FBLPromises` to `.framework` in your `Package.swift` file should fix the issue: ```swift [Tuist/Package.swift] // swift-tools-version: 5.10 import PackageDescription #if TUIST import ProjectDescription import ProjectDescriptionHelpers let packageSettings = PackageSettings( productTypes: [ "FPLPromises": .framework, ] ) #endif let package = Package( ... ``` ### Transitive static dependencies leaking through `.swiftmodule` {#transitive-static-dependencies-leaking-through-swiftmodule} When a dynamic framework or library depends on static ones through `import StaticSwiftModule`, the symbols are included in the `.swiftmodule` of the dynamic framework or library, potentially [causing the compilation to fail](https://forums.swift.org/t/compiling-a-dynamic-framework-with-a-statically-linked-library-creates-dependencies-in-swiftmodule-file/22708/1). To prevent that, you'll have to import the static dependency using [`@_implementationOnly`](https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_implementationonly): ```swift @_implementationOnly import StaticModule ``` --- URL: "/pt/guides/develop/projects/cost-of-convenience" LLMS_URL: "/pt/guides/develop/projects/cost-of-convenience.md" title: "The cost of convenience" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the cost of convenience in Xcode and how Tuist helps you prevent the issues that come with it." --- # The cost of convenience {#the-cost-of-convenience} Designing a code editor that the spectrum **from small to large-scale projects can use** is a challenging task. Many tools approach the problem by layering their solution and providing extensibility. The bottom-most layer is very low-level and close to the underlying build system, and the top-most layer is a high-level abstraction that's convenient to use but less flexible. By doing so, they make the simple things easy, and everything else possible. However, **[Apple](https://www.apple.com) decided to take a different approach with Xcode**. The reason is unknown, but it's likely that optimizing for the challenges of large-scale projects has never been their goal. They overinvested in convenience for small projects, provided little flexibility, and strongly coupled the tools with the underlying build system. To achieve the convenience, they provide sensible defaults, which you can easily replace, and added a lot of implicit build-time-resolved behaviors that are the culprit of many issues at scale. ## Explicitness and scale {#explicitness-and-scale} When working at scale, **explicitness is key**. It allows the build system to analyze and understand the project structure and dependencies ahead of time, and perform optimizations that would be impossible otherwise. The same explicitness is also key in ensuring that editor features such as [SwiftUI previews](https://developer.apple.com/documentation/swiftui/previews-in-xcode) or [Swift Macros](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros/) work reliably and predictably. Because Xcode and Xcode projects embraced implicitness as a valid design choice to achieve convenience, a principle that the Swift Package Manager has inherited, the difficulties of using Xcode are also present in the Swift Package Manager. > [!INFO] THE ROLE OF TUIST > We could summarize Tuist's role as a tool that prevents implicitly-defined projects and leverages explicitness to provide a better developer experience (e.g. validations, optimizations). Tools like [Bazel](https://bazel.build) take it further by bringing it down to the build system level. This is an issue that's barely discussed in the community, but it's a significant one. While working on Tuist, we've noticed many organizations and developers thinking that the current challenges they face will be addressed by the [Swift Package Manager](https://www.swift.org/documentation/package-manager/), but what they don't realize is that because it's building on the same principles, even though it mitigates the so well-known Git conflicts, they degrade the developer experience in other areas and continue to make the projects non-optimizable. In the following sections, we'll discuss some real examples of how implicitness affects the developer experience and the project's health. The list is not exhaustive, but it should give you a good idea of the challenges that you might face when working with Xcode projects or Swift Packages. ## Convenience getting in your way {#convenience-getting-in-your-way} ### Shared built products directory {#shared-built-products-directory} Xcode uses a directory inside the derived data directory for each product. Inside it, it stores the build artifacts, such as the compiled binaries, the dSYM files, and the logs. Because all the products of a project go into the same directory, which is visible by default from other targets to link against, **you might end up with targets that implicitly depend on each other.** While this might not be a problem when having just a few targets, it might manifest as failing builds that are hard to debug when the project grows. The consequence of this design decision is that many projects acidentally compile with a graph that is not well-defined. > [!TIP] TUIST DETECTION OF IMPLICIT DEPENDENCIES > Tuist provides a command to detect implicit dependencies. You can use the command to validate in CI that all your dependencies are explicit. ### Find implicit dependencies in schemes {#find-implicit-dependencies-in-schemes} Defining and maintaining a dependency graph in Xcode gets harder as the project grows. It's hard because they are codified in the `.pbxproj` files as build phases and build settings, there are no tools to visualize and work with the graph, and the changes in the graph (e.g. adding a new dynamic precompiled framework), might require configuration changes upstream (e.g. adding a new build phase to copy the framework into the bundle). Apple decided at some point that instead of evolving the graph model into something more manageable, it'd make more sense to add an option to resolve implicit dependencies at build time. This is once again a questionable design choice because you might end up with slower build times or unpredictable builds. For example, a build might pass locally due to some state in derive data, which acts as a [singleton](https://en.wikipedia.org/wiki/Singleton_pattern), but then fail to compile on CI because the state is different. > [!TIP] > We recommend disabling this in your project schemes, and use like Tuist that eases the management of the dependency graph. ### SwiftUI Previews and static libraries/frameworks {#swiftui-previews-and-static-librariesframeworks} Some editor features like SwiftUI Previews or Swift Macros require the compilation of the dependency graph from the file that's being edited. This integration between the editor requires that the build system resolves any implicitness and output the right artifacts that are necessary for those features to work. As you can imagine, **the more implicit the graph is, the more challenging the task is for the build system**, and therefore it's not surprising that many of these features don't work reliably. We often hear from developers that they stopped using SwiftUI previews long time ago because they were too unreliable. Instead, they are using either example apps, or avoiding certaing things, like the usage of static libraries or script build phases, because they cause the feature to break. ### Mergeable libraries {#mergeable-libraries} Dynamic frameworks, while more flexible and easier to work with, have a negative impact in the launch time of apps. On the other side, static libraries are faster to launch, but impact the compilation time and are a bit harder to work with, specially in complex graph scenarios. _Wouldn't it be great if you could change between one or the other depending on the configuration?_ That's what Apple must have thought when they decided to work on mergeable libraries. But once again, they moved more build-time inference to the build-time. If reasoning about a dependency graph, imagine having to do so when the static or dynamic nature of the target will be resolved at build-time based on some build settings in some targets. Good luck making that work reliably while ensuring features like SwiftUI previews don't break. **Many users come to Tuist wanting to use mergeable libraries and our answer is always the same. You don't need to.** You can control the static or dynamic nature of your targets at generation-time leading to a project whose graph is known ahead of compilation. No variables need to be resolved at build-time. ```bash # The value of TUIST_DYNAMIC can be read from the project {#the-value-of-tuist_dynamic-can-be-read-from-the-project} # to set the product as static or dynamic based on the value. {#to-set-the-product-as-static-or-dynamic-based-on-the-value} TUIST_DYNAMIC=1 tuist generate ``` ## Explicit, explicit, and explicit {#explicit-explicit-and-explicit} If there's an important non-written principle that we recommend every developer or organization that wants their development with Xcode to scale, is that they should embrace explicitness. And if explicitness is hard to manage with raw Xcode projects, they should consider something else, either [Tuist](https://tuist.io) or [Bazel](https://bazel.build). **Only then reliability, predicability, and optimizations will be possible.** ## Future {#future} Whether Apple will do something to prevent all the above issues is unknown. Their continuous decisions embedded into Xcode and the Swift Package Manager don't suggest that they will. Once you allow implicit configuration as a valid state, **it's hard to move from there without introducing breaking changes.** Going back to first principles and rethinking the design of the tools might lead to breaking many Xcode projects that accidentally compiled for years. Imagine the community uproar if that happened. Apple finds itself in a bit of a chicken-and-egg problem. Convenience is what helps developers get started quickly and build more apps for their ecosystem. But their decisions to make the experience convenience at that scale, is making it hard for them to ensure some of the Xcode features work reliably. Because the future is unknown, we try to **be as close as possible to the industry standards and Xcode projects**. We prevent the above issues, and leverage the knowledge that we have to provide a better developer experience. Ideally we wouldn't have to resort to project generation for that, but the lack of extensibility of Xcode and the Swift Package Manager make it the only viable option. And it's also a safe option because they'll have to break the Xcode projects to break Tuist projects. Ideally, **the build system was more extensible**, but wouldn't it be a bad idea to have plugins/extensions that contract with a world of implicitness? It doesn't seem like a good idea. So it seems like we'll need external tools like Tuist or [Bazel](https://bazel.build) to provide a better developer experience. Or maybe Apple will surprise us all and make Xcode more extensible and explicit... Until that happens, you have to choose whether you want to embrace the convencience of Xcode and take on the debt that comes with it, or trust us on this journey to provide a better developer experience. We won't disappoint you. --- URL: "/pt/guides/develop/projects/code-sharing" LLMS_URL: "/pt/guides/develop/projects/code-sharing.md" title: "Code sharing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to share code across manifest files to reduce duplications and ensure consistency" --- # Code sharing {#code-sharing} One of the inconveniences of Xcode when we use it with large projects is that it doesn't allow reusing elements of the projects other than the build settings through `.xcconfig` files. Being able to reuse project definitions is useful for the following reasons: - It eases the **maintenance** because changes can be applied in one place and all the projects get the changes automatically. - It makes it possible to define **conventions** that new projects can conform to. - Projects are more **consistent** and therefore the likelihood of broken builds due inconsistencies is significantly less. - Adding a new projects becomes an easy task because we can reuse the existing logic. Reusing code across manifest files is possible in Tuist thanks to the concept of **project description helpers**. > [!TIP] A TUIST UNIQUE ASSET > Many organizations like Tuist because they see in project description helpers a platform for platform teams to codify their own conventions and come up with their own language for describing their projects. For example, YAML-based project generators have to come up with their own YAML-based propietary templating solution, or force organizations onto building their tools upon. ## Project description helpers {#project-description-helpers} Project description helpers are Swift files that get compiled into a module, `ProjectDescriptionHelpers`, that manifest files can import. The module is compiled by gathering all the files in the `Tuist/ProjectDescriptionHelpers` directory. You can import them into your manifest file by adding an import statement at the top of the file: ```swift // Project.swift import ProjectDescription import ProjectDescriptionHelpers ``` `ProjectDescriptionHelpers` are available in the following manifests: - `Project.swift` - `Package.swift` (only behind the `#TUIST` compiler flag) - `Workspace.swift` ## Example {#example} The snippets below contain an example of how we extend the `Project` model to add static constructors and how we use them from a `Project.swift` file: ::: code-group ```swift [Tuist/Project+Templates.swift] import ProjectDescription extension Project { public static func featureFramework(name: String, dependencies: [TargetDependency] = []) -> Project { return Project( name: name, targets: [ .target( name: name, destinations: .iOS, product: .framework, bundleId: "io.tuist.\(name)", infoPlist: "\(name).plist", sources: ["Sources/\(name)/**"], resources: ["Resources/\(name)/**",], dependencies: dependencies ), .target( name: "\(name)Tests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.\(name)Tests", infoPlist: "\(name)Tests.plist", sources: ["Sources/\(name)Tests/**"], resources: ["Resources/\(name)Tests/**",], dependencies: [.target(name: name)] ) ] ) } } ``` ```swift {2} [Project.swift] import ProjectDescription import ProjectDescriptionHelpers let project = Project.featureFramework(name: "MyFeature") ``` ::: > [!TIP] A TOOL TO ESTABLISH CONVENTIONS > Note how through the function we are defining conventions about the name of the targets, the bundle identifier, and the folders structure. --- URL: "/pt/guides/develop/projects/best-practices" LLMS_URL: "/pt/guides/develop/projects/best-practices.md" title: "Best practices" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the best practices for working with Tuist and Xcode projects." --- # Best practices {#best-practices} Over the years working with different teams and projects, we've identified a set of best practices that we recommend following when working with Tuist and Xcode projects. These practices are not mandatory, but they can help you structure your projects in a way that makes them easier to maintain and scale. ## Xcode {#xcode} ### Discouraged patterns {#discouraged-patterns} #### Configurations to model remote environments {#configurations-to-model-remote-environments} Many organizations use build configurations to model different remote environments (e.g., `Debug-Production` or `Release-Canary`), but this approach has some downsides: - **Inconsistencies:** If there are configuration inconsistencies throughout the graph, the build system might end up using the wrong configuration for some targets. - **Complexity:** Projects can end up with a long list of local configurations and remote environments that are hard to reason about and maintain. Build configurations were designed to embody different build settings, and projects rarely need more than just `Debug` and `Release`. The need to model different environments can be achieved by using schemes: - **In Debug builds:** You can include all the configurations that should be accessible in development in the app (e.g. endpoints), and switch them at runtime. The switch can happen either using scheme launch environment variables, or with a UI within the app. - **In Release builds:** In case of release, you can only include the configuration that the release build is bound to, and not include the runtime logic for switching configurations by using compiler directives. --- URL: "/pt/guides/develop/projects/adoption/swift-package" LLMS_URL: "/pt/guides/develop/projects/adoption/swift-package.md" title: "Use Tuist with a Swift Package" titleTemplate: ":title · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to use Tuist with a Swift Package." --- # Using Tuist with a Swift Package {#using-tuist-with-a-swift-package-badge-typewarning-textbeta-} Tuist supports using `Package.swift` as a DSL for your projects and it converts your package targets into a native Xcode project and targets. > [!WARNING] > The aim of this feature is to provide an easy way for developers to assess the impact of adopting Tuist in their Swift Packages. Therefore, we don't plan to support the full range of Swift Package Manager features nor to bring every Tuist's unique features like project description helpers to the packages world. > [!NOTE] ROOT DIRECTORY > Tuist commands expect a certain directory structure whose root is identified by a `Tuist` or a `.git` directory. ## Using Tuist with a Swift Package {#using-tuist-with-a-swift-package} We are going to use Tuist with the [TootSDK Package](https://github.com/TootSDK/TootSDK) repository, which contains a Swift Package. The first thing that we need to do is to clone the repository: ```bash git clone https://github.com/TootSDK/TootSDK cd TootSDK ``` Once in the repository's directory, we need to install the Swift Package Manager dependencies: ```bash tuist install ``` Under the hood `tuist install` uses the Swift Package Manager to resolve and pull the dependencies of the package. After the resolution completes, you can then generate the project: ```bash tuist generate ``` Voilà! You have a native Xcode project that you can open and start working on. --- URL: "/pt/guides/develop/projects/adoption/new-project" LLMS_URL: "/pt/guides/develop/projects/adoption/new-project.md" title: "Create a new project" titleTemplate: ":title · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to create a new project with Tuist." --- # Create a new project {#create-a-new-project} The most straightforward way to start a new project with Tuist is to use the `tuist init` command. This command launches an interactive CLI that guides you through setting up your project. When prompted, make sure to select the option to create a "generated project". One of the files that are generated is the `Project.swift`, which contains the definition of your project. If you are familiar with the Swift Package Manager, think of it as the `Package.swift` but with the lingo of Xcode projects. You can then edit the project running `tuist edit`, and Xcode will open a project where you can edit the project. ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "MyApp", targets: [ .target( name: "MyApp", destinations: .iOS, product: .app, bundleId: "io.tuist.MyApp", infoPlist: .extendingDefault( with: [ "UILaunchScreen": [ "UIColorName": "", "UIImageName": "", ], ] ), sources: ["MyApp/Sources/**"], resources: ["MyApp/Resources/**"], dependencies: [] ), .target( name: "MyAppTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyAppTests", infoPlist: .default, sources: ["MyApp/Tests/**"], resources: [], dependencies: [.target(name: "MyApp")] ), ] ) ``` ::: > [!NOTE] > We intentionally keep the list of available templates short to minimize maintenance overhead. If you want to create a project that doesn't represent an application, for example a framework, you can use `tuist init` as a starting point and then modify the generated project to suit your needs. ## Manually creating a project {#manually-creating-a-project} Alternatively, you can create the project manually. We recommend doing this only if you're already familiar with Tuist and its concepts. The first thing that you'll need to do is to create additional directories for the project structure: ```bash mkdir MyFramework cd MyFramework ``` Then create a `Tuist.swift` file, which will configure Tuist and is used by Tuist to determine the root directory of the project, and a `Project.swift`, where your project will be declared: ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "MyFramework", targets: [ .target( name: "MyFramework", destinations: .macOS, product: .framework, bundleId: "io.tuist.MyFramework", sources: ["MyFramework/Sources/**"], dependencies: [] ) ] ) ``` ```swift [Tuist.swift] import ProjectDescription let tuist = Tuist() ``` ::: > [!IMPORTANT] > Tuist uses the `Tuist/` directory to determine the root of your project, and from there it looks for other manifest files globbing the directories. We recommend creating those files with your editor of choice, and from that point on, you can use `tuist edit` to edit the project with Xcode. --- URL: "/pt/guides/develop/projects/adoption/migrate/xcodegen-project" LLMS_URL: "/pt/guides/develop/projects/adoption/migrate/xcodegen-project.md" title: "Migrate an XcodeGen project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate your projects from XcodeGen to Tuist." --- # Migrate an XcodeGen project {#migrate-an-xcodegen-project} [XcodeGen](https://github.com/yonaskolb/XcodeGen) is a project-generation tool that uses YAML as [a configuration format](https://github.com/yonaskolb/XcodeGen/blob/master/Docs/ProjectSpec.md) to define Xcode projects. Many organizations **adopted it trying to escape from the frequent Git conflicts that arise when working with Xcode projects.** However, frequent Git conflicts is just one of the many problems that organizations experience. Xcode exposes developers with a lot of intricacies and implicit configurations that make it hard to maintain and optimize projects at scale. XcodeGen falls short there by design because it's a tool that generates Xcode projects, not a project manager. If you need a tool that helps you beyond generating Xcode projects, you might want to consider Tuist. > [!TIP] SWIFT OVER YAML > Many organizations prefer Tuist as a project generation tool too because it uses Swift as a configuration format. Swift is a programming language that developers are familiar with, and that provides them with the convenience of using Xcode's autocompletion, type-checking, and validation features. What follows are some considerations and guidelines to help you migrate your projects from XcodeGen to Tuist. ## Project generation {#project-generation} Both Tuist and XcodeGen provide a `generate` command that turns your project declaration into Xcode projects and workspaces. ::: code-group ```bash [XcodeGen] xcodegen generate ``` ```bash [Tuist] tuist generate ``` ::: The difference lays in the editing experience. With Tuist, you can run the `tuist edit` command, which generates an Xcode project on the fly that you can open and start working on. This is particularly useful when you want to make quick changes to your project. ## `project.yaml` {#projectyaml} XcodeGen's `project.yaml` description file becomes `Project.swift`. Moreover, you can have `Workspace.swift` as a way to customize how projects are grouped in workspaces. You can also have a project `Project.swift` with targets that reference targets from other projects. In those cases, Tuist will generate an Xcode Workspace including all the projects. ::: code-group ```bash [XcodeGen directory structure] / project.yaml ``` ```bash [Tuist directory structure] / Tuist.swift Project.swift Workspace.swift ``` ::: > [!TIP] XCODE'S LANGUAGE > Both XcodeGen and Tuist embrace Xcode's language and concepts. However, Tuist's Swift-based configuration provides you with the convenience of using Xcode's autocompletion, type-checking, and validation features. ## Spec templates {#spec-templates} One of the disadvantages of YAML as a language for project configuration is that it doesn't support reusability across YAML files out of the box. This is a common need when describing projects, which XcodeGen had to solve with their own propietary solution named _"templates"_. With Tuist's re-usability is built into the language itself, Swift, and through a Swift module named project description helpers, which allow reusing code across all your manifest files. ::: code-group ```swift [Tuist/ProjectDescriptionHelpers/Target+Features.swift] import ProjectDescription extension Target { /** This function is a factory of targets that together represent a feature. */ static func featureTargets(name: String) -> [Target] { // ... } } ``` ```swift [Project.swift] import ProjectDescription import ProjectDescriptionHelpers // [!code highlight] let project = Project(name: "MyProject", targets: Target.featureTargets(name: "MyFeature")) // [!code highlight] ``` --- URL: "/pt/guides/develop/projects/adoption/migrate/xcode-project" LLMS_URL: "/pt/guides/develop/projects/adoption/migrate/xcode-project.md" title: "Migrate an Xcode project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate an Xcode project to a Tuist project." --- # Migrate an Xcode project {#migrate-an-xcode-project} Unless you create a new project using Tuist, in which case you get everything configured automatically, you'll have to define your Xcode projects using Tuist's primitives. How tedious this process is, depends on how complex your projects are. As you probably know, Xcode projects can become messy and complex over time: groups that don't match the directory structure, files that are shared across targets, or file references that point to nonexisting files (to mention some). All that accumulated complexity makes it hard for us to provide a command that reliably migrates project. Moreover, manual migration is an excellent exercise to clean up and simplify your projects. Not only the developers in your project will be thankful for that, but Xcode, who will be faster processing and indexing them. Once you have fully adopted Tuist, it will make sure that projects are consistently defined and that they remain simple. In the aim of easing that work, we are giving you some guidelines based on the feedback that we have received from the users. ## Create project scaffold {#create-project-scaffold} First of all, create a scaffold for your project with the following Tuist files: ::: code-group ```js [Tuist.swift] import ProjectDescription let tuist = Tuist() ``` ```js [Project.swift] import ProjectDescription let project = Project( name: "MyApp-Tuist", targets: [ /** Targets will go here **/ ] ) ``` ```js [Tuist/Package.swift] // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies ] ) ``` ::: `Project.swift` is the manifest file where you'll define your project, and `Package.swift` is the manifest file where you'll define your dependencies. The `Tuist.swift` file is where you can define project-scoped Tuist settings for your project. > [!TIP] PROJECT NAME WITH -TUIST SUFFIX > To prevent conflicts with the existing Xcode project, we recommend adding the `-Tuist` suffix to the project name. You can drop it once you've fully migrated your project to Tuist. ## Build and test the Tuist project in CI {#build-and-test-the-tuist-project-in-ci} To ensure the migration of each change is valid, we recommend extending your continuous integration to build and test the project generated by Tuist from your manifest file: ```bash tuist install tuist generate tuist build -- ...{xcodebuild flags} # or tuist test ``` ## Extract the project build settings into `.xcconfig` files {#extract-the-project-build-settings-into-xcconfig-files} Extract the build settings from the project into an `.xcconfig` file to make the project leaner and easier to migrate. You can use the following command to extract the build settings from the project into an `.xcconfig` file: ```bash mkdir -p xcconfigs/ tuist migration settings-to-xcconfig -p MyApp.xcodeproj -x xcconfigs/MyApp-Project.xcconfig ``` Then update your `Project.swift` file to point to the `.xcconfig` file you've just created: ```swift import ProjectDescription let project = Project( name: "MyApp", settings: .settings(configurations: [ .debug(name: "Debug", xcconfig: "./xcconfigs/MyApp-Project.xcconfig"), // [!code ++] .release(name: "Release", xcconfig: "./xcconfigs/MyApp-Project.xcconfig"), // [!code ++] ]), targets: [ /** Targets will go here **/ ] ) ``` Then extend your continuous integration pipeline to run the following command to ensure that changes to build settings are made directly to the `.xcconfig` files: ```bash tuist migration check-empty-settings -p Project.xcodeproj ``` ## Extract package dependencies {#extract-package-dependencies} Extract all your project's dependencies into the `Tuist/Package.swift` file: ```swift // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies .package(url: "https://github.com/onevcat/Kingfisher", .upToNextMajor(from: "7.12.0")) // [!code ++] ] ) ``` > [!TIP] PRODUCT TYPES > You can override the product type for a specific package by adding it to the `productTypes` dictionary in the `PackageSettings` struct. By default, Tuist assumes that all packages are static frameworks. ## Determine the migration order {#determine-the-migration-order} We recommend migrating the targets from the one that is the most dependent upon to the least. You can use the following command to list the targets of a project, sorted by the number of dependencies: ```bash tuist migration list-targets -p Project.xcodeproj ``` Start migrating the targets from the top of the list, as they are the ones that are the most depended upon. ## Migrate targets {#migrate-targets} Migrate the targets one by one. We recommend doing a pull request for each target to ensure that the changes are reviewed and tested before merging them. ### Extract the target build settings into `.xcconfig` files {#extract-the-target-build-settings-into-xcconfig-files} Like you did with the project build settings, extract the target build settings into an `.xcconfig` file to make the target leaner and easier to migrate. You can use the following command to extract the build settings from the target into an `.xcconfig` file: ```bash tuist migration settings-to-xcconfig -p MyApp.xcodeproj -t TargetX -x xcconfigs/TargetX.xcconfig ``` ### Define the target in the `Project.swift` file {#define-the-target-in-the-projectswift-file} Define the target in `Project.targets`: ```swift import ProjectDescription let project = Project( name: "MyApp", settings: .settings(configurations: [ .debug(name: "Debug", xcconfig: "./xcconfigs/Project.xcconfig"), .release(name: "Release", xcconfig: "./xcconfigs/Project.xcconfig"), ]), targets: [ .target( // [!code ++] name: "TargetX", // [!code ++] destinations: .iOS, // [!code ++] product: .framework, // [!code ++] // or .staticFramework, .staticLibrary... bundleId: "io.tuist.targetX", // [!code ++] sources: ["Sources/TargetX/**"], // [!code ++] dependencies: [ // [!code ++] /** Dependencies go here **/ // [!code ++] /** .external(name: "Kingfisher") **/ // [!code ++] /** .target(name: "OtherProjectTarget") **/ // [!code ++] ], // [!code ++] settings: .settings(configurations: [ // [!code ++] .debug(name: "Debug", xcconfig: "./xcconfigs/TargetX.xcconfig"), // [!code ++] .debug(name: "Release", xcconfig: "./xcconfigs/TargetX.xcconfig"), // [!code ++] ]) // [!code ++] ), // [!code ++] ] ) ``` > [!NOTE] TEST TARGETS > If the target has an associated test target, you should define it in the `Project.swift` file as well repeating the same steps. ### Validate the target migration {#validate-the-target-migration} Run `tuist build` and `tuist test` to ensure the project builds and tests pass. Additionally, you can use [xcdiff](https://github.com/bloomberg/xcdiff) to compare the generated Xcode project with the existing one to ensure that the changes are correct. ### Repeat {#repeat} Repeat until all the targets are fully migrated. Once you are done, we recommend updating your CI and CD pipelines to build and test the project using `tuist build` and `tuist test` commands to benefit from the speed and reliability that Tuist provides. ## Troubleshooting {#troubleshooting} ### Compilation errors due to missing files. {#compilation-errors-due-to-missing-files} If the files associated to your Xcode project targets were not all contained in a file-system directory representing the target, you might end up with a project that doesn't compile. Make sure the list of files after generating the project with Tuist matches the list of files in the Xcode project, and take the opportunity to align the file structure with the target structure. --- URL: "/pt/guides/develop/projects/adoption/migrate/swift-package" LLMS_URL: "/pt/guides/develop/projects/adoption/migrate/swift-package.md" title: "Migrate a Swift Package" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate from Swift Package Manager as a solution for managing your projects to Tuist projects." --- # Migrate a Swift Package {#migrate-a-swift-package} Swift Package Manager emerged as a dependency manager for Swift code that uninentionally found itself solving the problem of managing projects and supporting other programming languages like Objective-C. Because the tool was designed with a different purpose in mind, it can be challenging to use it to manage projects at scale because it lacks flexibility, performance, and power that Tuist provides. This is well captured in the [Scaling iOS at Bumble](https://medium.com/bumble-tech/scaling-ios-at-bumble-239e0fa009f2) article, which includes the following table comparing the performance of Swift Package Manager and native Xcode projects: A table that compares the regression in performance when using SPM over native Xcode projects We often come across developers and organizations that challenge the need for Tuist considering that Swift Package Manager can take a similar project management role. Some venture into a migration to later on realize that their developer experience has degraded signicantly. For instance, the rename of a file might take up to 15 seconds to re-index. 15 seconds! **Whether Apple will make Swift Package Manager a built-for-scale project manager is uncertain.** However, we are not seeing any signs that it's happening. In fact, we are seeing quite the opposite. They are making Xcode-inspired decisions, like achieving convenience through implicit configurations, which as you might know, is the source of complications at scale. We believe it'd take Apple to go to first principles and revisit some decisions that made sense as a dependency manager but not as a project manager, for example the usage of a compiled language as an interface to define projects. > [!TIP] SPM AS JUST A DEPENDENCY MANAGER > Tuist treats Swift Package Manager as a dependency manager, and it's a great one. We use it to resolve dependencies and to build them. We don't use it to define projects because it's not designed for that. ## Migrating from Swift Package Manager to Tuist {#migrating-from-swift-package-manager-to-tuist} The similarities between Swift Package Manager and Tuist make the migration process straightforward. The main difference is that you'll be defining your projects using Tuist's DSL instead of `Package.swift`. First, create a `Project.swift` file next to your `Package.swift` file. The `Project.swift` file will contain the definition of your project. Here's an example of a `Project.swift` file that defines a project with a single target: ```swift import ProjectDescription let project = Project( name: "App", targets: [ .target( name: "App", destinations: .iOS, product: .app, bundleId: "io.tuist.App", sources: ["Sources/**/*.swift"]* ), ] ) ``` Some things to note: - **ProjectDescription**: Instead of using `PackageDescription`, you'll be using `ProjectDescription`. - **Project:** Instead of exporting a `package` instance, you'll be exporting a `project` instance. - **Xcode language:** The primitives that you use to define your project mimic Xcode's language, so you'll find schemes, targets, and build phases among others. Then create a `Tuist.swift` file with the following content: ```swift import ProjectDescription let tuist = Tuist() ``` The `Tuist.swift` contains the configuration for your project and its path serves as a reference to determine the root of your project. You can check out the directory structure document to learn more about the structure of Tuist projects. ## Editing the project {#editing-the-project} You can use `tuist edit` to edit the project in Xcode. The command will generate an Xcode project that you can open and start working on. ```bash tuist edit ``` Depending on the size of the project, you might consider using it in one shot or incrementally. We recommend starting with a small project to get familiar with the DSL and the workflow. Our advise is always to start from the most depended upon target and work all the way up to the top-level target. --- URL: "/pt/guides/develop/projects/adoption/migrate/bazel-project" LLMS_URL: "/pt/guides/develop/projects/adoption/migrate/bazel-project.md" title: "Migrate a Bazel project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate your projects from Bazel to Tuist." --- # Migrate a Bazel project {#migrate-a-bazel-project} [Bazel](https://bazel.build) is a build system that Google open-sourced in 2015. It's a powerful tool that allows you to build and test software of any size, quickly and reliably. Some large organizations like [Spotify](https://engineering.atspotify.com/2023/10/switching-build-systems-seamlessly/), [Tinder](https://medium.com/tinder/bazel-hermetic-toolchain-and-tooling-migration-c244dc0d3ae), or [Lyft](https://semaphoreci.com/blog/keith-smiley-bazel) use it, however, it requires an upfront (i.e., learning the technology) and ongoing investment (i.e., keeping up with Xcode updates) to introduce and maintain. While this works for some organizations that treat it as a cross-cutting concern, it might not be the best fit for others that want to focus on their product development. For instance, we've seen organizations whose iOS platform team introduced Bazel and had to drop it after the engineers that led the effort left the company. Apple's stance on the strong coupling between Xcode and the build system is another factor that makes it hard to maintain Bazel projects over time. > [!TIP] TUIST UNIQUENESS LIES IN ITS FINESSE > Instead of fighting Xcode and Xcode projects, Tuist embraces it. It's the same concepts (e.g., targets, schemes, build settings), a familiar language (i.e., Swift), and a simple and enjoyable experience that makes maintaining and scaling projects everyone's job and not just the iOS platform team's. ## Rules {#rules} Bazel uses rules to define how to build and test software. The rules are written in [Starlark](https://github.com/bazelbuild/starlark), a Python-like language. Tuist uses Swift as a configuration language, which provides developers with the convenience of using Xcode's autocompletion, type-checking, and validation features. For example, the following rule describes how to build a Swift library in Bazel: ::: code-group ```txt [BUILD (Bazel)] swift_library( name = "MyLibrary.library", srcs = glob(["**/*.swift"]), module_name = "MyLibrary" ) ``` ```swift [Project.swift (Tuist)] let project = Project( // ... targets: [ .target(name: "MyLibrary", product: .staticLibrary, sources: ["**/*.swift"]) ] ) ``` ::: Here's another example but compating how to define unit tests in Bazel and Tuist: :::code-group ```txt [BUILD (Bazel)] ios_unit_test( name = "MyLibraryTests", bundle_id = "io.tuist.MyLibraryTests", minimum_os_version = "16.0", test_host = "//MyApp:MyLibrary", deps = [":MyLibraryTests.library"], ) ``` ```swift [Project.swift (Tuist)] let project = Project( // ... targets: [ .target( name: "MyLibraryTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyLibraryTests", sources: "Tests/MyLibraryTests/**", dependencies: [ .target(name: "MyLibrary"), ] ) ] ) ``` ::: ## Swift Package Manager dependencies {#swift-package-manager-dependencies} In Bazel, you can use the [`rules_swift_package_manager`](https://github.com/cgrindel/rules_swift_package_manager) [Gazelle](https://github.com/bazelbuild/bazel-gazelle/blob/master/extend.md) plugin to use Swift Packages as dependencies. The plugin requires a `Package.swift` as a source of truth for the dependencies. Tuist's interface is similar to Bazel's in that sense. You can use the `tuist install` command to resolve and pull the dependencies of the package. After the resolution completes, you can then generate the project with the `tuist generate` command. ```bash tuist install # Fetch dependencies defined in Tuist/Package.swift tuist generate # Generate an Xcode project ``` ## Project generation {#project-generation} The community provides a set of rules, [rules_xcodeproj](https://github.com/MobileNativeFoundation/rules_xcodeproj), to generate Xcode projects off Bazel-declared projects. Unlike Bazel, where you need to add some configuration to your `BUILD` file, Tuist doesn't require any configuration at all. You can run `tuist generate` in the root directory of your project, and Tuist will generate an Xcode project for you. --- URL: "/pt/guides/develop/projects" LLMS_URL: "/pt/guides/develop/projects.md" title: "Projects" titleTemplate: ":title · Develop · Guides · Tuist" description: "Learn about Tuist's DSL for defining Xcode projects." --- # Projects {#projects} Generated is a viable alternative that helps to overcome these challenges while keeping complexity and costs at an acceptable level. It considers Xcode projects as a fundamental element, ensuring resilience against future Xcode updates, and leverages Xcode project generation to provide teams with a modularization-focused declarative API. Tuist uses the project declaration to simplify the complexities of modularization\*\*, optimize workflows like build or test across various environments, and facilitate and democratize the evolution of Xcode projects. ## How does it work? {#how-does-it-work} To get started with generated projects, all you need is to define your project using **Tuist's Domain Specific Language (DSL)**. This entails using manifest files such as `Workspace.swift` or `Project.swift`. If you've worked with the Swift Package Manager before, the approach is very similar. Once you've defined your project, Tuist offers various workflows to manage and interact with it: - **Generate:** This is a foundational workflow. Use it to create an Xcode project that's compatible with Xcode. - **Build:** This workflow not only generates the Xcode project but also employs `xcodebuild` to compile it. - **Test:** Operating much like the build workflow, this not only generates the Xcode project but utilizes `xcodebuild` to test it. ## Challenges with Xcode projects {#challenges-with-xcode-projects} As Xcode projects grow, **organizations may face a decline in productivity** due to several factors, including unreliable incremental builds, frequent clearing of Xcode's global cache by developers encountering issues, and fragile project configurations. To maintain rapid feature development, organizations typically explore various strategies. Some organizations choose to bypass the compiler by abstracting the platform using JavaScript-based dynamic runtimes, such as [React Native](https://reactnative.dev/). While this approach may be effective, it [complicates access to the platform's native features](https://shopify.engineering/building-app-clip-react-native). Other organizations opt for **modularizing the codebase**, which helps establish clear boundaries, making the codebase easier to work with and improving the reliability of build times. However, the Xcode project format is not designed for modularity and results in implicit configurations that few understand and frequent conflicts. This leads to a bad bus factor, and although incremental builds may improve, developers might still frequently clear Xcode's build cache (i.e., derived data) when builds fail. To address this, some organizations choose to **abandon Xcode's build system** and adopt alternatives like [Buck](https://buck.build/) or [Bazel](https://bazel.build/). However, this comes with a [high complexity and maintenance burden](https://bazel.build/migrate/xcode). ## Alternatives {#alternatives} ### Swift Package Manager {#swift-package-manager} While the Swift Package Manager (SPM) primarily focuses on dependencies, Tuist offers a different approach. With Tuist, you don't just define packages for SPM integration; you shape your projects using familiar concepts like projects, workspaces, targets, and schemes. ### XcodeGen {#xcodegen} [XcodeGen](https://github.com/yonaskolb/XcodeGen) is a dedicated project generator designed to reduce conflicts in collaborative Xcode projects and simplify some complexities of Xcode's internal workings. However, projects are defined using serializable formats like [YAML](https://yaml.org/). Unlike Swift, this doesn't allow developers to build upon abstractions or checks without incorporating additional tools. While XcodeGen does offer a way to map dependencies to an internal representation for validation and optimization, it still exposes developers to the nuances of Xcode. This might make XcodeGen a suitable foundation for [building tools](https://github.com/MobileNativeFoundation/rules_xcodeproj), as seen in the Bazel community, but it's not optimal for inclusive project evolution that aims to maintain a healthy and productive environment. ### Bazel {#bazel} [Bazel](https://bazel.build) is an advanced build system renowned for its remote caching features, gaining popularity within the Swift community primarily for this capability. However, given the limited extensibility of Xcode and its build system, substituting it with Bazel's system demands significant effort and maintenance. Only a few companies with abundant resources can bear this overhead, as evident from the select list of firms investing heavily to integrate Bazel with Xcode. Interestingly, the community created a [tool](https://github.com/MobileNativeFoundation/rules_xcodeproj) that employs Bazel's XcodeGen to generate an Xcode project. This results in a convoluted chain of conversions: from Bazel files to XcodeGen YAML and finally to Xcode Projects. Such layered indirection often complicates troubleshooting, making issues more challenging to diagnose and resolve. --- URL: "/pt/guides/develop/insights" LLMS_URL: "/pt/guides/develop/insights.md" title: "Insights" titleTemplate: ":title · Develop · Guides · Tuist" description: "Get insights into your projects to maintain a product developer environment." --- # Insights {#insights} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project Working on large projects shouldn't feel like a chore. In fact, it should be as enjoyable as working on a project you started just two weeks ago. One of the reasons it is not is because as the project grows, the developer experience suffers. The build times increase and tests become slow and flaky. It's often easy to overlook these issues until it gets to a point where they become unbearable – however, at that point, it's difficult to address them. Tuist Insights provides you with the tools to monitor the health of your project and maintain a productive developer environment as your project scales. In other words, Tuist Insights helps you to anwer questions such as: - Has the build time significantly increased in the last week? - Have my tests become slower? Which ones? > [!NOTE] > Tuist Insights are in early development. ## Builds {#builds} While you probably have some metrics for the performance of CI workflows, you might not have the same visibility into the local development environment. However, local build times are one of the most important factors that contribute to the developer experience. To start tracking local build times, you can leverage the `tuist inspect build` command by adding it to your scheme's post-action: ![Post-action for inspecting builds](/images/guides/develop/insights/inspect-build-scheme-post-action.png) In case you're using [Mise](https://mise.jdx.dev/), your script will need to activate `tuist` in the post-action environment: ```sh # -C ensures that Mise loads the configuration from the Mise configuration # file in the project's root directory. eval "$($HOME/.local/bin/mise activate -C $SRCROOT bash --shims)" tuist inspect build ``` Your local builds are now tracked as long as you are logged in to your Tuist account. You can now access your build times in the Tuist dashboard and see how they evolve over time: > [!TIP] > To quickly access the dashboard, run `tuist project show --web` from the CLI. ![Dashboard with build insights](/images/guides/develop/insights/builds-dashboard.png) ## Projects {#projects} > [!NOTE] > Auto-generated schemes automatically include the `tuist inspect build` post-action. > > If you are not interested in tracking build insights in your auto-generated schemes, disable them using the buildInsightsDisabled generation option. If you are using generated projects, you can set up a custom build post-action using a custom scheme, such as: ```swift let project = Project( name: "MyProject", targets: [ // Your targets ], schemes: [ .scheme( name: "MyApp", shared: true, buildAction: .buildAction(targets: ["MyApp"]), testAction: .testAction(targets: ["MyAppTests"]), runAction: .runAction(configuration: "Debug"), postActions: [ .postAction( name: "Inspect Build", scriptText: """ eval \"$($HOME/.local/bin/mise activate -C $SRCROOT bash --shims)\" tuist inspect build """ ) ] ) ] ) ``` If you're not using Mise, your script can be simplified to just: ```swift .postAction( name: "Inspect Build", script: "tuist inspect build", execution: .always ) ``` ## Continuous integration {#continuous-integration} To track build times also on the CI, you will need to ensure that your CI is authenticated. Additionally, you will either need to: - Use the `tuist xcodebuild` command when invoking `xcodebuild` actions. - Add `-resultBundlePath` to your `xcodebuild` invocation. When `xcodebuild` builds your project without `-resultBundlePath`, the `.xcactivitylog` file is not generated. But the `tuist inspect build` post-action requires that file to be generated to analyze your build. --- URL: "/pt/guides/develop/cache" LLMS_URL: "/pt/guides/develop/cache.md" title: "Cache" titleTemplate: ":title · Develop · Guides · Tuist" description: "Optimize your build times by caching compiled binaries and sharing them across different environments." --- # Cache {#cache} > [!IMPORTANT] REQUIREMENTS > > - A generated project > - A Tuist account and project Xcode's build system provides [incremental builds](https://en.wikipedia.org/wiki/Incremental_build_model), enhancing efficiency under normal circumstances. However, this feature falls short in [Continuous Integration (CI) environments](https://en.wikipedia.org/wiki/Continuous_integration), where data essential for incremental builds is not shared across different builds. Additionally, **developers often reset this data locally to troubleshoot complex compilation problems**, leading to more frequent clean builds. This results in teams spending excessive time waiting for local builds to finish or for Continuous Integration pipelines to provide feedback on pull requests. Furthermore, the frequent context switching in such an environment compounds this unproductiveness. Tuist addresses these challenges effectively with its caching feature. This tool optimizes the build process by caching compiled binaries, significantly reducing build times both in local development and CI environments. This approach not only accelerates feedback loops but also minimizes the need for context switching, ultimately boosting productivity. ## Warming {#warming} Tuist efficiently utilizes hashes for each target in the dependency graph to detect changes. Utilizing this data, it builds and assigns unique identifiers to binaries derived from these targets. At the time of graph generation, Tuist then seamlessly substitutes the original targets with their corresponding binary versions. This operation, known as _"warming,"_ produces binaries for local use or for sharing with teammates and CI environments via Tuist. The process of warming the cache is straightforward and can be initiated with a simple command: ```bash tuist cache ``` The command re-uses binaries to speed up the process. ## Usage {#usage} By default, when Tuist commands necessitate project generation, they automatically substitute dependencies with their binary equivalents from the cache, if available. Additionally, if you specify a list of targets to focus on, Tuist will also replace any dependent targets with their cached binaries, provided they are available. For those who prefer a different approach, there is an option to opt out of this behavior entirely by using a specific flag: ::: code-group ```bash [Project generation] tuist generate # Only dependencies tuist generate Search # Dependencies + Search dependencies tuist generate Search Settings # Dependencies, and Search and Settings dependencies tuist generate --no-binary-cache # No cache at all ``` ```bash [Testing] tuist test ``` ::: > [!WARNING] > Binary caching is a feature designed for development workflows such as running the app on a simulator or device, or running tests. It is not intended for release builds. When archiving the app, generate a project with the sources by using the `--no-binary-cache` flag. ## Supported products {#supported-products} Only the following target products are cacheable by Tuist: - Frameworks (static and dynamic) that don't depend on [XCTest](https://developer.apple.com/documentation/xctest) - Bundles - Swift Macros We are working on supporting libraries and targets that depend on XCTest. > [!NOTE] UPSTREAM DEPENDENCIES > When a target is non-cacheable it makes the upstream targets non-cacheable too. For example, if you have the dependency graph `A > B`, where A depends on B, if B is non-cacheable, A will also be non-cacheable. ## Efficiency {#efficiency} The level of efficiency that can be achieved with binary caching depends strongly on the graph structure. To achieve the best results, we recommend the following: 1. Avoid very nested dependency graphs. The shallower the graph, the better. 2. Define dependencies with protocol/interface targets instead of implementation ones, and dependency-inject implementations from the top-most targets. 3. Split frequently-modified targets into smaller ones whose likelihood of change is lower. The above suggestions are part of the The Modular Architecture, which we propose as a way to structure your projects to maximize the benefits not only of binary caching but also of Xcode's capabilities. ## Recommended setup {#recommended-setup} We recommend having a CI job that **runs in every commit in the main branch** to warm the cache. This will ensure the cache always contains binaries for the changes in `main` so local and CI branch build incrementally upon them. > [!TIP] CACHE WARMING USES BINARIES > The `tuist cache` command also makes use of the binary cache to speed up the warming. The following are some examples of common workflows: ### A developer starts to work on a new feature {#a-developer-starts-to-work-on-a-new-feature} 1. They create a new branch from `main`. 2. They run `tuist generate`. 3. Tuist pulls the most recent binaries from `main` and generates the project with them. ### A developer pushes changes upstream {#a-developer-pushes-changes-upstream} 1. The CI pipeline will run `tuist build` or `tuist test` to build or test the project. 2. The workflow will pull the most recent binaries from `main` and generate the project with them. 3. It will then build or test the project incrementally. ## Troubleshooting {#troubleshooting} ### It doesn't use binaries for my targets {#it-doesnt-use-binaries-for-my-targets} Ensure that the hashes are deterministic across environments and runs. This might happen if the project has references to the environment, for example through absolute paths. You can use the `diff` command to compare the projects generated by two consecutive invocations of `tuist generate` or across environments or runs. Also make sure that the target doesn't depend either directly or indirectly on a non-cacheable target. --- URL: "/pt/guides/develop/build" LLMS_URL: "/pt/guides/develop/build.md" title: "Build" titleTemplate: ":title · Develop · Guides · Tuist" description: "Learn how to use Tuist to build your projects efficiently." --- # Build {#build} Projects are usually built through a build-system-provided CLI (e.g. `xcodebuild`). Tuist wraps them to improve the user experience and integrate the workflows with the platform to provide optimizations and analytics. You might wonder what's the value of using `tuist build` over generating the project with `tuist generate` (if needed) and building it with the platform-specific CLI. Here are some reasons: - **Single command:** `tuist build` ensures the project is generated if needed before compiling the project. - **Beautified output:** Tuist enriches the output using tools like [xcbeautify](https://github.com/cpisciotta/xcbeautify) that make the output more user-friendly. - Cache: It optimizes the build by deterministically reusing the build artifacts from a remote cache. - **Analytics:** It collects and reports metrics that are correlated with other data points to provide you with actionable information to make informed decisions. ## Usage {#usage} `tuist build` generates the project if needed, and then build it using the platform-specific build tool. We support the use of the `--` terminator to forward all subsequent arguments directly to the underlying build tool. This is useful when you need to pass arguments that are not supported by `tuist build` but are supported by the underlying build tool. ::: code-group ```bash [Build a scheme] tuist build MyScheme ``` ```bash [Build a specific configuration] tuist build MyScheme -- -configuration Debug ``` ```bash [Build all schemes without binary cache] tuist build --no-binary-cache ``` ::: --- URL: "/pt/guides/automate/continuous-integration" LLMS_URL: "/pt/guides/automate/continuous-integration.md" title: "Continuous Integration (CI)" titleTemplate: ":title · Automate · Guides · Tuist" description: "Learn how to use Tuist in your CI workflows." --- # Continuous Integration (CI) {#continuous-integration-ci} You can use Tuist in [continuous integration](https://en.wikipedia.org/wiki/Continuous_integration) environments. The following sections provide examples of how to do this on different CI platforms. ## Examples {#examples} To run Tuist commands in your CI workflows, you’ll need to install it in your CI environment. ### Xcode Cloud {#xcode-cloud} In [Xcode Cloud](https://developer.apple.com/xcode-cloud/), which uses Xcode projects as the source of truth, you'll need to add a [post-clone](https://developer.apple.com/documentation/xcode/writing-custom-build-scripts#Create-a-custom-build-script) script to install Tuist and run the commands you need, for example `tuist generate`: :::code-group ```bash [Mise] #!/bin/sh curl https://mise.jdx.dev/install.sh | sh mise install # Installs the version from .mise.toml # Runs the version of Tuist indicated in the .mise.toml file {#runs-the-version-of-tuist-indicated-in-the-misetoml-file} mise exec -- tuist generate ``` ```bash [Homebrew] #!/bin/sh brew install --formula tuist@x.y.z tuist generate ``` ::: ### Codemagic {#codemagic} In [Codemagic](https://codemagic.io), you can add an additional step to your workflow to install Tuist: ::: code-group ```yaml [Mise] workflows: lint: name: Build max_build_duration: 30 environment: xcode: 15.0.1 scripts: - name: Install Mise script: | curl https://mise.jdx.dev/install.sh | sh mise install # Installs the version from .mise.toml - name: Build script: mise exec -- tuist build ``` ```yaml [Homebrew] workflows: lint: name: Build max_build_duration: 30 environment: xcode: 15.0.1 scripts: - name: Install Tuist script: | brew install --formula tuist@x.y.z - name: Build script: tuist build ``` ::: ### GitHub Actions {#github-actions} On [GitHub Actions](https://docs.github.com/en/actions) you can add an additional step to install Tuist, and in the case of managing the installation of Mise, you can use the [mise-action](https://github.com/jdx/mise-action), which abstracts the installation of Mise and Tuist: ::: code-group ```yaml [Mise] name: Build Application on: pull_request: branches: - main push: branches: - main jobs: build: runs-on: macos-latest steps: - uses: actions/checkout@v3 - uses: jdx/mise-action@v2 - run: tuist build ``` ```yaml [Homebrew] name: test on: pull_request: branches: - main push: branches: - main jobs: lint: runs-on: macos-latest steps: - uses: actions/checkout@v3 - run: brew install --formula tuist@x.y.z - run: tuist build ``` ::: :::tip We recommend using `mise use --pin` in your Tuist projects to pin the version of Tuist across environments. The command will create a `.tool-versions` file containing the version of Tuist. ::: ## Authentication {#authentication} When using server-side features such as cache, you'll need a way to authenticate requests going from your CI workflows to the server. For that, you can generate a project-scoped token by running the following command: ```bash tuist project tokens create my-handle/MyApp ``` The command will generate a token for the project with full handle `my-account/my-project`. Set the value to the environment variable `TUIST_CONFIG_TOKEN` in your CI environment ensuring it's configured as a secret so it's not exposed. > [!IMPORTANT] CI ENVIRONMENT DETECTION > Tuist only uses the token when it detects it's running on a CI environment. If your CI environment is not detected, you can force the token usage by setting the environment variable `CI` to `1`. --- URL: "/pt/guides/ai/mcp" LLMS_URL: "/pt/guides/ai/mcp.md" title: "Model Context Protocol (MCP)" titleTemplate: ":title · AI · Guides · Tuist" description: "Learn how to use Tuist's MCP server to have a language-based interface for your app development environment." --- # Model Context Protocol (MCP) [Model Context Protocol (MCP)](https://www.claudemcp.com) is a standard proposed by [Claude](https://claude.ai) for LLMs to interact with development environments. You can think of it as the USB-C of LLMs. Like shipping containers, which made cargo and transportation more interoperable, or protocols like TCP, which decoupled the application layer from the transport layer, MCP makes LLM-powered applications such as [Claude](https://claude.ai/) and editors like [Zed](https://zed.dev) or [Cursor](https://www.cursor.com) interoperable with other domains. Tuist provides a local server through its CLI so that you can interact with your **app development environment**. By connecting your client apps to it, you can use language to interact with your projects. In this page you'll learn about how to set it up and its capabilities. > [!NOTE] > Tuist MCP server uses Xcode's most-recent projects as the source of truth for projects you want to interact with. ## Set it up ### [Claude](https://claude.ai) If you are using [Claude desktop](https://claude.ai/download), you can run the tuist mcp setup claude command to configure your Claude environment. Alternatively, can manually edit the file at `~/Library/Application\ Support/Claude/claude_desktop_config.json`, and add the Tuist MCP server: :::code-group ```json [Global Tuist installation (e.g. Homebrew)] { "mcpServers": { "tuist": { "command": "tuist", "args": ["mcp", "start"] } } } ``` ```json [Mise installation] { "mcpServers": { "tuist": { "command": "mise", "args": ["x", "tuist@latest", "--", "tuist", "mcp", "start"] // Or tuist@x.y.z to fix the version } } } ``` ::: ## Capabilities In the following sections you'll learn about the capabilities of the Tuist MCP server. ### Resources #### Recent projects and workspaces Tuist keeps a record of the Xcode projects and workspaces you’ve recently worked with, giving your application access to their dependency graphs for powerful insights. You can query this data to uncover details about your project structure and relationships, such as: - What are the direct and transitive dependencies of a specific target? - Which target has the most source files, and how many does it include? - What are all the static products (e.g., static libraries or frameworks) in the graph? - Can you list all targets, sorted alphabetically, along with their names and product types (e.g., app, framework, unit test)? - Which targets depend on a particular framework or external dependency? - What’s the total number of source files across all targets in the project? - Are there any circular dependencies between targets, and if so, where? - Which targets use a specific resource (e.g., an image or plist file)? - What’s the deepest dependency chain in the graph, and which targets are involved? - Can you show me all the test targets and their associated app or framework targets? - Which targets have the longest build times based on recent interactions? - What are the differences in dependencies between two specific targets? - Are there any unused source files or resources in the project? - Which targets share common dependencies, and what are they? With Tuist, you can dig into your Xcode projects like never before, making it easier to understand, optimize, and manage even the most complex setups! --- URL: "/pt/contributors/translate" LLMS_URL: "/pt/contributors/translate.md" title: "Translate" titleTemplate: ":title · Contributors · Tuist" description: "This document describes the principles that guide the development of Tuist." --- # Translate {#translate} Languages can be barriers to understanding. We want to make sure that Tuist is accessible to as many people as possible. If you speak a language that Tuist doesn't support, you can help us by translating the various surfaces of Tuist. Since maintaining translations is a continuous effort, we add languages as we see contributors willing to help us maintain them. The following languages are currently supported: - English - Korean - Japanese - Russian > [!TIP] REQUEST A NEW LANGUAGE > If you believe Tuist would benefit from supporting a new language, please create a new [topic in the community forum](https://community.tuist.io/c/general/4) to discuss it with the community. ## How to translate We use [Crowdin](https://crowdin.com/) to manage the translations. First, go to the project that you want to contribute to: - [Documentation](https://crowdin.com/project/tuist-documentation) - [Website](https://crowdin.com/project/tuist-documentation) You'll need an account to start translating. You can sign in with GitHub. Once you have access, you can start translating. You'll see the list of resources that are available for translation. When you click on a resource, the editor will open, and you'll see a split view with the resource in the source language on the left and the translation on the right. Translate the text on the right and save your changes. As translations are updated, Crowdin will push them automatically to the right repository opening a pull request, which the maintainers will review and merge. > [!IMPORTANT] DON'T MODIFY THE RESOURCES IN THE TARGET LANGUAGE > Crowdin segments the files to bind source and target languages. If you modify the source language, you'll break the binding, and the reconciliation might yield unexpected results. ## Guidelines The following are the guidelines we follow when translating. ### Custom containers and GitHub alerts When translating [custom containers](https://vitepress.dev/guide/markdown#custom-containers) or [GitHub Alerts](https://docs.github.com/pt/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts), only translate the title and the content **but not the type of alert**. :::details Example with GitHub Alert ````markdown > [!WARNING] 루트 변수 > 매니페스트의 루트에 있어야 하는 변수는... // Instead of > [!주의] 루트 변수 > 매니페스트의 루트에 있어야 하는 변수는... ``` ::: ::: details Example with custom container ```markdown ::: warning 루트 변수\ 매니페스트의 루트에 있어야 하는 변수는... ::: # Instead of ::: 주의 루트 변수\ 매니페스트의 루트에 있어야 하는 변수는... ::: ```` ::: ### Heading titles When translating headings, only translate tht title but not the id. For example, when translating the following heading: ```markdown # Add dependencies {#add-dependencies} ``` It should be translated as (note the id is not translated): ```markdown # 의존성 추가하기 {#add-dependencies} ``` --- URL: "/pt/contributors/principles" LLMS_URL: "/pt/contributors/principles.md" title: "Principles" titleTemplate: ":title · Contributors · Tuist" description: "This document describes the principles that guide the development of Tuist." --- # Principles {#principles} This page describes principles that are pillars to the design and development of Tuist. They evolve with the project and are meant to ensure a sustainable growth that is well-aligned with the project foundation. ## Default to conventions {#default-to-conventions} One of the reasons why Tuist exists is because Xcode is weak in conventions and that leads to complex projects that are hard to scale up and maintain. For that reason, Tuist takes a different approach by defaulting to simple and thoroughly designed conventions. **Developers can opt-out from the conventions, but that’s a conscious decision that doesn’t feel natural.** For example, there’s a convention for defining dependencies between targets by using the provided public interface. By doing that, Tuist ensures that the projects are generated with the right configurations for the linking to work. Developers have the option to define the dependencies through build settings, but they’d be doing it implicitly and therefore breaking Tuist features such as `tuist graph` or `tuist cache` that rely on some conventions being followed. The reason why we default to conventions is that the more decision we can make on behalf of the developers, the more focus they’ll have crafting features for their apps. When we are left with no conventions like it’s the case in many projects, we have to make decisions that will end up not being consistent with other decisions and as a consequence, there’ll be an accidental complexity that will be hard to manage. ## Manifests are the source of truth {#manifests-are-the-source-of-truth} Having many layers of configurations and contracts between them results in a project setup that is hard to reason about and maintain. Think for a second on an average project. The definition of the project lives in the `.xcodeproj` directories, the CLI in scripts (e.g `Fastfiles`), and the CI logic in pipelines. Those are three layers with contracts between them that we need to maintain. _How often have you been in a situation where you changed something in your projects, and then a week later you realized that the release scripts broke?_ We can simplify this by having a single source of truth, the manifest files. Those files provide Tuist with the information that it needs to generate Xcode projects that developers can use to edit their files. Moreover, it allows having standard commands for building projects from a local or CI environment. **Tuist should own the complexity and expose a simple, safe, and enjoyable interface to describe their projects as explicitly as possible.** ## Make the implicit explicit {#make-the-implicit-explicit} Xcode supports implicit configurations. A good example of that is inferring the implicitly defined dependencies. While implicitness is fine for small projects, where configurations are simple, as projects get larger it might cause slowness or odd behaviors. Tuist should provide explicit APIs for implicit Xcode behaviors. It should also support defining Xcode implicitness but implemented in such a way that encourages developers to opt for the explicit approach. Supporting Xcode implicitness and intricacies facilitates the adoption of Tuist, after which teams can take some time to get rid of the implicitness. The definition of dependencies is a good example of that. While developers can define dependencies through build settings and phases, Tuist provides a beautiful API that encourages its adoption. **Designing the API to be explicit allows Tuist to run some checks and optimizations on the projects that otherwise wouldn’t be possible.** Moreover, it enables features like `tuist graph`, which exports a representation of the dependency graph, or `tuist cache`, which caches all the targets as binaries. > [!TIP] > We should treat each request to port features from Xcode as an opportunity to simplify concepts with simple and explicit APIs. ## Keep it simple {#keep-it-simple} One of the main challenges when scaling Xcode projects comes from the fact that **Xcode exposes a lot of complexity to the users.** Due to that, teams have a high bus factor and only a few people in the team understand the project and the errors that the build system throws. That’s a bad situation to be in because the team relies on a few people. Xcode is a great tool, but so many years of improvements, new platforms, and programming languages, are reflected on their surface, which struggled to remain simple. Tuist should take the opportunity to keep things simple because working on simple things is fun and motivates us. No one wants to spend time trying to debug an error that happens at the very end of the compilation process, or understanding why they are not able to run the app on their devices. Xcode delegates the tasks to its underlying build system and in some cases it does a very poor job translating errors into actionable items. Have you ever got a _“framework X not found”_ error and you didn’t know what to do? Imagine if we got a list of potential root causes for the bug. ## Start from the developer’s experience {#start-from-the-developers-experience} Part of the reason why there is a lack of innovation around Xcode, or put differently, not as much as in other programming environments, is because **we often start analyzing problems from existing solutions.** As a consequence, most of the solutions that we find nowadays revolve around the same ideas and workflows. While it’s good to include existing solutions in the equations, we should not let them constrain our creativity. We like to think as [Tom Preston](https://tom.preston-werner.com/) puts it in [this podcast](https://tom.preston-werner.com/): _“Most things can be achieved, whatever you have in your head you can probably pull off with code as long as is possible within the constrains of the universe”._ If **we imagine how we’d like the developer experience to be**, it’s just a matter of time to pull it off — by starting to analyze the problems from the developer experience gives us a unique point of view that will lead us to solutions that users will love to use. We might feel tempted to follow what everyone is doing, even if that means sticking with the inconveniences that everyone continues to complain about. Let’s not do that. How do I imagine archiving my app? How would I love code signing to be? What processes can I help streamline with Tuist? For example, adding support for [Fastlane](https://fastlane.tools/) is a solution to a problem that we need to understand first. We can get to the root of the problem by asking “why” questions. Once we narrow down where the motivation comes from, we can think of how Tuist can help them best. Maybe the solution is integrating with Fastlane, but it’s important we don’t disregard other equally valid solutions that we can put on the table before making trade-offs. ## Errors can and will happen {#errors-can-and-will-happen} We, developers, have an inherent temptation to disregard that errors can happen. As a result, we design and test software only considering the ideal scenario. Swift, its type system, and a well-architected code might help prevent some errors, but not all of them because some are out of our control. We can’t assume the user will always have an internet connection, or that the system commands will return successfully. The environments in which Tuist runs are not sandboxes that we control, and hence we need to make an effort to understand how they might change and impact Tuist. Poorly handled errors result in bad user experience, and users might lose trust in the project. We want users to enjoy every single piece of Tuist, even the way we present errors to them. We should put ourselves in the shoes of users and imagine what we’d expect the error to tell us. If the programming language is the communication channel through which errors propagate, and the users are the destination of the errors, they should be written in the same language that the target (users) speak. They should include enough information to know what happened and hide the information that is not relevant. Also, they should be actionable by telling users what steps they can take to recover from them. And last but not least, our test cases should contemplate failing scenarios. Not only they ensure that we are handling errors as we are supposed to, but prevent future developers from breaking that logic. --- URL: "/pt/contributors/issue-reporting" LLMS_URL: "/pt/contributors/issue-reporting.md" title: "Issue reporting" titleTemplate: ":title · Contributors · Tuist" description: "Learn how to contribute to Tuist by reporting bugs" --- # Issue reporting {#issue-reporting} As user of Tuist, you might come across bugs or unexpected behaviors. If you do, we encourage you to report them so that we can fix them. ## GitHub issues is our ticketing platform {#github-issues-is-our-ticketing-platform} Issues should be reported on GitHub as [GitHub issues](https://github.com/tuist/tuist/issues) and not on Slack or other platforms. GitHub is better for tracing and managing issues, is closer to the codebase, and allows us to track the progress of the issue. Moreover, it encourages a long-form description of the problem, which forces the reporter to think about the problem and provide more context. ## Context is crucial {#context-is-crucial} An issue without enough context will be deemed incomplete and the author will be asked for additional context. If not provided, the issue will be closed. Think about it this way: the more context you provide, the easier it is for us to understand the problem and fix it. So if you want your issue to be fixed, provide as much context as possible. Try to answer the following questions: - What were you trying to do? - How does your graph look? - What version of Tuist are you using? - Is this blocking you? We also require you to provide a minimal **reproducible project**. ## Reproducible project {#reproducible-project} ### What is a reproducible project? {#what-is-a-reproducible-project} A reproducible project is a small Tuist project to demonstrate a problem - often this problem is caused by a bug in Tuist. Your reproducible project should contain the bare minimum features needed to clearly demonstrate the bug. ### Why should you create a reproducible test case? {#why-should-you-create-a-reproducible-test-case} A reproducible projects lets us isolate the cause of a problem, which is the first step towards fixing it! The most important part of any bug report is to describe the exact steps needed to reproduce the bug. A reproducible project is a great way to share a specific environment that causes a bug. Your reproducible project is the best way to help people that want to help you. ### Steps to create a reproducible project {#steps-to-create-a-reproducible-project} - Create a new git repository. - Initialize a project using `tuist init` in the repository directory. - Add the code needed to recreate the error you’ve seen. - Publish the code (your GitHub account is a good place to do this) and then link to it when creating an issue. ### Benefits of reproducible projects {#benefits-of-reproducible-projects} - **Smaller surface area:** By removing everything but the error, you don’t have to dig to find the bug. - **No need to publish secret code:** You might not be able to publish your main site (for many reasons). Remaking a small part of it as a reproducible test case allows you to publicly demonstrate a problem without exposing any secret code. - **Proof of the bug:** Sometimes a bug is caused by some combination of settings on your machine. A reproducible test case allows contributors to pull down your build and test it on their machines as well. This helps verify and narrow down the cause of a problem. - **Get help with fixing your bug:** If someone else can reproduce your problem, they often have a good chance of fixing the problem. It’s almost impossible to fix a bug without first being able to reproduce it. --- URL: "/pt/contributors/get-started" LLMS_URL: "/pt/contributors/get-started.md" title: "Get started" titleTemplate: ":title · Contributors · Tuist" description: "Get started contributing to Tuist by following this guide." --- # Get started {#get-started} If you have experience building apps for Apple platforms, like iOS, adding code to Tuist shouldn’t be much different. There are two differences compared to developing apps that are worth mentioning: - **The interactions with CLIs happen through the terminal.** The user executes Tuist, which performs the desired task, and then returns successfully or with a status code. During the execution, the user can be notified by sending output information to the standard output and standard error. There are no gestures, or graphical interactions, just the user intent. - **There’s no runloop that keeps the process alive waiting for input**, like it happens in an iOS app when the app receives system or user events. CLIs run in its process and finishes when the work is done. Asynchronous work can be done using system APIs like [DispatchQueue](https://developer.apple.com/documentation/dispatch/dispatchqueue) or [structured concurrency](https://developer.apple.com/tutorials/app-dev-training/managing-structured-concurrency), but need to make sure the process is running while the asynchronous work is being executed. Otherwise, the process will terminate the asynchronous work. If you don’t have any experience with Swift, we recommend [Apple’s official book](https://docs.swift.org/swift-book/) to get familiar with the language and the most used elements from the Foundation’s API. ## Minimum requirements {#minimum-requirements} To contribute to Tuist, minimum requirements are: - macOS 14.0+ - Xcode 16.3+ ## Set up the project locally {#set-up-the-project-locally} To start working on the project, we can follow the steps below: - Clone the repository by running: `git clone git@github.com:tuist/tuist.git` - [Install](https://mise.jdx.dev/getting-started.html) Mise to provision the development environment. - Run `mise install` to install the system dependencies needed by Tuist - Run `tuist install` to install the external dependencies needed by Tuist - (Optional) Run `tuist auth login` to get access to the Tuist Cache - Run `tuist generate` to generate the Tuist Xcode project using Tuist itself **The generated project opens automatically**. If you need to open again without generating it, run open `Tuist.xcworkspace` (or use Finder). > [!NOTE] XED . > If you try to open the project using `xed .`, it will open the package, and not the project generated by Tuist. We recommend using the Tuist-generated project to dog-food the tool. ## Edit the project {#edit-the-project} If you needed to edit the project, for example to add dependencies or adjust targets, you can use the `tuist edit` command. This is barely used, but it's good to know that it exists. ## Run Tuist {#run-tuist} ### From Xcode {#from-xcode} To run `tuist` from the generated Xcode project, edit the `tuist` scheme, and set the arguments that you'd like to pass to the command. For example, to run the `tuist generate` command, you can set the arguments to `generate --no-open` to prevent the project from opening after the generation. ![An example of a scheme configuration to run the generate command with Tuist](/images/contributors/scheme-arguments.png) You'll also have to set the working directory to the root of the project being generated. You can do that either by using the `--path` argument, which all the commands accept, or configuring the working directory in the scheme as shown below: ![An example of how to set the working directory to run Tuist](/images/contributors/scheme-working-directory.png) > [!WARNING] PROJECTDESCRIPTION COMPILATION > The `tuist` CLI depends on the `ProjectDescription` framework's presence in the built products directory. If `tuist` fails to run because it can't find the `ProjectDescription` framework, build the `Tuist-Workspace` scheme first. ### From the terminal {#from-the-terminal} You can run `tuist` using Tuist itself through its `run` command: ```bash tuist run tuist generate --path /path/to/project --no-open ``` Alternatively, you can also run it through the Swift Package Manager directly: ```bash swift build --product ProjectDescription swift run tuist generate --path /path/to/project --no-open ``` --- URL: "/pt/contributors/code-reviews" LLMS_URL: "/pt/contributors/code-reviews.md" title: "Code reviews" titleTemplate: ":title · Contributors · Tuist" description: "Learn how to contribute to Tuist by reviewing code" --- # Code reviews {#code-reviews} Reviewing pull requests is a common type of contribution. Despite continuous integration (CI) ensuring the code does what’s supposed to do, it’s not enough. There are contribution traits that can’t be automated: design, code structure & architecture, tests quality, or typos. The following sections represent different aspects of the code review process. ## Readability {#readability} Does the code express its intention clearly? **If you need to spend a bunch of time figuring out what the code does, the code implementation needs to be improved.** Suggest splitting the code into smaller abstractions that are easier to understand. Alternative, and as a last resource, they can add a comment explaining the reasoning behind it. Ask yourself if you’d be able to understand the code in a near future, without any surrounding context like the pull request description. ## Small pull requests {#small-pull-requests} Large pull requests are hard to review and it’s easier to miss out details. If a pull request becomes too large and unmanageable, suggest the author to break it down. > [!NOTE] EXCEPTIONS > There are few scenarios where splitting up the pull request is not possible, like when the changes are tightly coupled and can’t be split. In those cases, the author should provide a clear explanation of the changes and the reasoning behind them. ## Consistency {#consistency} It’s important that the changes are consistent with the rest of the project. Inconsistencies complicate maintenance, and therefore we should avoid them. If there’s an approach to output messages to the user, or report errors, we should stick to that. If the author disagrees with the project’s standards, suggest them to open an issue where we can discuss them further. ## Tests {#tests} Tests allow changing code with confidence. The code on pull requests should be tested, and all tests should pass. A good test is a test that consistently produces the same result and that it’s easy to understand and maintain. Reviewers spend most of the review time in the implementation code, but tests are equally important because they are code too. ## Breaking changes {#breaking-changes} Breaking changes are a bad user experience for users of Tuist. Contributions should avoid introducing breaking changes unless it’s strictly necessary. There are many language features that we can leverage to evolve the interface of Tuist without resorting to a breaking change. Whether a change is breaking or not might not be obvious. A method to verify whether the change is breaking is running Tuist against the fixture projects in the fixtures directory. It requires putting ourselves in the user’s shoes and imagine how the changes would impact them. --- URL: "/pt/contributors/cli/logging" LLMS_URL: "/pt/contributors/cli/logging.md" title: "Logging" titleTemplate: ":title · CLI · Contributors · Tuist" description: "Learn how to contribute to Tuist by reviewing code" --- # Logging {#logging} The CLI embraces the [swift-log](https://github.com/apple/swift-log) interface for logging. The package abstracts away the implementation details of logging, allowing the CLI to be agnostic to the logging backend. The logger is dependency-injected using [swift-service-context](https://github.com/apple/swift-service-context) and can be accessed anywhere using: ```bash ServiceContext.current?.logger ``` > [!NOTE] > `swift-service-context` passes the instance using [task locals](https://developer.apple.com/documentation/swift/tasklocal) which don't propagate the value when using `Dispatch`, so if you run asynchronous code using `Dispatch`, you'll to get the instance from the context and pass it to the asynchronous operation. ## What to log {#what-to-log} Logs are not the CLI's UI. They are a tool to diagnose issues when they arise. Therefore, the more information you provide, the better. When building new features, put yourself in the shoes of a developer coming across unexpected behavior, and think about what information would be helpful to them. Ensure you you use the right [log level](https://www.swift.org/documentation/server/guides/libraries/log-levels.html). Otherwise developers won't be able to filter out the noise. --- URL: "/pt/cli/shell-completions" LLMS_URL: "/pt/cli/shell-completions.md" title: "Shell completions" titleTemplate: ":title · CLI · Tuist" description: "Learn how to configure your shell to auto-complete Tuist commands." --- # Shell completions If you have Tuist **globally installed** (e.g., via Homebrew), you can install shell completions for Bash and Zsh to autocomplete commands and options. :::warning WHAT IS A GLOBAL INSTALLATION A global installation is an installation that's available in your shell's `$PATH` environment variable. This means you can run `tuist` from any directory in your terminal. This is the default installation method for Homebrew. ::: #### Zsh {#zsh} If you have [oh-my-zsh](https://ohmyz.sh/) installed, you already have a directory of automatically loading completion scripts — `.oh-my-zsh/completions`. Copy your new completion script to a new file in that directory called `_tuist`: ```bash tuist --generate-completion-script > ~/.oh-my-zsh/completions/_tuist ``` Without `oh-my-zsh`, you'll need to add a path for completion scripts to your function path, and turn on completion script autoloading. First, add these lines to `~/.zshrc`: ```bash fpath=(~/.zsh/completion $fpath) autoload -U compinit compinit ``` Next, create a directory at `~/.zsh/completion` and copy the completion script to the new directory, again into a file called `_tuist`. ```bash tuist --generate-completion-script > ~/.zsh/completion/_tuist ``` #### Bash {#bash} If you have [bash-completion](https://github.com/scop/bash-completion) installed, you can just copy your new completion script to file `/usr/local/etc/bash_completion.d/_tuist`: ```bash tuist --generate-completion-script > /usr/local/etc/bash_completion.d/_tuist ``` Without bash-completion, you'll need to source the completion script directly. Copy it to a directory such as `~/.bash_completions/`, and then add the following line to `~/.bash_profile` or `~/.bashrc`: ```bash source ~/.bash_completions/example.bash ``` #### Fish {#fish} If you use [fish shell](https://fishshell.com), you can copy your new completion script to `~/.config/fish/completions/tuist.fish`: ```bash mkdir -p ~/.config/fish/completions tuist --generate-completion-script > ~/.config/fish/completions/tuist.fish ``` --- URL: "/pt/cli/logging" LLMS_URL: "/pt/cli/logging.md" title: "Logging" titleTemplate: ":title · CLI · Tuist" description: "Learn how to enable and configure logging in Tuist." --- # Logging {#logging} The CLI logs messages internally to help you diagnose issues. ## Diagnose issues using logs {#diagnose-issues-using-logs} If a command invocation doesn't yield the intended results, you can diagnose the issue by inspecting the logs. The CLI forwards the logs to [OSLog](https://developer.apple.com/documentation/os/oslog) and the file-system. In every run, it creates a log file at `$XDG_STATE_HOME/tuist/logs/{uuid}.log` where `$XDG_STATE_HOME` takes the value `~/.local/state` if the environment variable is not set. By default, the CLI outputs the logs path when the execution exits unexpectedly. If it doesn't, you can find the logs in the path mentioned above (i.e., the most recent log file). > [!IMPORTANT] > Sensitive information is not redacted, so be cautious when sharing logs. ### Continuous integration {#diagnose-issues-using-logs-ci} In CI, where environments are disposable, you might want to configure your CI pipeline to export Tuist logs. Exporting artifacts is a common capability across CI services, and the configuration depends on the service you use. For example, in GitHub Actions, you can use the `actions/upload-artifact` action to upload the logs as an artifact: ```yaml name: Node CI on: [push] env: XDG_STATE_HOME: /tmp jobs: build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 # ... other steps - run: tuist generate # ... do something with the project - name: Export Tuist logs uses: actions/upload-artifact@v4 with: name: tuist-logs path: /tmp/tuist/logs/*.log ``` --- URL: "/pt/cli/[command]" LLMS_URL: "/pt/cli/[command].md" editLink: false titleTemplate: ":title · CLI · Tuist" --- --- URL: "/ko/server/on-premise/metrics" LLMS_URL: "/ko/server/on-premise/metrics.md" title: "Metrics" titleTemplate: ":title | On-premise | Server | Tuist" description: "컴파일된 바이너리를 캐싱하고 다양한 환경 간에 공유하여 빌드 시간을 최적화하세요." --- # 메트릭 {#metrics} Tuist 서버에서 수집한 메트릭을 [Prometheus](https://prometheus.io/)를 통해 가져오고 [Grafana](https://grafana.com/)와 같은 시각화 도구를 활용하여 사용자 요구에 맞는 커스텀 대시보드를 생성할 수 있습니다. Prometheus 메트릭은 9091 port의 `/metrics` endpoint를 통해 제공됩니다. Prometheus의 [scrape_interval](https://prometheus.io/docs/introduction/first_steps/#configuring-prometheus)은 10,000초 미만으로 설정해야 합니다 (기본 값인 15 초로 유지할 것을 권장합니다). ## Elixir 메트릭 {#elixir-metrics} 기본적으로 Elixir 런타임, BEAM, Elixir, 그리고 사용하는 일부 라이브러리의 메트릭이 포함되어 있습니다. 다음은 확인할 수 있는 메트릭의 일부입니다: - [Application](https://hexdocs.pm/prom_ex/PromEx.Plugins.Application.html) - [BEAM](https://hexdocs.pm/prom_ex/PromEx.Plugins.Beam.html) - [Phoenix](https://hexdocs.pm/prom_ex/PromEx.Plugins.Phoenix.html) - [Phoenix LiveView](https://hexdocs.pm/prom_ex/PromEx.Plugins.PhoenixLiveView.html) - [Ecto](https://hexdocs.pm/prom_ex/PromEx.Plugins.Ecto.html) - [Oban](https://hexdocs.pm/prom_ex/PromEx.Plugins.Oban.html) We recommend checking those pages to know which metrics are available and how to use them. ## Runs 메트릭 {#runs-metrics} Tuist run과 관련된 메트릭 모음입니다. ### `tuist_runs_total` (카운터) {#tuist_runs_total-counter} Tuist Run의 총 실행 횟수. #### Tags {#tuist-runs-total-tags} | Tag | Description | | -------- | ---------------------------------------------------------- | | `name` | `build`, `test` 등과 같이 실행된 `tuist` 명령어의 이름. | | `is_ci` | CI 또는 개발자의 머신에서 실행되었는 지를 나타내는 불리언 값. | | `status` | `성공` 시 `0`, `실패` 시 `1` | ### `tuist_runs_duration_milliseconds` (히스토그램) {#tuist_runs_duration_milliseconds-histogram} 각 tuist run의 총 소요 시간(milliseconds). #### Tags {#tuist-runs-duration-miliseconds-tags} | Tag | Description | | -------- | ---------------------------------------------------------- | | `name` | `build`, `test` 등과 같이 실행된 `tuist` 명령어의 이름. | | `is_ci` | CI 또는 개발자의 머신에서 실행되었는 지를 나타내는 불리언 값. | | `status` | `성공` 시 `0`, `실패` 시 `1` | ## Cache 메트릭 {#cache-metrics} Tuist Cache와 관련된 메트릭 모음입니다. ### `tuist_cache_events_total` (카운터) {#tuist_cache_events_total-counter} 바이너리 캐시 이벤트의 총 개수. #### Tags {#tuist-cache-events-total-tags} | Tag | Description | | ------------ | -------------------------------------- | | `event_type` | `local_hit`, `remote_hit`, `miss` 중 하나 | ### `tuist_cache_uploads_total` (카운터) {#tuist_cache_uploads_total-counter} 바이너리 캐시 업로드 개수. ### `tuist_cache_uploaded_bytes` (합) {#tuist_cache_uploaded_bytes-sum} 바이너리 캐시에 업로드된 바이트 수. ### `tuist_cache_downloads_total` (카운터) {#tuist_cache_downloads_total-counter} 바이너리 캐시에 다운로드 수. ### `tuist_cache_downloaded_bytes` (합) {#tuist_cache_downloaded_bytes-sum} 바이너리 캐시로 부터 다운로드된 바이트 수. --- ## Preview 메트릭 {#previews-metrics} 프리뷰 기능과 관련된 메트릭 모음입니다. ### `tuist_previews_uploads_total` (합) {#tuist_previews_uploads_total-counter} 업로드된 프리뷰의 수. ### `tuist_previews_downloads_total` (합) {#tuist_previews_downloads_total-counter} 다운로드된 프리뷰의 수. --- ## Storage 메트릭 {#storage-metrics} remote storage(예: s3)에 아티팩트를 저장하는 것과 관련된 메트릭 모음. > [!TIP] > 이 메트릭은 storage의 작업 성능을 이해하고 잠재적인 병목 현상을 식별하는데 유용합니다. ### `tuist_storage_get_object_size_size_bytes` (히스토그램) {#tuist_storage_get_object_size_size_bytes-histogram} remote storage에서 가져온 object의 크기(byte) #### Tags {#tuist-storage-get-object-size-size-bytes-tags} | Tag | Description | | ------------ | ----------------------------- | | `object_key` | remote storage에서 object의 조회 키 | ### `tuist_storage_get_object_size_duration_miliseconds` (히스토그램) {#tuist_storage_get_object_size_duration_miliseconds-histogram} remote storage에서 object의 크기를 가져오는 데 소요된 시간(milliseconds) #### Tags {#tuist-storage-get-object-size-duration-miliseconds-tags} | Tag | Description | | ------------ | ----------------------------- | | `object_key` | remote storage에서 object의 조회 키 | ### `tuist_storage_get_object_size_count` (카운터) {#tuist_storage_get_object_size_count-counter} remote storage에서 object 크기를 가져온 횟수. #### Tags {#tuist-storage-get-object-size-count-tags} | Tag | Description | | ------------ | ----------------------------- | | `object_key` | remote storage에서 object의 조회 키 | ### `tuist_storage_delete_all_objects_duration_milliseconds` (히스토그램) {#tuist_storage_delete_all_objects_duration_milliseconds-histogram} remote storage에서 모든 object를 삭제하는 데 소요된 시간(milliseconds) #### Tags {#tuist-storage-delete-all-objects-duration-milliseconds-tags} | Tag | Description | | -------------- | ---------------------------------------------------- | | `project_slug` | object가 삭제되는 프로젝트의 프로젝트 슬러그(slug) | ### `tuist_storage_delete_all_objects_count` (카운터) {#tuist_storage_delete_all_objects_count-counter} remote storage에서 프로젝트의 모든 object가 삭제된 횟수 #### Tags {#tuist-storage-delete-all-objects-count-tags} | Tag | Description | | -------------- | ---------------------------------------------------- | | `project_slug` | object가 삭제되는 프로젝트의 프로젝트 슬러그(slug) | ### `tuist_storage_multipart_start_upload_duration_milliseconds` (히스토그램) {#tuist_storage_multipart_start_upload_duration_milliseconds-histogram} remote storage로 업로드를 시작하는 데 소요된 시간(milliseconds) #### Tags {#tuist-storage-multipart-start-upload-duration-milliseconds-tags} | Tag | Description | | ------------ | ----------------------------- | | `object_key` | remote storage에서 object의 조회 키 | ### `tuist_storage_multipart_start_upload_duration_count` (카운터) {#tuist_storage_multipart_start_upload_duration_count-counter} remote storage로 업로드가 시작된 횟수 #### Tags {#tuist-storage-multipart-start-upload-duration-count-tags} | Tag | Description | | ------------ | ----------------------------- | | `object_key` | remote storage에서 object의 조회 키 | ### `tuist_storage_get_object_as_string_duration_milliseconds` (히스토그램) {#tuist_storage_get_object_as_string_duration_milliseconds-histogram} remote storage에서 object를 문자열로 가져오는 데 소요된 시간(milliseconds) #### Tags {#tuist-storage-get-object-as-string-duration-milliseconds-tags} | Tag | Description | | ------------ | ----------------------------- | | `object_key` | remote storage에서 object의 조회 키 | ### `tuist_storage_get_object_as_string_count` (횟수) {#tuist_storage_get_object_as_string_count-count} remote storage에서 객체를 문자열로 가져온 횟수 #### Tags {#tuist-storage-get-object-as-string-count-tags} | Tag | Description | | ------------ | ----------------------------- | | `object_key` | remote storage에서 object의 조회 키 | ### `tuist_storage_check_object_existence_duration_milliseconds` (히스토그램) {#tuist_storage_check_object_existence_duration_milliseconds-histogram} remote storage에서 object의 존재 여부를 확인하는 데 소요된 시간(milliseconds) #### Tags {#tuist-storage-check-object-existence-duration-milliseconds-tags} | Tag | Description | | ------------ | ----------------------------- | | `object_key` | remote storage에서 object의 조회 키 | ### `tuist_storage_check_object_existence_count` (횟수) {#tuist_storage_check_object_existence_count-count} remote storage에서 object의 존재 여부를 확인한 횟수 #### Tags {#tuist-storage-check-object-existence-count-tags} | Tag | Description | | ------------ | ----------------------------- | | `object_key` | remote storage에서 object의 조회 키 | ### `tuist_storage_generate_download_presigned_url_duration_milliseconds` (히스토그램) {#tuist_storage_generate_download_presigned_url_duration_milliseconds-histogram} remote storage에서 object의 download presigned URL을 생성하는 데 소요된 시간(milliseconds) #### Tags {#tuist-storage-generate-download-presigned-url-duration-milliseconds-tags} | Tag | Description | | ------------ | ----------------------------- | | `object_key` | remote storage에서 object의 조회 키 | ### `tuist_storage_generate_download_presigned_url_count` (횟수) {#tuist_storage_generate_download_presigned_url_count-count} remote storage에서 object의 download presigned URL이 생성된 횟수 #### Tags {#tuist-storage-generate-download-presigned-url-count-tags} | Tag | Description | | ------------ | ----------------------------- | | `object_key` | remote storage에서 object의 조회 키 | ### `tuist_storage_multipart_generate_upload_part_presigned_url_duration_milliseconds` (히스토그램) {#tuist_storage_multipart_generate_upload_part_presigned_url_duration_milliseconds-histogram} remote storage에서 object의 part upload presigned URL을 생성하는 데 소요된 시간(milliseconds) #### Tags {#tuist-storage-multipart-generate-upload-part-presigned-url-duration-milliseconds-tags} | Tag | Description | | ------------- | ----------------------------- | | `object_key` | remote storage에서 object의 조회 키 | | `part_number` | 업로드 중인 object의 part number | | `upload_id` | multipart upload의 upload ID | ### `tuist_storage_multipart_generate_upload_part_presigned_url_count` (횟수) {#tuist_storage_multipart_generate_upload_part_presigned_url_count-count} remote storage에서 object의 part upload presigned URL이 생성된 횟수 #### Tags {#tuist-storage-multipart-generate-upload-part-presigned-url-count-tags} | Tag | Description | | ------------- | ----------------------------- | | `object_key` | remote storage에서 object의 조회 키 | | `part_number` | 업로드 중인 object의 part number | | `upload_id` | multipart upload의 upload ID | ### `tuist_storage_multipart_complete_upload_duration_milliseconds` (히스토그램) {#tuist_storage_multipart_complete_upload_duration_milliseconds-histogram} remote storage로 업로드를 완료하는 데 소요된 시간(milliseconds) #### Tags {#tuist-storage-multipart-complete-upload-duration-milliseconds-tags} | Tag | Description | | ------------ | ----------------------------- | | `object_key` | remote storage에서 object의 조회 키 | | `upload_id` | multipart upload의 upload ID | ### `tuist_storage_multipart_complete_upload_count` (횟수) {#tuist_storage_multipart_complete_upload_count-count} remote storage로 업로드가 완료된 총 횟수. #### Tags {#tuist-storage-multipart-complete-upload-count-tags} | Tag | Description | | ------------ | ----------------------------- | | `object_key` | remote storage에서 object의 조회 키 | | `upload_id` | multipart upload의 upload ID | --- ## 프로젝트 메트릭 {#projects-metrics} 프로젝트와 관련된 메트릭 모음입니다. ### `tuist_projects_total` (last_value) {#tuist_projects_total-last_value} 프로젝트의 수. --- ## 계정 메트릭 {#accounts-metrics} 계정 (사용자와 조직) 과 관련된 메트릭 모음입니다. ### `tuist_accounts_organizations_total` (last_value) {#tuist_accounts_organizations_total-last_value} 조직의 총 수. ### `tuist_accounts_users_total` (last_value) {#tuist_accounts_users_total-last_value} 사용자의 총 수. ## 데이터베이스 메트릭 {#database-metrics} 데이터베이스 연결과 관련된 메트릭입니다. ### `tuist_repo_pool_checkout_queue_length` (last_value) {#tuist_repo_pool_checkout_queue_length-last_value} 데이터베이스 연결에 할당되기를 기다리며 큐에 대기 중인 데이터베이스 쿼리 수입니다. ### `tuist_repo_pool_ready_conn_count` (last_value) {#tuist_repo_pool_ready_conn_count-last_value} 데이터베이스 쿼리에 할당될 준비가 된 데이터베이스 연결 수입니다. ### `tuist_repo_pool_db_connection_connected` (counter) {#tuist_repo_pool_db_connection_connected-counter} 데이터베이스에 설정된 연결 수입니다. ### `tuist_repo_pool_db_connection_disconnected` (counter) {#tuist_repo_pool_db_connection_disconnected-counter} 데이터베이스에서 해제된 연결 수입니다. ## HTTP 메트릭 {#http-metrics} Tuist가 다른 서비스와 HTTP를 통해 상호작용할 때 관련된 메트릭의 집합 ### `tuist_http_request_count` (counter) {#tuist_http_request_count-last_value} HTTP 요청 수 ### `tuist_http_request_duration_nanosecond_sum` (sum) {#tuist_http_request_duration_nanosecond_sum-last_value} 요청에 대한 전체 시간 합계(연결에 할당되기까지 대기한 시간 포함) ### `tuist_http_request_duration_nanosecond_bucket` (distribution) {#tuist_http_request_duration_nanosecond_bucket-distribution} 요청에 대한 지속 시간 분포(연결에 할당되기까지 대기한 시간 포함) ### `tuist_http_queue_count` (counter) {#tuist_http_queue_count-counter} 풀에서 가져온 요청 수 ### `tuist_http_queue_duration_nanoseconds_sum` (sum) {#tuist_http_queue_duration_nanoseconds_sum-sum} 풀에서 연결을 가져오는데 걸리는 시간 ### `tuist_http_queue_idle_time_nanoseconds_sum` (sum) {#tuist_http_queue_idle_time_nanoseconds_sum-sum} 풀에서 연결을 가져올 때 유휴 상태로 있던 시간 ### `tuist_http_queue_duration_nanoseconds_bucket` (distribution) {#tuist_http_queue_duration_nanoseconds_bucket-distribution} 풀에서 연결을 가져오는데 걸리는 시간 ### `tuist_http_queue_idle_time_nanoseconds_bucket` (distribution) {#tuist_http_queue_idle_time_nanoseconds_bucket-distribution} 풀에서 연결을 가져올 때 유휴 상태로 있던 시간 ### `tuist_http_connection_count` (counter) {#tuist_http_connection_count-counter} 설정된 연결 수 ### `tuist_http_connection_duration_nanoseconds_sum` (sum) {#tuist_http_connection_duration_nanoseconds_sum-sum} 호스트와의 연결을 설정하는데 걸리는 시간 ### `tuist_http_connection_duration_nanoseconds_bucket` (distribution) {#tuist_http_connection_duration_nanoseconds_bucket-distribution} 호스트와의 연결을 설정하는데 걸리는 시간 분포 ### `tuist_http_send_count` (counter) {#tuist_http_send_count-counter} 풀에서 연결이 할당된 후에 전송된 요청 수 ### `tuist_http_send_duration_nanoseconds_sum` (sum) {#tuist_http_send_duration_nanoseconds_sum-sum} 풀에서 연결이 할당된 후에 요청이 완료되기까지의 시간 ### `tuist_http_send_duration_nanoseconds_bucket` (distribution) {#tuist_http_send_duration_nanoseconds_bucket-distribution} 풀에서 연결이 할당된 후에 요청이 완료되기까지의 시간 분포 ### `tuist_http_receive_count` (counter) {#tuist_http_receive_count-counter} 전송된 요청으로부터 수신된 응답 수 ### `tuist_http_receive_duration_nanoseconds_sum` (sum) {#tuist_http_receive_duration_nanoseconds_sum-sum} 응답을 수신하는데 걸리는 시간 ### `tuist_http_receive_duration_nanoseconds_bucket` (distribution) {#tuist_http_receive_duration_nanoseconds_bucket-distribution} 응답을 수신하는데 걸리는 시간 분포 ### `tuist_http_queue_available_connections` (last_value) {#tuist_http_queue_available_connections-last_value} 큐에서 사용 가능한 연결 수 ### `tuist_http_queue_in_use_connections` (last_value) {#tuist_http_queue_in_use_connections-last_value} 큐에서 사용 중인 연결 수 --- URL: "/ko/server/on-premise/install" LLMS_URL: "/ko/server/on-premise/install.md" title: "Installation" titleTemplate: ":title | On-premise | Server | Tuist" description: "Tuist를 인프라에 설치하는 방법을 배워봅니다." --- # On-premise installation {#onpremise-installation} 인프라에 대한 더 많은 제어를 요구하는 조직을 위해 Tuist 서버의 자체 호스팅 버전을 제공합니다. 이 버전은 Tuist를 자체 인프라에 호스팅하여 사용자의 데이터가 안전하고 비공개로 유지되도록 보장합니다. > [!IMPORTANT] 기업 고객 전용\ > Tuist의 On-Premise 버전은 Enterprise 플랜을 가입한 조직만 사용 가능합니다. On-Premise 버전에 관심이 있다면, [contact@tuist.dev](mailto:contact@tuist.dev)로 연락 바랍니다. ## 출시 주기 {#release-cadence} Tuist 서버는 **매주 월요일에 출시되며** 버전 이름은 `{MAJOR}.YY.MM.DD` 형식을 따릅니다. 날짜 구성 요소는 호스팅된 버전이 CLI의 출시일로부터 60일 이상 지난 경우, CLI 사용자에게 경고를 보내기 위해 사용됩니다. On-premise 조직은 개발자가 최신 개선 사항의 이점을 누릴 수 있도록 Tuist를 최신으로 유지하는 것이 중요하며, On-premise 설정을 손상시키지 않으면서 더이상 사용하지 않는 기능을 제거할 수 있습니다. CLI의 주요 구성 요소는 On-premise 사용자와의 조정이 필요한 Tuist 서버에서의 주요 변경 사항을 표시하는데 사용됩니다. 우리가 이것을 사용할 것이라고 기대해서는 안되며 필요한 경우, 전환이 원활하도록 함께 협력할 것입니다. > [!NOTE] 릴리즈 노트\ > 이미지가 게시되는 레지스트리와 연결된 `tuist/registry` 리포지토리에 연결 권한이 부여됩니다. 모든 릴리즈는 해당 리포지토리의 GitHub 릴리즈에 게시되고 변경 사항은 릴리즈 노트에 포함됩니다. ## 실행 환경 요구 사항 {#runtime-requirements} 이 섹션은 Tuist 서버를 인프라에 호스팅하기 위한 요구 사항을 설명합니다. ### Docker 가상화 이미지 실행 {#running-dockervirtualized-images} 우리는 서버를 [GitHub의 Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry)를 통해 [Docker](https://www.docker.com/)로 배포합니다. 실행하기 위해 인프라에서는 Docker 이미지 실행을 지원해야 합니다. 대부분의 인프라는 이것을 지원하는데 이는 운영 환경에서 소프트웨어를 배포하고 실행하는 표준 컨테이너로 자리 잡았기 때문입니다. ### Postgres 데이터베이스 {#postgres-database} Docker 이미지를 실행하는 것 외에도, 관계형 데이터를 저장하기 위한 [Postgres 데이터베이스](https://www.postgresql.org/)도 필요합니다. 대부분의 인프라는 Postgres 데이터베이스를 포함하여 제공하고 있습니다 (예: [AWS](https://aws.amazon.com/rds/postgresql/) & [Google Cloud](https://cloud.google.com/sql/docs/postgres)). 뛰어난 성능 측정을 위해 우리는 [Timescale Postgres 확장](https://www.timescale.com/)을 사용합니다. Postgres 데이터베이스가 실행되는 머신에 TimescaleDB가 설치되어 있는지 확인해야 합니다. 자세한 설치 방법은 [여기](https://docs.timescale.com/self-hosted/latest/install/)에서 확인할 수 있습니다. Timescale 확장을 설치할 수 없는 경우, Prometheus 메트릭을 사용하여 자체 대시보드를 설정할 수 있습니다. > [!INFO] 마이그레이션\ > Docker 이미지의 엔트리포인트는 컨테이너가 실행되기 전에 자동으로 대기 중인 스킴 마이그레이션을 실행합니다. ### 저장소 {#storage} 파일 (예: 프레임워크 및 라이브러리 바이너리) 을 저장하기 위한 솔루션도 필요합니다. 현재 S3 호환 저장소를 모두 지원합니다. ## 구성 {#configuration} 서비스의 구성은 실행 시 환경 변수를 통해 이루어집니다. 환경 변수는 민감한 정보이므로, 이를 암호화하여 안전한 비밀번호 관리 솔루션에 저장하길 권장합니다. Tuist는 이러한 변수를 최대한 신중하게 처리하고 로그에 절대로 표시되지 않도록 보장하므로 안심할 수 있습니다. > [!NOTE] 시작 검증\ > 필요한 변수는 시작할 때 검증됩니다. 필요한 변수가 누락되면 실행이 실패하고 오류 메세지에 누락된 변수가 상세히 표시됩니다. ### 라이센스 구성 {#license-configuration} On-premise 사용자는 환경 변수로 설정해야 하는 라이센스 키를 받습니다. 이 키는 라이센스를 검증하고 서비스가 계약 조건 내에서 실행되고 있음을 보장합니다. | 환경 변수 | 설명 | 필수 여부 | 기본값 | 예시 | | --------------- | ---------------------------------------------------- | ----- | --- | -------- | | `TUIST_LICENSE` | 서비스 수준 계약 (SLA) 을 체결한 후 제공되는 라이센스 | Yes | | `******` | > [!IMPORTANT] 만료일\ > 라이센스는 만료일이 있습니다. 사용자가 서버와 상호 작용하는 Tuist 명령어를 사용할 때, 라이센스가 30일 이내에 만료가 된다면 경고가 표시됩니다. 라이센스를 갱신하고 싶다면, [contact@tuist.dev](mailto:contact@tuist.dev)로 연락 바랍니다. ### 기본 환경 구성 {#base-environment-configuration} | 환경 변수 | 설명 | 필수 여부 | 기본값 | 예시 | | | ------------------------------ | --------------------------------------------------------------------- | ----- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | | `TUIST_APP_URL` | 인터넷에서 인스턴스에 접근하기 위한 기본 URL | Yes | | https://cloud.tuist.io | | | `TUIST_SECRET_KEY_BASE` | 정보를 암호화 하는데 사용되는 키 (예: 쿠키에 저장된 세션) | Yes | | | `c5786d9f869239cbddeca645575349a570ffebb332b64400c37256e1c9cb7ec831345d03dc0188edd129d09580d8cbf3ceaf17768e2048c037d9c31da5dcacfa` | | `TUIST_SECRET_KEY_PASSWORD` | 해시된 비밀번호를 생성하기 위한 페퍼 (Pepper) | No | $TUIST_SECRET_KEY_BASE | | | | `TUIST_SECRET_KEY_TOKENS` | 랜덤 토큰을 생성하기 위한 비밀 키 | No | $TUIST_SECRET_KEY_BASE | | | | `TUIST_USE_IPV6` | `1`로 설정하면 IPv6 주소를 사용해 앱을 구성 | No | `0` | `1` | | | `TUIST_LOG_LEVEL` | 앱에 사용할 로그 수준 | No | `info` | [Log levels](https://hexdocs.pm/logger/1.12.3/Logger.html#module-levels) | | | `TUIST_GITHUB_APP_PRIVATE_KEY` | 자동 PR 코멘트 게시와 같은 추가 기능을 활성화하기 위해 GitHub 앱에 사용되는 비공개 키 | No | `-----BEGIN RSA...` | | | | `TUIST_OPS_USER_HANDLES` | 작업 URL에 접근할 수 있는 쉼표로 구분된 사용자 아이디 목록 | No | | `user1,user2` | | ### 데이터베이스 구성 {#database-configuration} 다음의 환경 변수는 데이터베이스 연결을 구성하기 위해 사용됩니다: | 환경 변수 | 설명 | 필수 여부 | 기본값 | 예시 | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | ------ | ---------------------------------------------------------------------- | | `DATABASE_URL` | Postgres 데이터베이스 접근을 위한 URL 입니다. URL에는 인증 정보가 포함되어야 합니다. | Yes | | `postgres://username:password@cloud.us-east-2.aws.test.com/production` | | `TUIST_USE_SSL_FOR_DATABASE` | true 이면 데이터베이스에 접속하기 위해 [SSL](https://en.wikipedia.org/wiki/Transport_Layer_Security)을 사용 | No | `1` | `1` | | `TUIST_DATABASE_POOL_SIZE` | 연결 풀에서 유지할 연결 수 | No | `10` | `10` | | `TUIST_DATABASE_QUEUE_TARGET` | 풀에서 체크아웃된 모든 연결이 큐 대기 시간보다 더 오래 걸렸는지 확인하는 범위 (밀리초 단위) [(자세한 정보)](https://hexdocs.pm/db_connection/DBConnection.html#start_link/2-queue-config) | No | `300` | `300` | | `TUIST_DATABASE_QUEUE_INTERVAL` | 풀에서 새로운 연결을 끊기위해 필요한 큐에서의 임계 시간 (밀리초 단위) [(자세한 정보)](https://hexdocs.pm/db_connection/DBConnection.html#start_link/2-queue-config) | No | `1000` | `1000` | ### 인증 환경 구성 {#authentication-environment-configuration} 우리는 [아이덴티티 제공자 (IdP)](https://en.wikipedia.org/wiki/Identity_provider) 를 통해 인증을 지원합니다. 이를 활용하려면, 선택한 제공자에 필요한 환경 변수가 서버의 환경에 설정되어 있는지 확인해야 합니다. **누락된 변수**가 있으면 Tuist는 해당 제공자를 건너뜁니다. #### GitHub {#github} 우리는 [GitHub App](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps)을 사용하여 인증하는 것을 권장하지만 [OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)을 사용할 수도 있습니다. GitHub에서 지정한 필수 환경 변수를 모두 서버 환경에 포함시켜야 합니다. 변수가 없으면 Tuist는 GitHub 인증을 무시합니다. GitHub 앱을 올바르게 설정하려면 다음과 같습니다: - GitHub 앱의 일반 설정: - `Client ID`를 복사하고 `TUIST_GITHUB_APP_CLIENT_ID`로 설정합니다. - 새로운 `client secret`을 생성하고 복사한 다음에 `TUIST_GITHUB_APP_CLIENT_SECRET`로 설정합니다. - `Callback URL`을 `http://YOUR_APP_URL/users/auth/github/callback`으로 설정합니다. `YOUR_APP_URL`은 서버의 IP 주소도 사용할 수 있습니다. - `Permissions and events`의 `Account permissions` 섹션에서 `Email addresses` 권한을 `Read-only`로 설정합니다. 그런 다음, Tuist 서버가 실행되는 환경에서 다음 환경 변수를 노출시킵니다: | 환경 변수 | 설명 | 필수 여부 | 기본값 | 예시 | | -------------------------------- | ----------------------- | ----- | --- | ------------------------------------------ | | `TUIST_GITHUB_APP_CLIENT_ID` | GitHub 애플리케이션의 클라이언트 ID | Yes | | `Iv1.a629723000043722` | | `TUIST_GITHUB_APP_CLIENT_SECRET` | 애플리케이션의 클라이언트 비밀키 | Yes | | `232f972951033b89799b0fd24566a04d83f44ccc` | #### Google {#google} [OAuth 2](https://developers.google.com/identity/protocols/oauth2)를 사용하여 Google 인증을 설정할 수 있습니다. 이를 위해, OAuth 클라이언트 ID 타입의 새로운 자격 증명을 생성해야 합니다. 자격 증명을 생성할 때, 애플리케이션 타입으로 "Web Application"을 선택하고, 이름을 `Tuist`로 설정하고, 리다이렉트 URI를 호스팅되는 서비스가 실행되는 `base_url`을 활용하여 `{base_url}/users/auth/google/callback`으로 설정합니다. 앱을 생성한 후에 클라이언트 ID와 클라이언트 비밀키를 복사하고 각각 환경 변수 `GOOGLE_CLIENT_ID`와 `GOOGLE_CLIENT_SECRET`로 설정합니다. > [!NOTE] 동의 화면 범위\ > 동의 화면을 생성해야 할 수도 있습니다. 그렇게 할 때, `userinfo.email`과 `openid` 범위를 추가하고 내부 앱으로 표시해야 합니다. #### Okta {#okta} [OAuth 2.0](https://oauth.net/2/) 프로토콜을 통해 Okta 인증을 활성화할 수 있습니다. 다음 구성으로 Okta에서 [앱을 생성](https://developer.okta.com/docs/en/guides/implement-oauth-for-okta/main/#create-an-oauth-2-0-app-in-okta)해야 합니다: - **앱 통합 이름:** `Tuist` - **승인 타입:** 사용자를 대신하여 행동하는 Client에 대해 _Authorization Code_ 활성화 - **로그인 리다이렉트 URL:** 서비스에 접근하는 공개 URL을 `url`라고 하면 `{url}/users/auth/okta/callback` - **할당:** 이 구성은 보안팀 요구 사항에 따라 달라집니다. 앱이 생성되면, 다음의 환경 변수를 설정해야 합니다: | 환경 변수 | 설명 | 필수 여부 | 기본값 | 예시 | | -------------------------- | --------------------- | ----- | --- | --------------------------- | | `TUIST_OKTA_SITE` | Okta 조직의 URL | Yes | | `https://your-org.okta.com` | | `TUIST_OKTA_CLIENT_ID` | Okta 인증을 위한 클라이언트 ID | Yes | | | | `TUIST_OKTA_CLIENT_SECRET` | Okta 인증을 위한 클라이언트 비밀키 | Yes | | | ### 저장소 환경 구성 {#storage-environment-configuration} Tuist는 API를 통해 업로드된 산출물을 저장하기 위한 저장소가 필요합니다. Tuist가 원할하게 동작하려면 **지원되는 저장소 솔루션 중 하나를 구성하는 것이 필수입니다**. #### S3 호환 저장소 {#s3compliant-storages} 산출물을 저장하기 위해 S3 호환 저장소 제공자를 사용할 수 있습니다. 저장소 제공자와의 인증과 통합 구성을 위해 다음의 환경 변수가 필요합니다: | 환경 변수 | 설명 | 필수 여부 | 기본값 | 예시 | | ---------------------------------------------------- | ----------------------------------------------------------------------------- | ----- | ------- | ------------------------------------------ | | `TUIST_ACCESS_KEY_ID` 또는 `AWS_ACCESS_KEY_ID` | 저장소 제공자 인증을 위한 접근 키 ID | Yes | | `AKIAIOSFOD` | | `TUIST_SECRET_ACCESS_KEY` 또는 `AWS_SECRET_ACCESS_KEY` | 저장소 제공자 인증을 위한 비밀 접근 키 | Yes | | `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` | | `TUIST_S3_REGION` 또는 `AWS_REGION` | 버킷이 위치한 지역 | Yes | | `us-west-2` | | `TUIST_S3_ENDPOINT` 또는 `AWS_ENDPOINT` | 저장소 제공자의 엔드포인트 | Yes | | `https://s3.us-west-2.amazonaws.com` | | `TUIST_S3_BUCKET_NAME` | 산출물이 저장될 버킷의 이름 | Yes | | `tuist-artifacts` | | `TUIST_S3_REQUEST_TIMEOUT` | 저장소 제공자 요청에 대한 타임아웃 (초 단위) | No | `30` | `30` | | `TUIST_S3_POOL_TIMEOUT` | 저장소 제공자에 대한 연결 풀의 타임아웃 (초 단위) | No | `5` | `5` | | `TUIST_S3_POOL_COUNT` | 저장소 제공자와의 연결 풀의 수 | No | `1` | `1` | | `TUIST_S3_PROTOCOL` | 저장소 제공자와 연결할 때 사용하는 프로토콜 (`http1` 또는 `http2`) | No | `http2` | `http2` | | `TUIST_S3_VIRTUAL_HOST` | URL이 서브도메인 (가상 호스트) 으로 버킷 이름을 구성해야 하는지 여부. | No | No | `1` | > [!NOTE] 환경 변수에서 Web Identity Token을 사용한 AWS 인증\ > 저장소 제공자가 AWS이고 웹 아이덴티티 토큰을 사용하여 인증하려는 경우에 환경 변수 `TUIST_S3_AUTHENTICATION_METHOD`를 `aws_web_identity_token_from_env_vars`로 설정할 수 있습니다. 그러면 Tuist는 기존의 AWS 환경 변수를 사용하여 인증을 진행할 수 있습니다. #### Google Cloud Storage {#google-cloud-storage} Google Cloud Storage의 경우, [이 문서](https://cloud.google.com/storage/docs/authentication/managing-hmackeys)를 참고하여 `AWS_ACCESS_KEY_ID`와 `AWS_SECRET_ACCESS_KEY` 쌍을 얻어야 합니다. `AWS_ENDPOINT`는 `https://storage.googleapis.com`으로 설정해야 합니다. 다른 환경 변수는 모든 S3 호환 저장소와 동일합니다. ### Git 플랫폼 구성 {#git-platform-configuration} Tuist는 Pull Request에 자동으로 댓글을 게시하는 등의 추가 기능을 제공하기 위해 Git 플랫폼과 통합할 수 있습니다. #### GitHub {#platform-github} [GitHub 앱을 생성](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps)해야 합니다. 인증을 위해 생성한 GitHub 앱을 재사용할 수 있지만, OAuth GitHub 앱을 생성한 경우는 제외입니다. `Permissions and events`의 `Repository permissions` 섹션에서 `Pull requests` 권한을 `Read and write`로 설정해야 합니다. `TUIST_GITHUB_APP_CLIENT_ID`와 `TUIST_GITHUB_APP_CLIENT_SECRET` 외에도 다음의 환경 변수가 필요합니다: | 환경 변수 | 설명 | 필수 여부 | 기본값 | 예시 | | ------------------------------ | -------------------- | ----- | --- | ------------------------------------ | | `TUIST_GITHUB_APP_PRIVATE_KEY` | GitHub 애플리케이션의 비공개 키 | Yes | | `-----BEGIN RSA PRIVATE KEY-----...` | ## 배포 {#deployment} On-premise 사용자는 이미지를 가져올 수 있는 컨테이너 레지스트리를 가지는 [tuist/registry](https://github.com/cloud/registry)에 위치한 리포지토리에 대한 접근 권한을 부여 받습니다. 현재 컨테이너 레지스트리는 개인 사용자에게만 인증을 허용합니다. 따라서 리포지토리 접근 권한이 있는 사용자는 Tuist 조직 내에서 **개인 접근 토큰**을 생성해야 하고 패키지를 읽을 수 있는 권한이 있는지 확인해야 합니다. 제출 하면 우리는 이 토큰을 빠르게 승인할 것입니다. > [!IMPORTANT] 사용자 토큰 VS 조직 범위 토큰\ > 개인이 기업 조직을 떠난 경우 개인 접근 토큰은 개인과 연결되어 있으므로, 개인 접근 토큰을 사용하는 것이 문제가 될 수 있습니다. GitHub은 이런 문제를 인지하고 있으며, GitHub 앱에서 생성한 토큰을 사용하여 인증할 수 있는 해결책을 적극적으로 개발 중입니다. ### Docker 이미지 가져오기 {#pulling-the-docker-image} 토큰을 생성한 후에 다음 명령어를 실행하여 이미지를 가져올 수 있습니다: ```bash echo $TOKEN | docker login ghcr.io -u USERNAME --password-stdin docker pull ghcr.io/tuist/tuist:latest ``` ### Docker 이미지 배포 {#deploying-the-docker-image} Docker 이미지 배포 과정은 선택한 클라우드 제공 업체와 조직의 지속적인 배포 방식에 따라 달라집니다. [Kubernetes](https://kubernetes.io/)와 같은 대부분의 클라우드 솔루션 및 툴은 Docker 이미지를 기본 단위로 사용하므로, 이 섹션의 예시는 기존 설정과 잘 맞습니다. 우리는 **매주 화요일**에 새로운 이미지를 가져와 배포하는 파이프라인을 구축하는 것을 권장합니다. 이렇게 하면 최신 개선 사항을 계속해서 활용할 수 있습니다. > [!IMPORTANT] > 배포 파이프라인에서 서버가 정상적으로 동작하는지 확인해야 하는 경우에 `/ready`에 `GET` HTTP 요청을 보내고 응답에서 `200` 코드를 확인할 수 있습니다. #### Fly {#fly} 앱을 [Fly](https://fly.io/)에 배포하기 위해 `fly.toml` 구성 파일이 필요합니다. Continuous Deployment (CD) 파이프라인 내에서 이 구성 파일을 동적으로 생성하는 것을 고려해야 합니다. 아래는 참고용 예시입니다: ```toml app = "tuist" primary_region = "fra" kill_signal = "SIGINT" kill_timeout = "5s" [experimental] auto_rollback = true [env] # Your environment configuration goes here # Or exposed through Fly secrets [processes] app = "/usr/local/bin/hivemind /app/Procfile" [[services]] protocol = "tcp" internal_port = 8080 auto_stop_machines = false auto_start_machines = false processes = ["app"] http_options = { h2_backend = true } [[services.ports]] port = 80 handlers = ["http"] force_https = true [[services.ports]] port = 443 handlers = ["tls", "http"] [services.concurrency] type = "connections" hard_limit = 100 soft_limit = 80 [[services.http_checks]] interval = 10000 grace_period = "10s" method = "get" path = "/ready" protocol = "http" timeout = 2000 tls_skip_verify = false [services.http_checks.headers] [[statics]] guest_path = "/app/public" url_prefix = "/" ``` 이러면 앱을 실행하기 위해 `fly launch --local-only --no-deploy`을 수행할 수 있습니다. 이후 배포에서는 `fly launch --local-only`를 수행하는 대신에 `fly deploy --local-only`를 수행합니다. Fly.io에서는 비공개 Docker 이미지를 가져올 수 없으므로, `--local-only` 플래그를 사용해야 합니다. ### Docker Compose {#docker-compose} 아래는 서비스를 배포하기 위해 참고할 수 있는 `docker-compose.yml` 파일의 예시입니다: ```yaml version: '3.8' services: db: image: timescale/timescaledb-ha:pg16 restart: always environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - PGDATA=/var/lib/postgresql/data/pgdata ports: - '5432:5432' volumes: - db:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 pgweb: container_name: pgweb restart: always image: sosedoff/pgweb ports: - "8081:8081" links: - db:db environment: PGWEB_DATABASE_URL: postgres://postgres:postgres@db:5432/postgres?sslmode=disable depends_on: - db tuist: image: ghcr.io/tuist/tuist:latest container_name: tuist depends_on: - db ports: - "80:80" - "8080:8080" - "443:443" expose: - "80" - "8080" - "443:443" environment: # Base Tuist Env - https://docs.tuist.io/en/guides/dashboard/on-premise/install#base-environment-configuration TUIST_USE_SSL_FOR_DATABASE: "0" TUIST_LICENSE: # ... DATABASE_URL: postgres://postgres:postgres@db:5432/postgres?sslmode=disable TUIST_APP_URL: https://localhost:8080 TUIST_SECRET_KEY_BASE: # ... WEB_CONCURRENCY: 80 # Auth - one method # GitHub Auth - https://docs.tuist.io/en/guides/dashboard/on-premise/install#github TUIST_GITHUB_OAUTH_ID: TUIST_GITHUB_APP_CLIENT_SECRET: # Okta Auth - https://docs.tuist.io/en/guides/dashboard/on-premise/install#okta TUIST_OKTA_SITE: TUIST_OKTA_CLIENT_ID: TUIST_OKTA_CLIENT_SECRET: TUIST_OKTA_AUTHORIZE_URL: # Optional TUIST_OKTA_TOKEN_URL: # Optional TUIST_OKTA_USER_INFO_URL: # Optional TUIST_OKTA_EVENT_HOOK_SECRET: # Optional # Storage AWS_ACCESS_KEY_ID: # ... AWS_SECRET_ACCESS_KEY: # ... AWS_S3_REGION: # ... AWS_ENDPOINT: # https://amazonaws.com TUIST_S3_BUCKET_NAME: # ... # Other volumes: db: driver: local ``` ## Operations {#operations} Tuist는 인스턴스를 관리하기 위해 사용할 수 있는 유틸리티를 `/ops/`에서 제공합니다. > [!IMPORTANT] 인증\ > `TUIST_OPS_USER_HANDLES` 환경 변수에 작성된 사용자만 `/ops/` 엔드포인트에 접근할 수 있습니다. - **오류 (`/ops/errors`):** 애플리케이션에서 발생한 오류를 볼 수 있습니다. 이것은 디버깅과 문제의 원인을 파악하는데 유용하고, 우리는 문제가 발생할 경우 이 정보를 공유해 달라고 요청할 수 있습니다. - **대시보드 (`/ops/dashboard`):** 애플리케이션의 성능과 상태 (예: 메모리 사용량, 실행 중인 프로세스, 요청 수) 를 확인할 수 있는 대시보드를 볼 수 있습니다. 이 대시보드는 사용 중인 하드웨어가 부하를 처리하기에 충분한지 확인하는데 매우 유용할 수 있습니다. --- URL: "/ko/server/introduction/why-a-server" LLMS_URL: "/ko/server/introduction/why-a-server.md" title: "Why a server?" titleTemplate: ":title | Introduction | Server | Tuist" description: "Tuist에는 왜 서버가 필요하고 이것이 앱 개발을 확장하는데 어떤 도움이 되는지 배워봅니다." --- # Why a server? {#why-a-server} 어떤 규모에서는 프로젝트 최적화와 개발자가 프로젝트와 상호 작용하는 방식을 개선하기 위해 시간이 지남에 따라 변하는 데이터를 인지해야 하고, 팀들이 협업하는 데 사용하는 다른 툴과 서비스와의 통합이 필요합니다. 이것은 **데이터를 데이터베이스에 저장하고, 비동기적으로 처리하며, 다른 서비스와 통합할 수 있는 서버**가 있을 때만 가능합니다. 서버의 역할은 다른 시스템에서는 일반적이지만, 앱 개발에서는 일반적이지 않습니다. 여러 팀은 서버의 기능과 유사한 CI 서비스의 기능을 활용하는 오픈 소스에 크게 의존해 왔습니다. 하지만 프로젝트의 복잡성과 작업하는 개발자의 수가 증가하면서 이러한 솔루션의 한계가 더욱 뚜렷해 졌습니다. 우리는 여러 팀이 프로젝트를 확장하기 위해 서버를 설정하고 유지 관리하는 것에 대해 걱정할 필요가 없다고 믿습니다. 그래서 우리는 Tuist와 [Xcode 프로젝트](https://developer.apple.com/documentation/xcode/creating-an-xcode-project-for-an-app)를 통합하여 프로젝트와 팀을 확장할 수 있도록 지원하는 서버를 구축했습니다. > [!TIP] 프로젝트와 워크플로우에 슈퍼파워 부여\ > 서버를 프로젝트와 워크플로우에 부여할 수 있는 슈퍼파워라고 생각합니다. 바이너리 캐싱과 같은 슈퍼파워는 Tuist 프로젝트를 요구하지만, 다른 슈퍼파워는 일반 Xcode 프로젝트에서 잘 동작합니다. --- URL: "/ko/server/introduction/integrations" LLMS_URL: "/ko/server/introduction/integrations.md" title: "Integrations" titleTemplate: ":title | Introduction | Server | Tuist" description: "Tuist를 다른 툴과 서비스에 연결하는 방법을 배워봅니다." --- # Integrations {#integrations} 우리는 개발자들이 [GitHub](https://github.com)에서 Pull Request를 검토하거나 [Slack](https://slack.com)에서 팀과 소통하는 것과 같이 코딩 환경 밖에서도 시간을 보내기 때문에 개발자들이 있는 곳에서 그들을 만나야 한다고 생각합니다. 그래서 우리는 Tuist를 워크플로우에서 더 쉽게 사용할 수 있도록 인기있는 툴과 서비스와의 통합을 구축했습니다. 이 페이지에서는 현재 지원하고 있는 통합 목록을 나타냅니다. ## Git 플랫폼 {#git-platforms} Git 리포지토리는 대부분의 소프트웨어 프로젝트에서 핵심적인 역할을 하고 있습니다. 우리는 Git 플랫폼과 통합하여 Pull Request에서 바로 Tuist와 관련된 유용한 정보를 제공하거나 기본 브랜치 동기화와 같은 설정을 자동으로 처리합니다. ### GitHub {#github} [Tuist GitHub 앱](https://github.com/marketplace/tuist)을 설치합니다. 설치하면, Tuist에 리포지토리 URL을 알려줘야 합니다, 예를 들어: ```sh tuist project update tuist/tuist --repository-url https://github.com/tuist/tuist ``` --- URL: "/ko/server/introduction/authentication" LLMS_URL: "/ko/server/introduction/authentication.md" title: "Authentication" titleTemplate: ":title | Introduction | Server | Tuist" description: "CLI에서 Tuist 서버에 인증하는 방법을 배워봅니다." --- # 인증 {#authentication} 서버와 상호 작용하기 위해 CLI는 [Bearer 인증](https://swagger.io/docs/specification/authentication/bearer-authentication/)을 사용하여 요청을 인증해야 합니다. CLI는 사용자 또는 프로젝트로 인증하는 것을 지원합니다. ## 사용자로 인증 {#as-a-user} 로컬에서 CLI를 사용할 때 사용자로 인증하는 것을 권장합니다. 사용자로 인증하기 위해 다음의 명령어를 수행해야 합니다: ```bash tuist auth login ``` 이 명령어는 웹 기반 인증 절차를 안내합니다. 인증을 완료하면, CLI는 `~/.config/tuist/credentials`에 오래 지속되는 리프레시 토큰과 일시적인 접근 토큰을 저장합니다. 디렉토리에 각 파일은 인증한 도메인을 나타내며 기본값은 `cloud.tuist.io.json` 이어야 합니다. 해당 디렉토리에 저장된 정보는 민감한 정보이므로 **안전하게 보관해야 합니다**. CLI는 서버에 요청을 보낼 때 자동으로 자격 증명을 조회합니다. 접근 토근이 만료되면, CLI는 새로운 접근 토큰을 얻기 위해 리프레시 토큰을 사용합니다. ### 조직 SSO {#organization-sso} Google Workspace 조직이 있고 동일한 Google 도메인으로 로그인하는 개발자가 Tuist 조직에 추가되도록 설정하려면 다음과 같이 설정할 수 있습니다: ```bash tuist organization update sso my-organization --provider google --organization-id my-google-domain.com ``` On-premise 고객이 Okta를 설정한 경우, Google과 동일한 동작으로 수행되도록 하려면 다음 명령어를 수행할 수 있습니다: ```bash tuist organization update sso my-organization --provider okta --organization-id my-okta-domain.com ``` > [!IMPORTANT]. 조직의 도메인을 설정하려면 해당 조직에 연결된 이메일을 사용하여 Google에 인증되어 있어야 합니다. ## 프로젝트로 인증 {#as-a-project} CI와 같은 환경에서는 이런 상호 작용하며 인증할 수 없습니다. 이러한 환경에서는 프로젝트 범위의 토큰을 사용하여 프로젝트로 인증하는 것을 권장합니다: ```bash tuist project tokens create ``` CLI는 토큰이 환경 변수 `TUIST_CONFIG_TOKEN`에 정의되어야 하고, `CI=1` 환경 변수도 설정되어야 합니다. CLI는 요청을 인증하기 위해 토큰을 사용합니다. > [!IMPORTANT] 제한된 범위\ > 프로젝트 범위의 토큰 권한은 CI 환경에서 프로젝트가 수행할 수 있는 안전한 작업으로 제한됩니다. 우리는 향후 토큰이 가진 권한에 대한 문서를 제공할 예정입니다. --- URL: "/ko/server/introduction/accounts-and-projects" LLMS_URL: "/ko/server/introduction/accounts-and-projects.md" title: "Accounts and projects" titleTemplate: ":title | Introduction | Server | Tuist" description: "Tuist에서 계정과 프로젝트를 생성하고 관리하는 방법을 배워봅니다." --- # Accounts and projects {#accounts-and-projects} ## 계정 {#accounts} 서버를 사용하려면 계정이 필요합니다. 계정은 두 가지 타입이 있습니다: - **개인 계정:** 이러한 계정은 회원 가입 시 자동으로 생성되고 아이디는 제공하는 서비스 (예: GitHub) 에서 얻거나 이메일 주소의 첫번째 부분으로 설정됩니다. - **조직 계정:** 이러한 계정은 수동으로 생성되고 개발자가 지정한 아이디로 설정합니다. 조직 계정은 프로젝트의 협업자로 멤버를 추가할 수 있습니다. [GitHub](https://github.com)에 익숙하다면 개인 계정과 조직 계정을 가질 수 있고 이 계정들은 URL을 구성할 때 식별자로 사용된다는 개념과 유사합니다. > [!NOTE] CLI-FIRST\ > 계정과 프로젝트를 관리하기 위한 동작은 대부분 CLI를 통해서 수행됩니다. 우리는 계정과 프로젝트를 쉽게 관리하기 위한 웹 인터페이스를 개발 중입니다. `tuist organization` 하위 명령어를 통해 조직을 관리할 수 있습니다. 새로운 조직 계정을 생성하기 위해 다음과 같이 수행합니다: ```bash tuist organization create {account-handle} ``` ## 프로젝트 {#projects} Tuist 프로젝트든 Xcode 프로젝트든 원격 프로젝트를 통해 계정과 통합되어야 합니다. GitHub와 계속 비교해 보면, 변경 사항을 푸시할 수 있는 로컬 리포지토리와 원격 리포지토리와 비슷합니다. 프로젝트를 생성하고 관리하기 위해 `tuist project`를 사용할 수 있습니다. 프로젝트는 조직 식별자와 프로젝트 식별자를 결합한 전체 식별자로 식별됩니다. 예를 들어, `tuist`라는 식별자를 가진 조직과 `tuist`라는 식별자를 가지는 프로젝트가 있다면, 전체 식별자는 `tuist/tuist` 입니다. 로컬 프로젝트와 원격 프로젝트 간의 연결은 구성 파일을 통해 이루어집니다. 아무런 구성 파일이 없다면 `Tuist.swift` 파일을 생성하고 다음의 내용을 추가합니다: ```swift let tuist = Tuist(fullHandle: "{account-handle}/{project-handle}") // e.g. tuist/tuist ``` > [!IMPORTANT] TUIST 프로젝트 전용 기능\ 바이너리 캐싱과 같은 기능은 Tuist 프로젝트가 있어야 사용할 수 있습니다. Xcode 프로젝트를 사용한다면 해당 기능을 사용할 수 없습니다. 프로젝트 URL은 전체 식별자를 사용하여 구성됩니다. 예를 들어, Tuist 대시보드는 프로젝트의 전체 식별자가 `tuist/tuist`라면 [cloud.tuist.io/tuist/tuist](https://cloud.tuist.io/tuist/tuist)으로 접근할 수 있습니다. --- URL: "/ko/references/project-description/[identifier]" LLMS_URL: "/ko/references/project-description/[identifier].md" editLink: false titleTemplate: ":title · Project Description · References · Tuist" --- --- URL: "/ko/references/migrations/from-v3-to-v4" LLMS_URL: "/ko/references/migrations/from-v3-to-v4.md" title: "버전 3에서 버전 4로" titleTemplate: ":title · Migrations · References · Tuist" description: "이 페이지에서는 Tuist CLI를 버전 3에서 버전 4로 마이그레이션하는 방법을 설명합니다." --- # 버전 3에서 버전 4로 {#from-tuist-v3-to-v4} [Tuist 4](https://github.com/tuist/tuist/releases/tag/4.0.0)의 출시와 함께, 프로젝트를 장기적으로 더 쉽게 사용하고 유지보수할 수 있도록 몇 가지 획기적인 변경 사항을 도입했습니다. 이 문서는 Tuist 3에서 Tuist 4로 업그레이드하기 위해 프로젝트에 필요한 변경 사항을 설명합니다. ### `tuistenv`를 통한 버전 관리가 삭제되었습니다. {#dropped-version-management-through-tuistenv} Tuist 4 이전에는 설치 스크립트가 `tuistenv`라는 도구를 설치했으며, 설치 시 이 도구는 `tuist`로 이름이 변경되었습니다. 이 도구는 Tuist의 버전을 설치하고 활성화하여 환경 간의 일관성을 보장하는 역할을 했습니다. Tuist의 기능 범위를 줄이기 위해, 동일한 작업을 수행하지만 더 유연하고 다양한 도구에서 사용할 수 있는 [Mise](https://mise.jdx.dev/)로 대체하면서 `tuistenv` 지원을 중단하기로 결정했습니다. `tuistenv`를 사용하고 있었다면, 현재 버전의 Tuist를 `curl -Ls https://uninstall.tuist.io | bash` 명령어로 제거한 후, 원하는 설치 방법을 사용하여 다시 설치해야 합니다. 환경 간에 일관된 방식으로 버전을 설치하고 활성화할 수 있기 때문에 Mise 사용을 강력히 권장합니다. ::: code-group ```bash [Uninstall tuistenv] curl -Ls https://uninstall.tuist.io | bash ``` ::: > [!IMPORTANT] CI 환경과 Xcode 프로젝트에서 Mise 사용 > Mise가 제공하는 일관성을 활용하기로 했다면, [CI 환경](https://mise.jdx.dev/continuous-integration.html)과 [Xcode 프로젝트](https://mise.jdx.dev/ide-integration.html#xcode)에서 Mise를 사용하는 방법에 대한 문서를 확인해 보시기를 권장합니다. > [!NOTE] Homebrew 지원됨 > macOS용 인기 패키지 관리자인 Homebrew를 통해 Tuist를 설치할 수 있다는 점을 참고하세요. Homebrew를 사용하여 Tuist를 설치하는 방법은 설치 가이드에서 확인할 수 있습니다. ### `ProjectDescription` 모델에서 `init` 생성자가 제거되었습니다. {#dropped-init-constructors-from-projectdescription-models} API의 가독성과 표현력을 높이기 위해, 모든 `ProjectDescription` 모델에서 `init` 생성자를 제거하기로 결정했습니다. 이제 각 모델은 인스턴스를 생성할 수 있는 정적 생성자를 제공합니다. 만약 `init` 생성자를 사용하고 있었다면, 이제 정적 생성자를 사용하도록 프로젝트를 업데이트해야 합니다. > [!TIP] 명명 규칙 > 우리가 따르는 명명 규칙은 모델의 이름을 정적 생성자의 이름으로 사용하는 것입니다. 예를 들어, `Target` 모델의 정적 생성자는 `Target.target`입니다. ### `--no-cache` 가 `--no-binary-cache`로 이름이 변경되었습니다. {#renamed-nocache-to-nobinarycache} 왜냐하면 `--no-cache` 플래그가 모호했기 때문에, 이를 `--no-binary-cache`로 변경하여 바이너리 캐시를 의미한다는 점을 명확히 했습니다. 만약 `--no-cache` 플래그를 사용하고 있었다면, 이제 `--no-binary-cache` 플래그를 사용하도록 프로젝트를 업데이트해야 합니다. ### `tuist fetch`가 `tuist install`로 이름이 변경되었습니다. {#renamed-tuist-fetch-to-tuist-install} 업계 표준에 맞추기 위해 `tuist fetch` 명령어를 `tuist install`로 변경했습니다. `tuist fetch` 명령어를 사용하고 있었다면 `tuist install` 명령어를 사용하도록 프로젝트를 업데이트해야 합니다. ### [종속성을 위한 DSL로 'Package.swift'를 채택합니다](https://github.com/tuist/tuist/pull/5862) {#adopt-packageswift-as-the-dsl-for-dependencieshttpsgithubcomtuisttuistpull5862} Tuist 4 이전에는 `Dependencies.swift` 파일에서 종속성을 정의할 수 있었습니다. 이 독자적인 형식은 [Dependabot](https://github.com/dependabot)이나 [Renovatebot](https://github.com/renovatebot/renovate)과 같은 도구에서 종속성을 자동으로 업데이트하는 기능을 지원하지 못하게 했습니다. 또한 사용자에게 불필요한 간접 경로를 도입하게 했습니다. 따라서 Tuist에서 종속성을 정의하는 유일한 방법으로 `Package.swift`를 채택하기로 결정했습니다. `Dependencies.swift` 파일을 사용하고 있었다면, `Tuist/Dependencies.swift`의 내용을 루트 디렉토리의 `Package.swift`로 이동하고, 통합을 설정하기 위해 `#if TUIST` 지시문을 사용해야 합니다. Swift Package 종속성을 통합하는 방법에 대한 자세한 내용은 여기에서 확인할 수 있습니다. ### `tuist cache warm`이 `tuist cache`로 이름이 변경되었습니다. {#renamed-tuist-cache-warm-to-tuist-cache} 간결함을 위해 `tuist cache warm` 명령어를 `tuist cache`로 이름 변경하기로 결정했습니다. `tuist cache warm` 명령어를 사용하고 있었다면, 이제 `tuist cache` 명령어를 사용하도록 프로젝트를 업데이트해야 합니다. ### `tuist cache print-hashes`가 `tuist cache --print-hashes`로 이름이 변경되었습니다. {#renamed-tuist-cache-printhashes-to-tuist-cache-printhashes} `tuist cache print-hashes` 명령어를 `tuist cache --print-hashes`로 변경하여, 이것이 `tuist cache` 명령어의 플래그임을 명확히 했습니다. `tuist cache print-hashes` 명령어를 사용하고 있었다면, 이제 `tuist cache --print-hashes` 플래그를 사용하도록 프로젝트를 업데이트해야 합니다. ### caching profiles가 제거되었습니다. {#removed-caching-profiles} Tuist 4 이전에는 Tuist/Config.swift에 캐시 구성을 포함한 caching profiles를 정의할 수 있었습니다. 이 기능은 다른 프로필을 사용하여 프로젝트를 생성할 때 혼란을 초래할 수 있기 때문에 제거하기로 결정했습니다. 게다가, 이 기능은 사용자가 디버그 프로필을 사용하여 앱의 릴리스 버전을 빌드하는 상황을 초래할 수 있어, 예상치 못한 결과를 발생시킬 수 있습니다. 그 대신, 프로젝트를 생성할 때 사용할 구성을 지정할 수 있는 `--configuration` 옵션을 도입했습니다. caching profiles을 사용하고 있었다면, 이제 `--configuration` 옵션을 대신 사용하도록 프로젝트를 업데이트해야 합니다. ### {#removed-skipcache-in-favor-of-arguments} `generate` 명령에서 `--skip-cache` 플래그를 제거하고, 대신 arguments를 사용하여 바이너리 캐시를 건너뛰어야 할 대상을 제어하도록 변경되었습니다. `--skip-cache` 플래그를 사용하고 있었다면, 이제 arguments를 사용하도록 프로젝트를 업데이트해야 합니다. ::: code-group ```bash [Before] tuist generate --skip-cache Foo ``` ```bash [After] tuist generate Foo ``` ::: ### [중단된 서명 기능](https://github.com/tuist/tuist/pull/5716) {#dropped-signing-capabilitieshttpsgithubcomtuisttuistpull5716} 서명 기능은 이미 [Fastlane](https://fastlane.tools/)과 Xcode 자체와 같은 커뮤니티 도구들에 의해 해결되었으며, 이들이 훨씬 더 잘 처리합니다. 우리는 서명 기능이 Tuist의 추가 목표라고 판단했으며, 프로젝트의 핵심 기능에 집중하는 것이 더 좋다고 생각했습니다. 만약 Tuist의 서명 기능을 사용하고 있었다면, 이는 저장소에서 인증서와 프로필을 암호화하여 프로젝트 생성 시 올바른 위치에 설치하는 방식이었습니다. 이제 이 기능이 제거되었기 때문에, 프로젝트 생성 전에 실행되는 스크립트에서 해당 로직을 직접 구현해야 할 수 있습니다. 특히: - 파일 시스템이나 환경 변수에 저장된 키를 사용하여 인증서와 프로필을 복호화하고, 인증서를 키체인에 설치하며, 프로비저닝 프로필은 `~/Library/MobileDevice/Provisioning\ Profiles` 디렉토리에 설치하는 스크립트를 작성해야 할 수 있습니다. - 기존의 프로필과 인증서를 받아서 암호화하는 스크립트를 작성해야 할 수 있습니다. > [!TIP] 서명 요구 사항 > 서명은 키체인에 올바른 인증서가 있어야 하고, 프로비저닝 프로필은 `~/Library/MobileDevice/Provisioning\ Profiles` 디렉토리에 있어야 합니다. `security` 명령어를 사용하여 인증서를 키체인에 설치하고, `cp` 명령어를 사용하여 프로비저닝 프로필을 올바른 디렉토리에 복사할 수 있습니다. ### `Dependencies.swift`를 통한 Carthage 통합이 제거되었습니다. {#dropped-carthage-integration-via-dependenciesswift} Tuist 4 이전에는 Carthage 의존성을 `Dependencies.swift` 파일에 정의할 수 있었으며, 사용자는 `tuist fetch` 명령을 실행하여 이를 가져올 수 있었습니다. 우리는 또한 이것이 Tuist의 추가 목표라고 생각했으며, 특히 앞으로 Swift Package Manager가 의존성을 관리하는 기본적인 방법이 될 것이라는 점을 고려했습니다. 만약 Carthage 의존성을 사용하고 있었다면, 이제 `Carthage`를 직접 사용하여 미리 컴파일된 프레임워크와 XCFramework를 Carthage의 표준 디렉토리로 가져온 후, `TargetDependency.xcframework`와 `TargetDependency.framework` 케이스를 사용하여 해당 바이너리를 타겟에서 참조해야 합니다. > 일부 사용자들은 우리가 Carthage 지원을 중단했다고 이해했습니다. 우리는 그렇지 않았습니다. Tuist와 Carthage의 출력 간의 계약은 시스템에 저장된 프레임워크와 XCFramework에 관한 것입니다. 변경된 유일한 점은 의존성을 가져오는 책임이 누구에게 있는지입니다. 이전에는 Tuist가 Carthage를 통해 의존성을 가져왔지만, 이제는 Carthage가 직접 의존성을 가져옵니다. ### `TargetDependency.packagePlugin` API가 제거되었습니다. {#dropped-the-targetdependencypackageplugin-api} Tuist 4 이전에는 `TargetDependency.packagePlugin` 케이스를 사용하여 패키지 플러그인 의존성을 정의할 수 있었습니다. Swift Package Manager가 새로운 패키지 유형을 도입하는 것을 본 후, 우리는 API를 더 유연하고 미래 지향적으로 발전시키기로 결정했습니다. 만약 `TargetDependency.packagePlugin`을 사용하고 있었다면, 대신 `TargetDependency.package`를 사용해야 하며, 사용하려는 패키지 유형을 인수로 전달해야 합니다. ### [더 이상 사용되지 않는 API가 제거되었습니다.](https://github.com/tuist/tuist/pull/5560) {#dropped-deprecated-apishttpsgithubcomtuisttuistpull5560} 우리는 Tuist 3에서 더 이상 사용되지 않는 것으로 표시된 API들을 제거했습니다. 만약 더 이상 사용되지 않는 API를 사용하고 있었다면, 새로운 API를 사용하도록 프로젝트를 업데이트해야 합니다. --- URL: "/ko/references/examples/[example]" LLMS_URL: "/ko/references/examples/[example].md" editLink: false titleTemplate: ":title · Examples · References · Tuist" --- 예제 확인 --- URL: "/ko" LLMS_URL: "/ko.md" title: "Tuist란?" description: "Apple의 기본 도구를 확장하여 더 나은 앱을 효과적으로 개발하세요." --- # From idea to the store 우리는 **더 나은 앱을 더 빠르게 개발할 수 있도록 Apple의 기본 도구와 통합된 확장 도구**입니다.
## Installation Tuist를 설치하고 `tuist init`을 수행해 시작합니다: ::: code-group ```bash [Homebrew] brew tap tuist/tuist brew install --formula tuist tuist init ``` ```bash [Mise] mise x tuist@latest -- tuist init ``` ::: 더 자세한 내용은 설치 가이드 를 확인하세요. ## 더 알아보기 몇 분 안에 Tuist를 사용해 보고, Tuist를 최대한 활용하는 방법을 배워봅니다. ## 최신 내용 확인 우리 팀의 발표를 확인하세요. 최신 정보를 얻고 전문성을 키워보세요. ## 커뮤니티 참여 소스 코드를 확인하고, 다른 사람들과 교류하며 소통하세요. --- URL: "/ko/guides/tuist/about" LLMS_URL: "/ko/guides/tuist/about.md" title: "About Tuist" titleTemplate: ":title · Guides · Tuist" description: "Extend your Apple native tooling to better apps at scale." --- # About Tuist {#about-tuist} 앱 개발, 특히 Apple과 같은 플랫폼에서 조직은 종종 **생산성을 저해하는 문제**에 직면합니다. 대표적인 예로는 느린 컴파일 속도, 신뢰할 수 없는 테스트, 그리고 많은 리소스를 소모하는 복잡한 자동화 워크플로우 등이 있습니다. 일반적으로 기업들은 이런 문제를 해결하기 위해 전담 플랫폼 팀을 운영합니다. 이 전문가들은 코드베이스의 품질과 안정성을 유지하여, 다른 개발자들이 기능 개발에 집중할 수 있도록 합니다. 하지만 이런 방식은 비용이 많이 들고 위험할 수 있습니다. 핵심 팀원이 떠나면 생산성이 심각하게 저하될 수 있기 때문입니다. ## What {#what} Tuist는 앱 개발을 빠르고 효율적으로 할 수 있도록 설계된 툴체인입니다. 공식 도구 및 시스템과 자연스럽게 연동되어 개발자들이 익숙한 환경에서 작업할 수 있도록 지원합니다. 도구 및 시스템 통합의 부담을 덜어줌으로써, 팀은 기능 개발과 전반적인 개발자 경험 향상에 집중할 수 있습니다. 즉, Tuist는 가상의 플랫폼 팀 역할을 하며, 앱 아이디어가 떠오르는 순간부터 사용자에게 출시될 때까지 Tuist는 모든 과정에서 함께하며 발생하는 문제를 해결합니다. Tuist는 개발자들을 위한 주된 인터페이스인 [CLI](https://github.com/tuist/tuist)와 상태 정보 유지 및 외부 서비스를 연동을 위한 서버 로 구성되어 있습니다. ## Why {#why} 왜 Tuist를 사용해야 할까요? 다음과 같은 매력적인 이유가 있습니다. ### Simplify 🌱 {#simplify} 프로젝트가 성장하고 여러 플랫폼에 걸쳐 확장될수록 모듈화는 매우 중요해집니다. Tuist는 복잡한 과정을 간단하게 만들어 프로젝트 구조를 최적화하고 더 이해하기 쉬운 도구를 제공합니다. **Further reading:** Projects ### Optimize workflows 🚀 {#optimize-workflows} 프로젝트 정보를 활용하여 Tuist는 선택적 테스트 실행과 빌드 바이너리 재사용을 통해 효율성을 향상시킵니다. **Further reading:** Cache, Selective testing, Registry, and Previews ### Foster healthy project evolution 📈 {#foster-healthy-project-evolution} 우리는 당신의 프로젝트 동향을 분석하고, 현명한 의사 결정을 위한 전문적인 가이드를 제공합니다. 이 접근 방식은 문제가 있는 프로젝트에서 발생하는 좌절감과 생산성 저하를 방지하여 개발자 이탈과 비즈니스 목표 미달성을 방지합니다. **Further reading:** Server ### Break down silos 💜 {#break-down-silos} 플랫폼별 생태계(예: Xcode의 폐쇄적인 환경 등)과 달리, Tuist는 웹 중심 경험을 제공하며 Slack, Prometheus, Github과 같은 인기 있는 도구와 원활하게 통합되어 도구 간 협업을 강화합니다. **Further reading:** Projects --- Tuist, 프로젝트, 그리고 회사에 대해 더 알고 싶다면 [핸드북](https://handbook.tuist.io/) 을 확인해보세요. 우리의 비전, 가치, 그리고 Tuist를 만들어가는 팀에 대한 자세한 내용을 담고 있습니다. --- URL: "/ko/guides/share/previews" LLMS_URL: "/ko/guides/share/previews.md" title: "Previews" titleTemplate: ":title · Share · Guides · Tuist" description: "앱 미리보기를 생성하고 다른 사람과 공유하는 방법을 알아보세요." --- # Previews {#previews} > [!IMPORTANT] 요구사항 > > - Tuist 계정과 프로젝트 앱을 개발할 때 다른 사람들의 피드백을 받기 위해 앱을 공유하고 싶을 수 있습니다. 전통적으로, 팀들은 앱을 빌드하고 서명한 후 Apple의 [TestFlight](https://developer.apple.com/testflight/)와 같은 플랫폼에 업로드하여 이 작업을 수행해왔습니다. 하지만, 이 과정은 번거롭고 느릴 수 있으며, 특히 동료나 친구로부터 빠른 피드백을 받고자 할 때는 더욱 그렇습니다. Tuist는 이러한 과정을 간소화하기 위해 앱 미리보기를 생성하고 다른 사람과 공유할 수 있는 방법을 제공합니다. > [!IMPORTANT] DEVICE(실기기) 빌드 시 서명 필요 > DEVICE용으로 빌드할 때, 앱이 올바르게 서명되었는지 확인하는 책임은 사용자에게 있습니다. 우리는 향후 이 과정을 더 간소화할 계획입니다. :::code-group ```bash [Tuist Project] xcodebuild -scheme App -project App.xcodeproj -configuration Debug # simulator용 앱 빌드 xcodebuild -scheme App -project App.xcodeproj -configuration Debug -destination 'generic/platform=iOS' # device용 앱 빌드 tuist share App --configuration Debug --platforms iOS ``` ```bash [Xcode Project] tuist build App # simulator용 앱 빌드 tuist build App -- -destination 'generic/platform=iOS' # device용 앱 빌드 tuist share App ``` ::: 이 명령어는 앱을 시뮬레이터나 실기기에서 실행할 수 있는 공유 가능한 링크를 생성합니다. 사용자가 해야 할 일은 아래 명령어를 실행하는 것뿐입니다: ```bash tuist run {url} tuist run {url} --device "My iPhone" # 특정 기기에서 앱 실행하기 ``` Preview 링크를 통해 모바일 기기에서 직접 `.ipa` 파일을 다운로드하여 앱을 설치할 수 있습니다. `.ipa` preview 링크는 기본적으로 _public_ 으로 설정되어 있어 누구나 접근할 수 있습니다. 추후에는 링크를 private으로 설정할 수 있는 옵션이 제공될 예정이며, 이 경우 Tuist 계정으로 인증해야만 앱을 다운로드할 수 있습니다. `tuist run` 명령어를 사용하면 `latest`, branch 이름, 또는 특정 커밋 해시와 같은 지정자를 기반으로 최신 preview를 실행할 수 있습니다. ```bash tuist run App@latest # 프로젝트의 기본 branch와 연결된 최신 App preview 실행 tuist run App@my-feature-branch # 지정된 branch와 연결된 최신 App preview 실행 tuist run App@00dde7f56b1b8795a26b8085a781fb3715e834be # 특정 Git 커밋 SHA와 연결된 최신 App preview 실행 ``` > [!IMPORTANT] Preview 공개 범위 > 프로젝트가 속한 조직에 접근 권한이 있는 사람만 preview를 볼 수 있습니다. 링크 만료 기능에 대한 지원을 추가할 계획입니다. ## Tuist macOS app {#tuist-macos-app}

Tuist

Download
Tuist Previews를 더욱 쉽게 실행할 수 있도록, 우리는 Tuist macOS menu bar 앱을 개발했습니다. Tuist CLI를 통해 Preview를 실행하는 대신, macOS 앱을 [다운로드](https://tuist.dev/download)하여 사용할 수 있습니다. `brew install --cask tuist/tuist/tuist` 명령어를 실행하여 설치할 수도 있습니다. Preview 페이지에서 "Run"을 클릭하면, macOS 앱이 현재 선택된 디바이스에서 자동으로 실행됩니다 > [!IMPORTANT] 요구 사항\ > Preview를 다운로드하려면, `tuist auth login` 명령어를 사용해 인증해야 합니다. > 앞으로는 앱에서 직접 인증할 수 있게 될 예정입니다. > > 추가로, 로컬에 Xcode가 설치되어 있어야 합니다. ## Pull/merge request 코멘트 {#pullmerge-request-comments} > [!IMPORTANT] Git 플랫폼 연동 필요 > 자동으로 pull/merge request에 대한 코멘트를 받으려면, remote project Git 플랫폼과 연동해야 합니다. 새로운 기능에 대한 테스트는 모든 코드 리뷰에서 필수 과정이어야 합니다. 그러나 앱을 로컬에서 빌드하는 과정은 번거로워 개발자들이 실기기에서 기능을 전혀 테스트하지 않게 되는 경우가 많습니다. But _what if each pull request contained a link to the build that would automatically run the app on a device you selected in the Tuist macOS app?_ [GitHub](https://github.com)와 같은 Git 플랫폼에 Tuist 프로젝트를 연결한 후, CI workflow에 `tuist share MyApp`을 추가하세요. 이후 Tuist는 Pull request에 Preview 링크를 직접 게시합니다: ![GitHub app comment with a Tuist Preview link](/images/guides/share/github-app-with-preview.png) ## README 배지 {#readme-badge} Tuist Previews를 repository에서 더 잘 보이게 하려면, `README` 파일에 최신 TUIST Preview를 가리키는 배지를 추가할 수 있습니다: [![Tuist Preview](https://tuist.dev/Dimillian/IcySky/previews/latest/badge.svg)](https://tuist.dev/Dimillian/IcySky/previews/latest) `README`에 배지를 추가하려면, 아래의 markdown을 사용하고 계정 및 프로젝트 핸들을 여러분의 것으로 교체하세요: ``` [![Tuist Preview](https://tuist.dev/{account-handle}/{project-handle}/previews/latest/badge.svg)](https://tuist.dev/{account-handle}/{project-handle}/previews/latest) ``` ## 자동화 {#automations} `tuist share` 명령어에서 `--json` 플래그를 사용하면 JSON 형식의 출력을 얻을 수 있습니다: ``` tuist share --json ``` JSON 출력은 CI provider를 사용해 Slack 메시지를 보내는 등 같은 커스텀 자동화를 만드는 데 유용합니다. JSON에는 전체 preview 링크가 포함된 `url` 키와 실기기에서 preview를 쉽게 다운로드할 수 있도록 QR 코드 이미지 URL이 포함된 `qrCodeURL` 키가 있습니다. JSON 출력 예시는 아래와 같습니다: ```json { "id": 1234567890, "url": "https://cloud.tuist.io/preview/1234567890", "qrCodeURL": "https://cloud.tuist.io/preview/1234567890/qr-code.svg" } ``` --- URL: "/ko/guides/quick-start/install-tuist" LLMS_URL: "/ko/guides/quick-start/install-tuist.md" title: "Install Tuist" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "Tuist를 설치하는 방법을 알아보세요." --- # Install Tuist {#install-tuist} Tuist CLI는 실행 가능한 동적 프레임워크와 일련의 리소스(예: 템플릿)로 구성되어 있습니다. [소스에서](https://github.com/tuist/tuist) 수동으로 Tuist를 빌드할 수도 있지만, **올바른 설치를 위해 다음 설치 방법 중 하나를 사용하는 것이 좋습니다.** ### Mise {#recommended-mise} :::info Mise는 여러 환경에서 툴의 버전을 일관되게 유지가 필요한 팀이나 조직에 추천되는 [Homebrew](https://brew.sh)의 대안입니다. ::: 다음의 명령어를 통해 Tuist를 설치할 수 있습니다: ```bash mise install tuist # .tool-versions/.mise.toml에 지정된 현재 버전을 설치합니다. mise install tuist@x.y.z # 특정 버전 설치 mise install tuist@3 # 주요 버전 설치 ``` 단일 버전의 도구를 시스템 전반에 걸쳐 설치 및 활성화하는 Homebrew와 같은 도구와 달리 **Mise는 버전을 시스템 전체에 또는 프로젝트별로 활성화해야 한다는 점**에 유의하세요. 이 작업은 `mise use`를 실행하여 수행합니다. ```bash mise use tuist@x.y.z # 현재 프로젝트에서 tuist-x.y.z 사용 mise use tuist@latest # 현재 디렉터리에서 최신 tuist를 사용합니다. mise use -g tuist@x.y.z # 시스템의 기본값으로 tuist-x.y.z 사용 mise use -g tuist@system # 시스템의 tuist를 전역 기본값으로 사용합니다. ``` ### Homebrew{#recommended-homebrew} Tuist는 [Homebrew](https://brew.sh) 와 [우리의 전용 설치 스크립트](https://github.com/tuist/homebrew-tuist)를 통해 설치할 수 있습니다: ```bash brew tap tuist/tuist brew install --formula tuist brew install --formula tuist@x.y.z ``` :::tip 바이너리 파일의 신뢰성 검증 ```bash curl -fsSL "https://docs.tuist.dev/verify.sh" | bash ``` ::: --- URL: "/ko/guides/quick-start/get-started" LLMS_URL: "/ko/guides/quick-start/get-started.md" title: "Get started" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "Tuist를 설치하는 방법을 알아보세요." --- # Get started {#get-started} 어떤 디렉토리나 Xcode 프로젝트 또는 워크스페이스 디렉토리에서 Tuist를 시작하는 가장 쉬운 방법은 다음과 같습니다: ::: code-group ```bash [Mise] mise x tuist@latest -- tuist init ``` ```bash [Global Tuist (Homebrew)] tuist init ``` ::: 이 명령어는 생성된 프로젝트를 만들거나 기존의 Xcode 프로젝트 또는 워크스페이스를 통합하는 과정을 안내합니다. 이 설정을 서버에 연결하여 선택적 테스트, 프리뷰, 레지스트리와 같은 기능을 사용할 수 있도록 도와줍니다. > [!NOTE] 기존 프로젝트 마이그레이션\ > 더 나은 개발자 경험과 캐시를 활용하기 위해 기존 프로젝트를 생성된 프로젝트로 마이그레이션 하기 원한다면 마이그레이션 가이드를 참고 바랍니다. --- URL: "/ko/guides/quick-start/gather-insights" LLMS_URL: "/ko/guides/quick-start/gather-insights.md" title: "Gather insights" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "프로젝트에서 인사이트를 수집하는 방법에 대해 배웁니다." --- # Gather insights {#gather-insights} Tuist는 기능을 확장하기 위해 서버와 통합할 수 있습니다. 프로젝트와 빌드에 대해 인사이트를 수집하는 것이 그 기능 중에 하나입니다. 서버의 프로젝트에 필요한 계정만 있으면 됩니다. 먼저, 다음을 수행하여 인증을 해야 합니다: ```bash tuist auth login ``` ## Create a project {#create-a-project} 그런 다음에 다음을 수행하여 프로젝트를 생성할 수 있습니다: ```bash tuist project create my-handle/MyApp # Tuist project my-handle/MyApp was successfully created 🎉 {#tuist-project-myhandlemyapp-was-successfully-created-} ``` 프로젝트의 전체 식별자를 나타내는 `my-handle/MyApp`을 복사합니다. ## 프로젝트 연결 {#connect-projects} 서버에 프로젝트를 생성한 후에 로컬 프로젝트와 연결해야 합니다. `tuist edit`를 수행하고 프로젝트의 전체 처리를 포함하기 위해 `Tuist.swift` 파일을 수정합니다: ```swift import ProjectDescription let tuist = Tuist(fullHandle: "my-handle/MyApp") ``` Voilà! 이제 프로젝트와 빌드에 대한 인사이트를 수집하기 위한 준비가 되었습니다. `tuist test`를 수행하여 테스트를 수행하고 서버에 결과를 전송합니다. > [!NOTE]\ > Tuist는 결과를 로컬의 대기열에 추가하여 차단없이 전송을 시도합니다. 그러므로 명령어가 종료된 후에 바로 전송되지 않을 수 있습니다. CI에서 결과는 바로 전송됩니다. ![An image that shows a list of runs in the server](/images/guides/quick-start/runs.png) 프로젝트와 빌드에서 얻은 데이터는 정보에 입각한 결정을 내리는데 중요합니다. Tuist는 계속해서 기능을 확장하고 프로젝트 구성 변경 없이 이러한 기능을 사용할 수 있습니다. 마법 같지 않나요? 🪄 --- URL: "/ko/guides/quick-start/add-dependencies" LLMS_URL: "/ko/guides/quick-start/add-dependencies.md" title: "Add dependencies" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "첫 번째 Swift 프로젝트에 의존성을 추가하는 방법을 배웁니다." --- # 의존성 추가하기 {#add-dependencies} 프로젝트에서 추가 기능을 제공하기 위해 서드 파티 라이브러리에 의존하는 것은 일반적입니다. 의존성을 추가하기 위해서는 다음의 명령어를 수행하여 프로젝트를 편집합니다: ```bash tuist edit ``` 프로젝트 파일이 포함된 Xcode 프로젝트가 열립니다. `Package.swift`를 수정하고 추가 ```swift // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies .package(url: "https://github.com/onevcat/Kingfisher", .upToNextMajor(from: "7.12.0")) // [!code ++] ] ) ``` 그런 다음에 프로젝트의 애플리케이션 타겟을 수정하여 의존성으로 `Kingfisher`를 선언합니다: ```swift import ProjectDescription let project = Project( name: "MyApp", targets: [ .target( name: "MyApp", destinations: .iOS, product: .app, bundleId: "io.tuist.MyApp", infoPlist: .extendingDefault( with: [ "UILaunchStoryboardName": "LaunchScreen.storyboard", ] ), sources: ["MyApp/Sources/**"], resources: ["MyApp/Resources/**"], dependencies: [ .external(name: "Kingfisher") // [!code ++] ] ), .target( name: "MyAppTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyAppTests", infoPlist: .default, sources: ["MyApp/Tests/**"], resources: [], dependencies: [.target(name: "MyApp")] ), ] ) ``` 그런 다음에 `tuist install`을 수행해서 [Swift Package Manager](https://www.swift.org/documentation/package-manager/)를 사용하여 의존성을 해결하고 가져옵니다. > [!NOTE] 의존성 해결 도구로 SPM > Tuist는 의존성을 해결하는 데에만 Swift Package Manager (SPM) 을 사용하도록 권장합니다. 그런 다음에 Tuist는 이를 최대한의 구성 가능성과 제어를 위해 Xcode 프로젝트와 타겟으로 변환합니다. ## 프로젝트 시각화 {#visualize-the-project} 다음 명령어를 통해 프로젝트를 시각화 할 수 있습니다: ```bash tuist graph ``` 이 명령어는 프로젝트의 디렉토리에 `graph.png` 파일을 생성하고 엽니다. ![Project graph](/images/guides/quick-start/graph.png) ## 의존성 사용 {#use-the-dependency} Xcode에서 프로젝트를 열기 위해 `tuist generate`를 수행하고 `ContentView.swift` 파일에 다음의 변경 사항을 적용합니다: ```swift import SwiftUI import Kingfisher // [!code ++] public struct ContentView: View { public init() {} public var body: some View { Text("Hello, World!") // [!code --] .padding() // [!code --] KFImage(URL(string: "https://cloud.tuist.io/images/tuist_logo_32x32@2x.png")!) // [!code ++] } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } ``` Xcode에서 앱을 실행하고 URL로 이미지가 출력되는 것을 볼 수 있습니다. --- URL: "/ko/guides/develop/test" LLMS_URL: "/ko/guides/develop/test.md" title: "tuist test" titleTemplate: ":title · Develop · Guides · Tuist" description: "Tuist로 효율적으로 테스트하는 방법을 배웁니다." --- # Test {#test} Tuist는 프로젝트 생성이 필요하면 프로젝트를 생성하고 그런 다음에 플랫폼 별 빌드 툴 (예: Apple 플랫폼의 경우 `xcodebuild`) 로 테스트를 수행하는 `tuist test` 명령어를 제공합니다. `tuist test` 사용하는 것이 `tuist generate`로 프로젝트를 생성하고 플랫폼별 빌드 툴로 테스트를 수행하는 것과 어떠한 차이가 있는지 궁금할 수 있습니다. - **단일 명령어:** `tuist test`는 프로젝트를 컴파일하기 전에 필요한 경우 프로젝트를 생성하도록 보장합니다. - **보기좋은 출력:** Tuist는 출력을 더 사용자 친화적으로 만들어 주는 [xcbeautify](https://github.com/cpisciotta/xcbeautify)와 같은 툴을 사용하여 출력합니다. - <0><1>캐시: 원격 캐시에서 빌드 artifact를 재사용하여 빌드를 최적화 합니다. - 스마트 러너: 필요한 테스트만 수행하므로 시간과 리소스를 절약할 수 있습니다. - 불안정성: 불안정한 테스트를 방지하고, 감지, 그리고 수정할 수 있습니다. ## 사용법 {#usage} 프로젝트의 테스트를 수행하기 위해 `tuist test` 명령어를 사용할 수 있습니다. 이 명령어는 필요한 경우 프로젝트를 생성한 다음에 플랫폼별 빌드 툴을 사용하여 테스트를 수행합니다. `--` 구분자를 사용하여 이후의 모든 인자를 직접 빌드 툴로 전달하는 것을 지원합니다. ::: code-group ```bash [Running scheme tests] tuist test MyScheme ``` ```bash [Running all tests without binary cache] tuist test --no-binary-cache ``` ```bash [Running all tests without selective testing] tuist test --no-selective-testing ``` ::: ## Pull/merge request 의견 {#pullmerge-request-comments} > [!IMPORTANT] 요구 사항\ > Pull/merge request 의견을 자동으로 받으려면 원격 프로젝트Git 플랫폼과 통합해야 합니다. CI 환경에서 테스트를 수행할 때 트리거된 CI 빌드의 pull/merge request와 테스트 결과를 연동할 수 있습니다. 이를 통해 pull/merge request에 테스트 결과를 게시할 수 있습니다. ![GitHub App example](/images/contributors/scheme-arguments.png) --- URL: "/ko/guides/develop/selective-testing/xcodebuild" LLMS_URL: "/ko/guides/develop/selective-testing/xcodebuild.md" title: "xcodebuild" titleTemplate: ":title · Selective testing · Develop · Guides · Tuist" description: "`xcodebuild`를 이용한 선택적 테스팅을 활용하는 방법 배우기." --- # xcodebuild {#xcodebuild} > [!IMPORTANT] 요구 사항 > > - Tuist 계정과 프로젝트 `xcodebuild`를 이용하여 테스트를 개별적으로 실행하려면, `xcodebuild` 명령어 앞에 `tuist`를 붙이면 됩니다. 예를 들어서, `tuist xcodebuild test -scheme App`와 같이 사용할 수 있습니다. 이 명령어는 프로젝트를 해시화하고, 이후에 어떠한 항목들이 바뀌는지 확인하기 위한 용도로 사용될 수 있습니다. `tuist xcodebuild test`는 해시를 이용하여 테스트들을 필터링하고 가장 최근에 성공한 테스트 실행과 비교하여 변경된 부분이 있는 테스트만 재실행합니다. 예를 들어, 다음과 같은 의존성 그래프가 있다고 가정해 봅니다: - `FeatureA`는 `FeatureATests`를 가지며, `Core`에 의존 - `FeatureB`는 `FeatureBTests`를 가지며, `Core`에 의존 - `Core`는 `CoreTests`를 가짐 `tuist xcodebuild test`는 다음과 같이 동작합니다: | Action | Description | Internal state | | -------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------ | | `tuist xcodebuild test` 실행 | `CoreTests`, `FeatureATests`, 그리고 `FeatureBTests`에서 테스트 실행 | `FeatureATests`, `FeatureBTests`, 그리고 `CoreTests`의 해시 저장 | | `FeatureA` 업데이트 | 개발자가 해당 타겟의 코드를 수정 | 이전과 동일 | | `tuist xcodebuild test` 실행 | `FeatureATests`의 해시가 변경되었으므로 `FeatureATests`의 테스트 실행 | `FeatureATests`의 새로운 해시 저장 | | `Core` 업데이트 | 개발자가 해당 타겟의 코드를 수정 | 이전과 동일 | | `tuist xcodebuild test` 실행 | `CoreTests`, `FeatureATests`, 그리고 `FeatureBTests`에서 테스트 실행 | `FeatureATests`, `FeatureBTests`, 그리고 `CoreTests`의 새로운 해시 저장 | `tuist xcodebuild test` 을 CI에서 사용하기 위해서는, Continuous integration guide에 나와있는 설명을 참고하시면 됩니다. --- URL: "/ko/guides/develop/selective-testing/generated-project" LLMS_URL: "/ko/guides/develop/selective-testing/generated-project.md" title: "Generated project" titleTemplate: ":title · Selective testing · Develop · Guides · Tuist" description: "생성된 프로젝트에서 선택적 테스트를 활용하는 방법을 배워봅니다." --- # Generated project {#generated-project} > [!IMPORTANT] 요구 사항 > > - 생성된 프로젝트 > - Tuist 계정과 프로젝트 생성된 프로젝트에서 선택적으로 테스트를 실행하려면 `tuist test` 명령어를 사용하세요. 이 명령어는 캐시 워밍과 동일한 방식으로 Xcode 프로젝트를 해시하며, 성공적으로 실행되면 다음 실행 시 변경 사항을 파악하기 위해 해시 값을 저장합니다. 다음에 실행하면 `tuist test`는 해시를 사용하여 마지막으로 성공적으로 실행된 테스트 이후 변경된 테스트만 선별합니다. 예를 들어, 다음과 같은 의존성 그래프가 있다고 가정해 봅니다: - `FeatureA`는 `FeatureATests`를 가지며, `Core`에 의존 - `FeatureB`는 `FeatureBTests`를 가지며, `Core`에 의존 - `Core`는 `CoreTests`를 가짐 `tuist test`는 다음과 같이 동작합니다: | Action | Description | Internal state | | --------------- | ---------------------------------------------------------- | ------------------------------------------------------------ | | `tuist test` 호출 | `CoreTests`, `FeatureATests`, 그리고 `FeatureBTests`에서 테스트 실행 | `FeatureATests`, `FeatureBTests`, 그리고 `CoreTests`의 해시 저장 | | `FeatureA` 업데이트 | 개발자가 해당 타겟의 코드를 수정 | 이전과 동일 | | `tuist test` 호출 | `FeatureATests`의 해시가 변경되었으므로 `FeatureATests`의 테스트 실행 | `FeatureATests`의 새로운 해시 저장 | | `Core` 업데이트 | 개발자가 해당 타겟의 코드를 수정 | 이전과 동일 | | `tuist test` 호출 | `CoreTests`, `FeatureATests`, 그리고 `FeatureBTests`에서 테스트 실행 | `FeatureATests`, `FeatureBTests`, 그리고 `CoreTests`의 새로운 해시 저장 | `tuist test`는 바이너리 캐싱을 활용하여 테스트를 실행할 때 로컬이나 원격 스토리지에서 가능한 많은 바이너리를 사용함으로써 빌드 시간을 단축합니다. 선택적 테스트와 바이너리 캐싱의 조합은 CI에서 테스트를 수행하는 시간을 극적으로 줄일 수 있습니다. ## UI 테스트 {#ui-tests} Tuist는 UI 테스트의 선택적 테스트를 지원합니다. 그러나 Tuist는 사전에 테스트 대상을 알아야 합니다. 다음과 같이, `destination` 파라미터를 지정한 경우에만, Tuist는 선택적 UI 테스트를 수행할 수 있습니다: ```sh tuist test --device 'iPhone 14 Pro' # or tuist test -- -destination 'name=iPhone 14 Pro' # or tuist test -- -destination 'id=SIMULATOR_ID' ``` --- URL: "/ko/guides/develop/selective-testing" LLMS_URL: "/ko/guides/develop/selective-testing.md" title: "Selective testing" titleTemplate: ":title · Develop · Guides · Tuist" description: "마지막 성공한 테스트 수행 이후에 변경된 테스트만 수행하기 위해 선택적 테스트를 사용합니다." --- # Selective testing {#selective-testing} 프로젝트가 커질 수록 테스트 수도 증가합니다. 오랜 시간 동안 모든 PR 또는 `main`에 푸시할 때마다 전체 테스트를 수행하면 수 초의 시간이 걸렸습니다. 하지만 이 방법은 팀이 가진 수천 개의 테스트에는 적합하지 않습니다. CI에서 테스트를 실행할 때마다 변경 사항에 관계없이 모든 테스트를 다시 실행할 가능성이 높습니다. Tuist의 선택적 테스트는 우리의 hashing algorithm을 기반으로 마지막 성공적인 테스트 이후에 변경된 테스트만 실행하여 테스트 자체의 실행 속도를 크게 높일 수 있도록 도와줍니다. 선택적 테스트는 모든 Xcode 프로젝트를 지원하는 `xcodebuild` 에서 작동합니다. 또한, Tuist를 사용하여 프로젝트를 만들었을 경우 binary cache와 같은 추가 편의성을 제공하는 `tuist test` 명령어를 대신 사용할 수도 있습니다. 선택적 테스트를 시작하려면, 프로젝트 설정에 따른 지침을 따르세요: - xcodebuild - 생성된 프로젝트 > [!WARNING] 모듈 VS 파일 단위 세분화\ > 테스트와 소스 코드 간의 의존성을 코드 내에서 파악할 수 없으므로 선택적 테스트의 세분화는 파일 단위에서만 가능합니다. 따라서 선택적 테스트의 이점을 극대화 하려면 파일을 작고 집중적으로 유지하길 권장합니다. ## Pull/merge request 의견 {#pullmerge-request-comments} > [!IMPORTANT] GIT 플랫폼 연동 필요\ > 자동으로 pull/merge request 의견을 받으려면, <0>Tuist 프로젝트를 <1>Git 플랫폼과 연동해야 합니다. Tuist 프로젝트를 [GitHub](https://github.com)와 같은 Git 플랫폼과 연결하고, CI 워크플로우로 `tuist xcodebuild test`나 `tuist test`를 사용하기 시작하면, Tuist는 실행된 테스트와 건너뛴 테스트 정보를 포함하여 pull/merge request에 직접 의견을 남깁니다: ![Tuist Preview 링크를 사용하는 GitHub 앱 의견](/images/guides/develop/selective-testing/github-app-comment.png) --- URL: "/ko/guides/develop/registry/xcodeproj-integration" LLMS_URL: "/ko/guides/develop/registry/xcodeproj-integration.md" title: "XcodeProj 기반으로 패키지를 구성한 프로젝트" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Tuist Registry를 XcodeProj 방식으로 구성된 Xcode 프로젝트에서 활용하는 방법을 학습합니다." --- # XcodeProj 기반으로 패키지를 구성한 프로젝트 {#generated-project-with-xcodeproj-based-integration} XcodeProj 기반 구성을 사용할 경우, 의존성이 레지스트리에 등록되어 있다면 `--replace-scm-with-registry` 플래그를 사용해 레지스트리에서 의존성을 가져올 수 있습니다. `Tuist.swift` 파일의 `installOptions`에 추가합니다: ```swift import ProjectDescription let tuist = Tuist( fullHandle: "{account-handle}/{project-handle}", project: .tuist( installOptions: .options(passthroughSwiftPackageManagerArguments: ["--replace-scm-with-registry"]) ) ) ``` 의존성을 가져올 때마다 항상 레지스트리를 사용하게 하려면, `Tuist/Package.swift` 파일의 `dependencies` 에서 URL 대신 레지스트리 식별자(registry identifier)를 사용해야 합니다. 레지스트리 식별자는 항상 `{organization}.{repository}` 형식으로 구성됩니다. 예를 들어, `swift-composable-architecture` 패키지를 레지스트리를 통해 가져오고자 할 경우, 다음과 같이 작성합니다. ```diff dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") + .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ] ``` --- URL: "/ko/guides/develop/registry/xcode-project" LLMS_URL: "/ko/guides/develop/registry/xcode-project.md" title: "Xcode project" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Xcode 프로젝트에서 Tuist Registry를 사용하는 방법을 배워봅니다." --- # Xcode project {#xcode-project} Xcode 프로젝트에서 레지스트리를 사용하여 패키지를 추가하려면, 기본 Xcode UI를 사용합니다. Xcode의 `Package Dependencies` 탭에서 `+` 버튼을 눌러서 레지스트리에 패키지를 검색할 수 있습니다. 패키지가 레지스트리에 사용가능하면 우측 상단에 `tuist.dev` 레지스트리가 표시됩니다: ![패키지 의존성 추가](/images/guides/develop/build/registry/registry-add-package.png) > [!NOTE]\ > Xcode는 현재 소스 제어 패키지를 레지스트리로 자동으로 대체하는 기능을 지원하지 않습니다. 처리 속도를 높이려면 소스 제어 패키지를 삭제하고 레지스트리 패키지를 추가해야 합니다. --- URL: "/ko/guides/develop/registry/swift-package" LLMS_URL: "/ko/guides/develop/registry/swift-package.md" title: "Swift package" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Swift Package에서 Tuist Registry를 사용하는 방법을 학습합니다." --- # Swift package {#swift-package} Swift Package를 작업 중이라면, 레지스트리에 해당 의존성이 등록되어 있는 경우 `--replace-scm-with-registry` 플래그를 사용하여 레지스트리에서 의존성을 가져올 수 있습니다. ```bash swift package --replace-scm-with-registry resolve ``` 의존성을 가져올 때마다 항상 레지스트리를 사용하게 하려면, `Package.swift` 파일의 `dependencies` 에서 URL 대신 레지스트리 식별자(registry identifier)를 사용해야 합니다. 레지스트리 식별자는 항상 `{organization}.{repository}` 형식으로 구성됩니다. 예를 들어, `swift-composable-architecture` 패키지를 레지스트리를 통해 가져오고자 할 경우, 다음과 같이 작성합니다. ```diff dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") + .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ] ``` --- URL: "/ko/guides/develop/registry/generated-project" LLMS_URL: "/ko/guides/develop/registry/generated-project.md" title: "Generated project with the Xcode package integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "생성된 Xcode 프로젝트에서 Xcode 패키지 통합을 사용하여 Tuist Registry를 사용하는 방법을 배워봅니다." --- # Generated project with the Xcode package integration {#generated-project-with-xcode-based-integration} Xcode의 기본 통합을 사용하여 Tuist 프로젝트에 패키지를 추가하는 경우 URL 대신 레지스트리 식별자를 사용해야 합니다: ```swift import ProjectDescription let project = Project( name: "MyProject", packages: [ // Source control resolution // .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") // Registry resolution .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ], .target( name: "App", product: .app, bundleId: "io.tuist.App", dependencies: [ .package(product: "ComposableArchitecture"), ] ) ) ``` --- URL: "/ko/guides/develop/registry/continuous-integration" LLMS_URL: "/ko/guides/develop/registry/continuous-integration.md" title: "Continuous integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in continuous integration." --- # Continuous Integration (CI) {#continuous-integration-ci} To use the registry on your CI, you need to ensure that you have logged in to the registry by running `tuist registry login` as part of your workflow. > [!NOTE] ONLY XCODE INTEGRATION > Creating a new pre-unlocked keychain is required only if you are using the Xcode integration of packages. Since the registry credentials are stored in a keychain, you need to ensure the keychain can be accessed in the CI environment. Note some CI providers or automation tools like [Fastlane](https://fastlane.tools/) already create a temporary keychain or provide a built-in way how to create one. However, you can also create one by creating a custom step with the following code: ```bash TMP_DIRECTORY=$(mktemp -d) KEYCHAIN_PATH=$TMP_DIRECTORY/keychain.keychain KEYCHAIN_PASSWORD=$(uuidgen) security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security default-keychain -s $KEYCHAIN_PATH security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH ``` `tuist registry login` will then store the credentials in the default keychain. Ensure that your default keychain is created and unlocked _before_ `tuist registry login` is run. Additionally, you need to ensure the `TUIST_CONFIG_TOKEN` environment variable is set. You can create one by following the documentation here. An example workflow for GitHub Actions could then look like this: ```yaml name: Build jobs: build: steps: - # Your set up steps... - name: Create keychain run: | TMP_DIRECTORY=$(mktemp -d) KEYCHAIN_PATH=$TMP_DIRECTORY/keychain.keychain KEYCHAIN_PASSWORD=$(uuidgen) security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security default-keychain -s $KEYCHAIN_PATH security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH - name: Log in to the Tuist Registry env: TUIST_CONFIG_TOKEN: ${{ secrets.TUIST_CONFIG_TOKEN }} run: tuist registry login - # Your build steps ``` ### Incremental resolution across environments {#incremental-resolution-across-environments} Clean/cold resolutions are slightly faster with our registry, and you can experience even greater improvements if you persist the resolved dependencies across CI builds. Note that thanks to the registry, the size of the directory that you need to store and restore is much smaller than without the registry, taking significantly less time. To cache dependencies when using the default Xcode package integration, the best way is to specify a custom `-clonedSourcePackagesDirPath` when resolving dependencies via `xcodebuild`, such as: ```sh xcodebuild -resolvePackageDependencies -clonedSourcePackagesDirPath .build ``` Additionally, you will need to find a path of the `Package.resolved`. You can grab the path by running `ls **/Package.resolved`. The path should look something like `App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved`. For Swift packages and the XcodeProj-based integration, we can use the default `.build` directory located either in the root of the project or in the `Tuist` directory. Make sure the path is correct when setting up your pipeline. Here's an example workflow for GitHub Actions for resolving and caching dependencies when using the default Xcode package integration: ```yaml - name: Restore cache id: cache-restore uses: actions/cache/restore@v4 with: path: .build key: ${{ runner.os }}-${{ hashFiles('App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} restore-keys: .build - name: Resolve dependencies if: steps.cache-restore.outputs.cache-hit != 'true' run: xcodebuild -resolvePackageDependencies -clonedSourcePackagesDirPath .build - name: Save cache id: cache-save uses: actions/cache/save@v4 with: path: .build key: ${{ steps.cache-restore.outputs.cache-primary-key }} ``` --- URL: "/ko/guides/develop/registry" LLMS_URL: "/ko/guides/develop/registry.md" title: "Registry" titleTemplate: ":title · Develop · Guides · Tuist" description: "Tuist Registry를 사용하여 Swift 패키지 해석시간을 최적화 합니다." --- # Registry {#registry} > [!IMPORTANT] 요구사항 > > - A Tuist account and project 의존성이 증가함에 따라 이것을 해결하는 시간도 늘어납니다. 다른 패키지 관리 툴인 [CocoaPods](https://cocoapods.org/) 또는 [npm](https://www.npmjs.com/)는 중앙 집중식이지만 Swift Package Manager는 그렇지 않습니다. Because of that, SwiftPM needs to resolve dependencies by doing a deep clone of each repository, which can be time-consuming. To address this, Tuist provides an implementation of the [Package Registry](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/PackageRegistryUsage.md), so you can download only the commit you _actually need_. 레지스트리에 있는 패키지는 [Swift Package Index](https://swiftpackageindex.com/)를 기반으로 합니다 – 해당 페이지에서 패키지를 찾을 수 있다면 Tuist Registry에서도 사용할 수 있습니다. 또한 패키지는 엣지 스토리지를 통해 전세계에 분산되어 제공되며, 패키지를 확인할 때 최소한의 지연 시간으로 이용할 수 있습니다. ## Usage {#usage} 레지스트리를 설정하고 로그인하기 위해서는, 프로젝트 경로에서 다음의 명령을 실행해줍니다: ```bash tuist registry setup ``` 위 명령은 레지스트리에 레지스트리 설정용 파일과 로그를 생성합니다. 다른 작업자들도 레지스트리에 접근하도록 하기 위해서, 생성된 파일들을 커밋하고 작업자들이 아래의 명령을 통해 로그인 할 수 있도록 합니다: ```bash tuist registry login ``` Now you can access the registry! To resolve dependencies from the registry instead of from source control, continue reading based on your project setup: - Xcode project - Generated project with the Xcode package integration - Generated project with the XcodeProj-based package integration - Swift package CI에서 레지스트리를 설정하려면, 다음 내용을 참고해주세요: Continuous integration. ### Package registry identifiers {#package-registry-identifiers} If you want to ensure that the registry is used every time you resolve dependencies, you will need to update `dependencies` in your `Package.swift` file to use the registry identifier instead of a URL. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `swift-composable-architecture` package, do the following: > [!NOTE] > The identifier can't contain more than one dot. If the repository name contains a dot, it's replaced with an underscore. > For example, the `https://github.com/groue/GRDB.swift` package would have the registry identifier `groue.GRDB_swift`. --- URL: "/ko/guides/develop/projects/tma-architecture" LLMS_URL: "/ko/guides/develop/projects/tma-architecture.md" title: "The Modular Architecture (TMA)" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "The Modular Architecture (TMA) 에 대해 배우고, 이를 사용하여 프로젝트를 구조화 하는 방법을 배워봅니다." --- # The Modular Architecture (TMA) {#the-modular-architecture-tma} TMA는 Apple OS 애플리케이션을 구조화 하는 아키텍처 접근 방식이고, 확장성을 가지며, 빌드와 테스트 주기를 최적화 하고, 팀 내에 좋은 개발 방식을 보장합니다. 이것의 핵심은 독립적인 기능을 만들고 명확하고 간결한 API를 통해 서로 연결하여 애플리케이션을 만드는 것입니다. 이 가이드라인은 아키텍처의 원칙을 소개하며, 다른 계층의 애플리케이션 기능을 식별하고 연결하는데 도움을 줍니다. 이 아키텍처를 사용하기로 결정한다면, 도움이 되는 팁, 툴, 그리고 충고도 소개합니다. > [!INFO] µFEATURES\ > 이 아키텍처는 이전에 µFeatures로 알려진 아키텍처 입니다. 우리는 이 아키텍처의 목적과 원칙이 더 잘 반영되도록 The Modular Architecture (TMA) 로 이름을 변경했습니다. ## 주요 원칙 {#core-principle} 개발자는 메인 앱과 독립적으로 기능을 빠르게 **빌드, 테스트, 그리고 실행** 할 수 있어야 하고, Xcode의 UI 프리뷰, 코드 자동 완성, 그리고 디버깅 기능이 잘 동작해야 합니다. ## 모듈이란 무엇인가 {#what-is-a-module} 모듈은 애플리케이션 기능이며, 다음의 다섯가지 타겟 (여기서 타겟은 Xcode 타겟을 의미) 의 조합입니다: - **Source:** 기능의 소스 코드 (Swift, Objective-C, C++, JavaScript...) 와 리소스 (이미지, 폰트, 스토리보드, xib) 를 포함합니다. - **Interface:** 기능의 공개 인터페이스와 모델을 포함하는 보조 타겟입니다. - **Tests:** 기능의 단위 테스트와 통합 테스트를 포함합니다. - **Testing:** 테스트와 예제 앱에서 사용될 수 있는 테스트 데이터를 제공합니다. 또한, 나중에 볼 수 있듯이 다른 기능에서 사용할 수 있는 모듈 클래스와 프로토콜에 대한 모의 객체 (Mock) 를 제공합니다. - **Example:** 개발자가 특정 조건 (다른 언어, 다른 화면 크기, 다른 설정) 에서 기능을 확인하는데 사용할 수 있는 예제 앱을 포함합니다. 타겟에 네이밍 규칙을 따를 것을 권장하며, 이는 Tuist의 DSL 덕분에 프로젝트에 강제로 적용할 수 있습니다. | Target | Dependencies | Content | | ------------------ | --------------------------- | -------------- | | `Feature` | `FeatureInterface` | 소스 코드와 리소스 | | `FeatureInterface` | - | 공개 인터페이스와 모델 | | `FeatureTests` | `Feature`, `FeatureTesting` | 단위 테스트와 통합 테스트 | | `FeatureTesting` | `FeatureInterface` | 테스트 데이터와 모의 객체 | | `FeatureExample` | `FeatureTesting`, `Feature` | 예제 앱 | > [!TIP] UI 프리뷰\ > `Feature`는 UI 프리뷰를 사용하기 위해 Development Asset으로 `FeatureTesting`을 사용할 수 있습니다. > [!IMPORTANT] 테스트 타겟 대신 컴파일러 지시문\ > 또한, `Debug`로 컴파일할 때 `Feature`나 `FeatureInterface`에 테스트 데이터와 모의 객체를 포함하기 위해 컴파일러 지시문을 사용할 수 있습니다. 그래프를 단순화할 수 있지만, 결국 앱을 실행하는데 필요하지 않은 코드를 컴파일하게 될 수 있습니다. ## 왜 모듈인가 {#why-a-module} ### 명확하고 간결한 API {#clear-and-concise-apis} 모든 앱 소스 코드가 같은 타겟에 있으면 코드에서 암시적 의존성이 쉽게 생기고 결국 잘 알려진 스파게티 코드가 될 수 있습니다. 모든 것이 강하게 결합되어 있고, 상태는 예측하기 힘들어지고, 새로운 변경 사항을 도입하기 힘들어 집니다. 독립적인 타겟에 기능을 정의할 때 기능 구현의 일환으로 공개 API를 설계해야 합니다. 우리는 무엇을 공개할지, 기능이 어떻게 사용되어야 할지, 무엇을 비공개로 남겨야 할지 결정해야 합니다. 우리는 기능 클라이언트가 기능을 어떻게 사용할지에 대해 더 많은 제어를 할 수 있고, 안전한 API 설계로 좋은 개발 방식을 강제할 수 있습니다. ### 작은 모듈 {#small-modules} [분할 정복 (Divide and conquer)](https://en.wikipedia.org/wiki/Divide_and_conquer). 작은 모듈로 작업하면 더 집중할 수 있고 기능을 독립적으로 테스트하고 확인할 수 있습니다. 게다가 개발 주기는 훨씬 더 빨라지는데, 이는 기능을 동작시키기 위해 필요한 컴포넌트만 컴파일하는 선택적 컴파일 덕분입니다. 앱 전체의 컴파일은 작업의 마지막에만 필요하며 이때 앱에 기능을 통합해야 합니다. ### 재사용성 {#reusability} 코드를 앱과 확장과 같은 다른 결과물에 재사용하는 것은 프레임워크나 라이브러리를 사용하도록 권장합니다. 모듈을 구축하면 코드 재사용이 매우 간단해 집니다. 기존 모듈을 결합하고 _(필요할 때)_ 플랫폼 별 UI 계층을 추가하여 iMessage 확장, Today 확장, 또는 watchOS 애플리케이션을 만들 수 있습니다. ## 의존성 {#dependencies} 모듈이 다른 모듈에 의존할 경우, 해당 모듈은 의존할 모듈의 인터페이스 타겟에 대한 의존성을 선언합니다. 이러면 두 가지 장점이 있습니다. 하나의 모듈 구현이 다른 모듈 구현과 결합되는 것을 방지하고, 기능의 구현만 컴파일하고 직접적인 의존성과 전이적 의존성에 대한 인터페이스만 컴파일하면 되므로 클린 빌드 속도가 빨라집니다. 이 접근 방식은 SwiftRock의 [인터페이스 모듈을 사용하여 iOS 빌드 시간 단축](https://swiftrocks.com/reducing-ios-build-times-by-using-interface-targets)에서 영감을 얻었습니다. 인터페이스에 의존하는 것은 앱이 실행 시간에 구현의 그래프를 구성하고, 필요한 모듈에 해당 구현을 의존성 주입해야 합니다. TMA는 이를 어떻게 구현할지 강제하지 않지만, 빌드 시간에 불필요한 간접 참조를 추가하거나 이 목적을 위해 설계되지 않은 플랫폼 API를 사용하지 않는 의존성 주입 솔루션이나 패턴을 권장합니다. ## 결과물 타입 {#product-types} 모듈을 구축할 때, 타겟에 대한 **라이브러리와 프레임워크** 및 **정적과 동적 링킹** 중 선택할 수 있습니다. Tuist가 없다면 의존성 그래프를 수동으로 구성해야 하므로 이 결정을 내리는데 더 복잡합니다. 하지만 Tuist 프로젝트 덕분에 이건 아무런 문제가 되지 않습니다. 타겟의 라이브러리나 프레임워크 특성과 번들 접근 로직을 분리하기 위해 개발 중에는 번들 접근자를 사용하여 동적 라이브러리나 동적 프레임워크를 사용하길 권장합니다. 이것은 빠른 컴파일 시간과 [SwiftUI 프리뷰](https://developer.apple.com/documentation/swiftui/previews-in-xcode)가 잘 동작하기 위해 중요한 포인트입니다. 그리고 릴리즈 빌드에서 앱이 더 빠르게 실행되기 위해 정적 라이브러리나 정적 프레임워크를 사용하는 것이 좋습니다. <0>동적 구성을 활용하여 생성 시점에 결과물 타입을 변경할 수 있습니다: ```bash # You'll have to read the value of the variable from the manifest {#youll-have-to-read-the-value-of-the-variable-from-the-manifest} # and use it to change the linking type {#and-use-it-to-change-the-linking-type} TUIST_PRODUCT_TYPE=static-library tuist generate ``` ```swift // You can place this in your manifest files or helpers // and use the returned value when instantiating targets. func productType() -> Product { if case let .string(productType) = Environment.productType { return productType == "static-library" ? .staticLibrary : .framework } else { return .framework } } ``` > [!IMPORTANT] MERGEABLE LIBRARIES\ > Apple은 [mergeable libraries](https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries)를 도입하여 정적 라이브러리와 동적 라이브러리 간 변환의 번거로움을 줄이려고 했습니다. 하지만 이것은 빌드 시 항상 동일한 결과를 가져오지 않고 최적화가 어려워 지기 때문에 권장하지 않습니다. ## 코드 {#code} TMA는 모듈의 코드 아키텍처와 패턴에 대해 강요하지 않습니다. 하지만, 경험을 토대로 몇가지 팁을 공유하려고 합니다: - **컴파일러를 활용하는 것은 훌륭합니다.** 그러나 컴파일러를 과도하게 사용하면 비생산적이며 프리뷰와 같은 Xcode의 기능이 원할하게 동작하지 않을 수 있습니다. 우리는 컴파일러를 사용하여 좋은 코드작성 관행과 조기에 오류를 찾도록 권장하지만, 코드를 읽기 어렵고 유지 보수 하기 어렵도록 사용하는 것은 권장하지 않습니다. - **Swift Macros는 신중하게 사용해야 합니다.** 이것은 매우 강력하지만 코드를 읽기 어렵고 유지 보수 하기 어렵게 만들 수도 있습니다. - **플랫폼과 언어를 받아들이고, 추상화 하지 말아야 합니다.** 복잡한 추상화 계층을 만들면 오히려 비효율적일 수 있습니다. 플랫폼과 언어는 추가적인 추상화 계층없이도 훌륭한 앱을 만들기에 충분합니다. 좋은 프로그래밍과 좋은 설계 패턴을 참조하여 기능을 구축합니다. ## 리소스 {#resources} - [Building µFeatures](https://speakerdeck.com/pepibumur/building-ufeatures) - [Framework Oriented Programming](https://speakerdeck.com/pepibumur/framework-oriented-programming-mobilization-dot-pl) - [A Journey into frameworks and Swift](https://speakerdeck.com/pepibumur/a-journey-into-frameworks-and-swift) - [Leveraging frameworks to speed up our development on iOS - Part 1](https://developers.soundcloud.com/blog/leveraging-frameworks-to-speed-up-our-development-on-ios-part-1) - [Library Oriented Programming](https://academy.realm.io/posts/justin-spahr-summers-library-oriented-programming/) - [Building Modern Frameworks](https://developer.apple.com/videos/play/wwdc2014/416/) - [The Unofficial Guide to xcconfig files](https://pewpewthespells.com/blog/xcconfig_guide.html) - [Static and Dynamic Libraries](https://pewpewthespells.com/blog/static_and_dynamic_libraries.html) --- URL: "/ko/guides/develop/projects/templates" LLMS_URL: "/ko/guides/develop/projects/templates.md" title: "Templates" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "프로젝트에서 코드 생성을 위해 Tuist에서 템플릿을 생성하고 사용하는 방법을 배워봅니다." --- # Templates {#templates} 기존 아키텍처가 있는 프로젝트에서 개발자가 프로젝트와 일관성 있는 새로운 컴포넌트나 기능을 부트스트랩 하고 싶을 수 있습니다. `tuist scaffold`를 사용하면 템플릿에서 파일을 생성할 수 있습니다. 템플릿을 정의할 수도 있고, Tuist에서 제공하는 템플릿을 사용할 수도 있습니다. 스캐폴딩 (Scaffolding) 이 유용한 몇가지 시나리오가 있습니다: - 주어진 아키텍처를 따르는 새로운 기능을 생성: `tuist scaffold viper --name MyFeature`. - 새로운 프로젝트 생성: `tuist scaffold feature-project --name Home` > [!NOTE] NON-OPINIONATED > Tuist는 템플릿의 내용과 사용 목적에 대해 의견을 제시하지 않습니다. 특정 디렉토리에만 있으면 됩니다. ## 템플릿 정의 {#defining-a-template} 템플릿을 정의하려면, `tuist edit`를 수행하고 `Tuist/Templates` 아래에 템플릿을 나타내는 `name_of_template` 디렉토리를 생성합니다. 템플릿은 템플릿을 설명하는 `name_of_template.swift` 매니페스트 파일이 필요합니다. 따라서 `framework`라는 템플릿을 생성한다면, `Tuist/Templates` 아래에 `framework` 디렉토리를 생성하고 다음 내용을 가지는 `framework.swift` 매니페스트 파일을 생성해야 합니다: ```swift import ProjectDescription let nameAttribute: Template.Attribute = .required("name") let template = Template( description: "Custom template", attributes: [ nameAttribute, .optional("platform", default: "ios"), ], items: [ .string( path: "Project.swift", contents: "My template contents of name \(nameAttribute)" ), .file( path: "generated/Up.swift", templatePath: "generate.stencil" ), .directory( path: "destinationFolder", sourcePath: "sourceFolder" ), ] ) ``` ## 템플릿 사용 {#using-a-template} 템플릿을 정의한 후에, `scaffold` 명령어를 사용할 수 있습니다: ```bash tuist scaffold name_of_template --name Name --platform macos ``` > [!NOTE]\ > 플랫폼은 옵셔널 인수이므로, `--platform macos` 인수없이 명령어 호출도 가능합니다. `.string`과 `.files`가 유연성을 제공하지 않으면, `.file` 케이스를 통해 [Stencil](https://stencil.fuller.li/en/latest/) 템플릿 언어를 활용할 수 있습니다. 그 외에 여기에 정의된 필터를 추가적으로 사용할 수도 있습니다. 문자열 보간을 사용하면, 위에 `\(nameAttribute)`은 `{{ name }}`로 변환됩니다. 템플릿 정의에서 Stencil 필터를 사용하고 싶으면, 해당 보간을 수동으로 사용하고 원하는 필터를 추가할 수 있습니다. 예를 들어, 이름 속성을 소문자로 하려면 `\(nameAttribute)` 대신에 `{ { name | lowercase } }`을 사용하면 됩니다. `.directory`를 사용하면, 주어진 경로에 전체 폴더를 복사할 수 있습니다. > [!TIP] 프로젝트 설명 도우미 > 템플릿은 템플릿간의 코드 재사용을 위해 프로젝트 설명 도우미의 사용을 지원합니다. --- URL: "/ko/guides/develop/projects/synthesized-files" LLMS_URL: "/ko/guides/develop/projects/synthesized-files.md" title: "Synthesized files" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Tuist 프로젝트에서 자동으로 생성된 파일에 대해 배워봅니다." --- # Synthesized files {#synthesized-files} Tuist는 Xcode 프로젝트를 관리하고 작업할 때 편의성을 가지기 위해 생성 시점에 파일과 코드를 생성할 수 있습니다. 이 문서에서는 해당 기능에 대해 배우고 프로젝트에서 어떻게 사용하는지 배워봅니다. ## 타겟 리소스 {#target-resources} Xcode 프로젝트는 타겟에 리소스 추가를 지원합니다. 그러나 이것은 소스와 리소스를 자주 이동하는 모듈화된 프로젝트를 작업할 때 팀에 몇가지 과제를 제시합니다: - **일관되지 않은 런타인 접근**: 리소스가 최종 결과물에서 어떻게 포함되고 리소스에 접근하는 방식은 타겟 결과물에 따라 다릅니다. 예를 들어, 타겟이 애플리케이션인 경우, 리소스는 애플리케이션 번들에 복사됩니다. 이것으로 인해 리소스 접근하는 코드가 번들 구조에 대해 가정을 하게 되고, 이것은 코드의 이해를 어렵게 하고 리소스 이동을 복잡하게 만들기 때문에 이상적이지 않습니다. - **리소스를 지원하지 않는 결과물**: 정적 라이브러리와 같이 번들이 아닌 특정 제품은 리소스를 지원하지 않습니다. 그렇기 때문에 프로젝트나 앱에 오버헤드를 추가할 수 있는 프레임워크와 같은 다른 결과물 타입을 사용해야 할 수도 있습니다. 예를 들어, 정적 프레임워크는 최종 결과물에 정적으로 연결되고 최종 결과물에 리소스를 복사하기 위한 빌드 단계가 필요합니다. 또는 동적 프레임워크는 Xcode가 최종 결과물에 바이너리와 리소스를 모두 복사하지만 프레임워크를 동적으로 로드해야 하므로 앱의 시작 시간이 증가합니다. - **런타임 오류가 발생하기 쉬움**: 리소스는 이름과 확장자 (문자열) 로 구분됩니다. 따라서 이 중에 오타가 있으면 리소스에 접근할 때 런타임 오류가 발생합니다. 이 방법은 컴파일 시점에 발견되지 않아 이상적인 방법이 아니며, 릴리즈 때 크래시가 발생할 수 있습니다. Tuist는 구현 세부 사항을 추상화하여 **번들과 리소스에 접근하기 위한 통합된 인터페이스를 자동으로 생성**하여 위의 문제를 해결합니다. > [!IMPORTANT] 권장 > Tuist가 자동으로 생성하는 인터페이스를 통해 리소스에 접근하는 방식은 필수가 아니지만, 코드를 쉽게 추론할 수 있고 리소스 이동에 용이하므로 권장합니다. ## 리소스 {#resources} Tuist는 `info.plist`나 entitlement와 같은 파일의 내용을 Swift로 선언할 수 있는 인터페이스를 제공합니다. 이것은 타겟과 프로젝트의 일관성을 유지하고, 컴파일 시 문제를 파악하는데 유용합니다. 또한 내용을 모델링하기 위해 추상화를 만들어 이를 여러 타겟과 프로젝트에 공유할 수도 있습니다. 프로젝트가 생성될 때, Tuist는 해당 파일의 내용을 합성하여 프로젝트가 포함된 디렉토리를 기준으로 `Derived` 디렉토리에 작성합니다. > [!TIP] DERIVED 디렉토리 GITIGNORE > 프로젝트의 `.gitignore` 파일에 `Derived` 디렉토리를 추가하길 권장합니다. ## 번들 접근자 {#bundle-accessors} Tuist는 타겟 리소스를 포함하는 번들에 접근하기 위해 자동으로 인터페이스를 생성합니다. ### Swift {#swift} 타겟은 번들을 노출하는 `Bundle` 타입의 확장자를 포함합니다: ```swift let bundle = Bundle.module ``` ### Objective-C {#objectivec} Objective-C에서 번들에 접근하기 위해 `{Target}Resources` 인터페이스가 있습니다. ```objc NSBundle *bundle = [MyFeatureResources bundle]; ``` > [!TIP] 번들로 라이브러리 리소스 지원 > 예를 들어, 라이브러리와 같이 타겟 결과물이 리소스를 지원하지 않으면, Tuist는 `bundle` 타입의 타겟에 리소스를 포함시켜 최종 결과물에 포함시키고 인터페이스가 올바른 번들을 가리키도록 보장합니다. ## 리소스 접근자 {#resource-accessors} 리소스는 문자열을 사용하여 이름과 확장자로 구분됩니다. 이 방법은 컴파일 시점에 발견되지 않아 이상적인 방법이 아니며, 릴리즈 때 크래시가 발생할 수 있습니다. 이것을 방지하기 위해 Tuist는 [SwiftGen](https://github.com/SwiftGen/SwiftGen)을 프로젝트 생성 과정에 통합하여 리소스를 접근하기 위한 인터페이스를 자동으로 생성합니다. 덕분에 컴파일러를 활용하여 리소스 접근을 보장하고 문제를 파악할 수 있습니다. Tuist는 기본적으로 다음의 리소스에 대한 접근자를 자동으로 생성하기 위해 [템플릿](https://github.com/tuist/tuist/tree/main/Sources/TuistGenerator/Templates)을 포함합니다. | Resource type | Synthesized file | | ------------- | ------------------------ | | 이미지와 색상 | `Assets+{Target}.swift` | | Strings | `Strings+{Target}.swift` | | Plists | `{NameOfPlist}.swift` | | Fonts | `Fonts+{Target}.swift` | | Files | `Files+{Target}.swift` | > Note: 프로젝트 기준으로 리소스 접근자의 자동 생성을 비활성화 하려면 프로젝트 옵션에 `disableSynthesizedResourceAccessors` 옵션을 추가하면 됩니다. #### 사용자 정의 템플릿 {#custom-templates} [SwiftGen](https://github.com/SwiftGen/SwiftGen)이 지원하는 다른 리소스 타입에 대해 접근자를 템플릿으로 제공하려면 리소스의 카멜-케이스 이름으로 `Tuist/ResourceSynthesizers/{name}.stencil`을 생성할 수 있습니다. | Resource | Template name | | ---------------- | -------------------------- | | strings | `Strings.stencil` | | assets | `Assets.stencil` | | plists | `Plists.stencil` | | fonts | `Fonts.stencil` | | coreData | `CoreData.stencil` | | interfaceBuilder | `InterfaceBuilder.stencil` | | json | `JSON.stencil` | | yaml | `YAML.stencil` | | files | `Files.stencil` | 접근자에 리소스 타입의 목록을 구성하려면, `Project.resourceSynthesizers` 프로퍼티를 사용하여 사용하려는 리소스 타입을 넘겨주면 됩니다: ```swift let project = Project(resourceSynthesizers: [.string(), .fonts()]) ``` > [!NOTE] 참조 > 리소스 접근자를 자동 생성하기 위해 어떻게 사용자 정의된 템플릿을 사용하는지 확인하려면 [이 예제](https://github.com/tuist/tuist/tree/main/fixtures/ios_app_with_templates)에서 확인할 수 있습니다. --- URL: "/ko/guides/develop/projects/plugins" LLMS_URL: "/ko/guides/develop/projects/plugins.md" title: "Plugins" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Tuist에서 Plugin을 생성하고 사용하여 기능을 확장하는 방법을 알아보세요." --- # Plugins {#plugins} Plugin은 여러 프로젝트에서 Tuist 아티팩트를 공유하고 재사용할 수 있는 도구입니다. 지원되는 아티팩트는 다음과 같습니다: - Project description helpers를 여러 프로젝트에서 사용. - Templates을 여러 프로젝트에서 사용. - Tasks를 여러 프로젝트에서 사용. - Resource accessor 템플릿을 여러 프로젝트에서 사용. Plugin은 Tuist의 기능을 확장하기 위한 간단한 방법으로 설계되었습니다. 따라서 고려해야 할 **몇 가지 제한 사항이 있습니다.** - Plugin은 다른 Plugin에 의존할 수 없습니다. - Plugin은 서드파티 Swift 패키지에 의존할 수 없습니다. - Plugin은 Plugin을 사용하는 프로젝트에 project description helpers을 사용할 수 없습니다. 더 많은 유연성이 필요하다면, 도구에 대한 기능 제안을 하거나 Tuist의 생성 프레임워크인 [`TuistGenerator`](https://github.com/tuist/tuist/tree/main/Sources/TuistGenerator)를 기반으로 자체 솔루션을 구축하는 것을 고려해 보세요. ## Plugin types {#plugin-types} ### Project description helper plugin {#project-description-helper-plugin} Project description helper plugin은 Plugin의 이름을 선언하는 `Plugin.swift` 매니페스트 파일이 포함된 디렉토리와 helper Swift files이 포함된 `ProjectDescriptionHelpers` 디렉토리로 표시됩니다. ::: code-group ```bash [Plugin.swift] import ProjectDescription let plugin = Plugin(name: "MyPlugin") ``` ```bash [Directory structure] . ├── ... ├── Plugin.swift ├── ProjectDescriptionHelpers └── ... ``` ::: ### Resource accessor templates plugin {#resource-accessor-templates-plugin} synthesized Resource accessor를 공유해야 하는 경우, 이 유형의 plugin을 사용할 수 있습니다. 이 plugin은 plugin의 이름을 선언하는 `Plugin.swift` 매니페스트 파일이 포함된 디렉토리와 resource accessor 템플릿 파일이 포함된 `ResourceSynthesizer` 디렉토리로 표시됩니다. ::: code-group ```bash [Plugin.swift] import ProjectDescription let plugin = Plugin(name: "MyPlugin") ``` ```bash [Directory structure] . ├── ... ├── Plugin.swift ├── ResourceSynthesizers ├───── Strings.stencil ├───── Plists.stencil ├───── CustomTemplate.stencil └── ... ``` ::: 템플릿의 이름은 resource type의 [camel case](https://en.wikipedia.org/wiki/Camel_case) 버전입니다. | Resource type | Template file name | | ----------------- | ---------------------------------------- | | Strings | Strings.stencil | | Assets | Assets.stencil | | Property Lists | Plists.stencil | | Fonts | Fonts.stencil | | Core Data | CoreData.stencil | | Interface Builder | InterfaceBuilder.stencil | | JSON | JSON.stencil | | YAML | YAML.stencil | 프로젝트에서 resource synthesizers를 정의할 때, plugin의 템플릿을 사용하도록 plugin 이름을 지정할 수 있습니다. ```swift let project = Project(resourceSynthesizers: [.strings(plugin: "MyPlugin")]) ``` ### Task plugin {#task-plugin-badge-typewarning-textdeprecated-} > [!WARNING] DEPRECATED\ > Task 플러그인은 더 이상 사용되지 않습니다. 프로젝트 자동화 솔루션을 찾고 있다면 [블로그 포스트](https://tuist.dev/blog/2025/04/15/automation-in-swift-projects)를 참고하세요. Tasks는 `tuist--` 명명 규칙을 따를 경우 `tuist` 명령을 통해 호출할 수 있는 `$PATH` 실행 파일입니다. 이전 버전에서 Tuist는 Swift 패키지에서 실행 파일로 구성된 `build`, `run`, `test` 및 `archive` 작업에 `tuist plugin`에 따라 몇 가지 약한 규칙과 도구를 제공했지만, 유지 관리 부담과 도구의 복잡성을 증가시키기 때문에 이 기능은 더 이상 지원되지 않습니다. Tuist를 tasks 배포에 사용하고 있었다면, 자체 솔루션을 구축할 것을 권장합니다. - 프로젝트 그래프에 접근하려면 매 Tuist 릴리스에 포함된 `ProjectAutomation.xcframework`를 계속 사용할 수 있으며, `let graph = try Tuist.graph()`와 같은 방식으로 로직에서 그래프에 접근할 수 있습니다. 이 명령은 시스템 프로세스를 사용하여 `tuist` 명령을 실행하고, 프로젝트 그래프의 in-memory 표현을 반환합니다. - Tasks를 배포하려면, `arm64` and `x86_64`를 지원하는 fat binary를 GitHub 릴리스에 포함하고, 설치 도구로 [Mise](https://mise.jdx.dev) 를 사용하는 것을 권장합니다. Mise에 도구 설치 방법을 알려주려면, plugin repository가 필요합니다. [Tuist's](https://github.com/asdf-community/asdf-tuist) 를 참고할 수 있습니다. - 도구의 이름을 `tuist-{xxx}`로 지정하면, 사용자는 `mise install`을 실행하여 설치할 수 있으며, 이를 직접 호출하거나 `tuist xxx`를 통해 실행할 수 있습니다. > [!NOTE] 프로젝트 자동화의 미래 > 우리는 `ProjectAutomation`과 `XcodeGraph`의 모델을 하나의 하위 호환 프레임워크로 통합하여 프로젝트 그래프의 전체를 사용자에게 보여줄 계획입니다. 또한, 생성 로직을 새로운 레이어인 `XcodeGraph`로 분리하여 여러분의 CLI에서도 사용할 수 있도록 할 예정입니다. 자신만의 Tuist를 만든다고 생각하세요. ## Using plugins {#using-plugins} plugin을 사용하려면, 프로젝트의 `Tuist.swift` manifest 파일에 추가해야 합니다: ```swift import ProjectDescription let tuist = Tuist( project: .tuist(plugins: [ .local(path: "/Plugins/MyPlugin") ]) ) ``` repository에 있는 프로젝트들 간에 plugin을 재사용하려면, plugin을 Git repository에 push하고 `Tuist.swift` 파일에서 참조할 수 있습니다: ```swift import ProjectDescription let tuist = Tuist( project: .tuist(plugins: [ .git(url: "https://url/to/plugin.git", tag: "1.0.0"), .git(url: "https://url/to/plugin.git", sha: "e34c5ba") ]) ) ``` plugin을 추가한 후, `tuist install`을 실행하면 플러그인이 전역 캐시 디렉토리로 가져와집니다. > [!NOTE] 버전 해결 없음 > 눈치채셨겠지만, 우리는 plugins에 대한 버전 해결을 제공하지 않습니다. 재현 가능성을 보장하기 위해 Git 태그나 SHA를 사용하는 것을 권장합니다. > [!TIP] PROJECT DESCRIPTION HELPERS PLUGINS > project description helpers plugin을 사용할 때, helpers를 포함하는 모듈의 이름은 plugin의 이름과 같습니다. > > ```swift > import ProjectDescription > import MyTuistPlugin > let project = Project.app(name: "MyCoolApp", platform: .iOS) > ``` --- URL: "/ko/guides/develop/projects/manifests" LLMS_URL: "/ko/guides/develop/projects/manifests.md" title: "Manifests" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Tuist가 프로젝트와 워크스페이스를 정의하고 생성 프로세스를 구성하는데 사용하는 매니페스트 파일에 대해 알아봅니다." --- # Manifests {#manifests} Tuist는 프로젝트와 워크스페이스를 정의하고 생성 프로세스를 구성하는데 기본적으로 Swift 파일을 사용합니다. 이러한 파일은 이 문서에서 **매니페스트 파일**이라고 합니다. Swift를 사용하기로 결정한 것은 패키지를 정의하기 위해 Swift 파일을 사용하는 [Swift Package Manager](https://www.swift.org/documentation/package-manager/)에서 영감을 받았습니다. Swift를 사용하면, 컴파일러를 활용해 컨텐츠의 정확성을 검증하고 다른 매니페스트 파일에서 코드를 재사용할 수 있고 Xcode를 활용하여 구문 강조, 자동 완성, 그리고 검증으로 좋은 편집 환경을 제공합니다. > [!NOTE] 캐싱 > 매니페스트 파일은 컴파일 되어야 할 Swift 파일이므로, Tuist는 파싱 과정의 속도를 올리기 위해 결과를 캐시합니다. 그래서 Tuist를 처음 실행해 프로젝트를 생성할 때 조금 더 시간이 걸릴 수 있습니다. 이후 실행은 더 빨라 집니다. ## Project.swift {#projectswift} `Project.swift` 매니페스트는 Xcode 프로젝트를 선언합니다. 프로젝트는 `name` 프로퍼티에 지정된 이름으로 매니페스트 파일이 위치한 디렉토리에 생성됩니다. ```swift // Project.swift let project = Project( name: "App", targets: [ // .... ] ) ``` > [!WARNING] 루트 변수 > 매니페스트의 루트에 있어야 하는 변수는 `let project = Project(...)` 입니다. 매니페스트의 일부분을 코드에서 재사용 해야 된다면 Swift 함수를 사용할 수 있습니다. ## Workspace.swift {#workspaceswift} 기본적으로 Tuist는 생성된 프로젝트와 해당 프로젝트가 의존하고 있는 프로젝트를 포함하는 [Xcode Workspace](https://developer.apple.com/documentation/xcode/projects-and-workspaces)를 생성합니다. 워크스페이스에 프로젝트를 추가하거나 파일과 그룹을 포함하려면 `Workspace.swift` 매니페스트를 정의해서 사용할 수 있습니다. ```swift // Workspace.swift import ProjectDescription let workspace = Workspace( name: "App-Workspace", projects: [ "./App", // Path to directory containing the Project.swift file ] ) ``` > [!NOTE]\ > Tuist는 의존성 그래프를 해결하고 의존성의 프로젝트를 워크스페이스에 포함 시킵니다. 수동으로 포함 시킬 필요가 없습니다. 이는 의존성을 올바르게 해결하기 위해 빌드 시스템에 필요합니다. ### 다중 또는 단일 프로젝트 {#multi-or-monoproject} 자주 질문하는 내용 중에 하나는 워크스페이스에 단일 프로젝트를 사용할지 아니면 여러 프로젝트를 사용할지에 대한 것입니다. Tuist가 없다면 단일 프로젝트 설정으로 인해 Git 충돌이 자주 발생하므로 워크스페이스 사용을 권장합니다. 그러나 Tuist로 생성한 Xcode 프로젝트는 Git 리포지토리에 포함하는 것을 권장하지 않으므로 Git 충돌은 문제가 되지 않습니다. 따라서 워크스페이스에서 단일 프로젝트를 사용할지 여러 프로젝트를 사용할지는 여러분 결정에 달렸습니다. Tuist 프로젝트에서는 첫 생성 시간 (Cold generation time) 이 더 빠르고 (더 적은 매니페스트 파일을 컴파일 하기 때문) 프로젝트 설명 도우미를 캡슐화 단위로 사용하기 때문에 단일 프로젝트를 사용합니다. 그러나 애플리케이션에 다른 도메인을 나타내기 위해 캡슐화의 단위로 Xcode 프로젝트를 사용하면 Xcode에서 권장하는 프로젝트 구조와 더 일치합니다. ## Tuist.swift {#tuistswift} Tuist는 프로젝트 구성을 단순화 하기 위해 기본값 (Sensible defaults)을 제공합니다. 하지만 Tuist가 프로젝트의 루트를 결정하는데 사용되는 `Tuist.swift`를 정의하여 구성을 사용자화 할 수 있습니다. ```swift import ProjectDescription let tuist = Tuist( project: .tuist(generationOptions: .options(enforceExplicitDependencies: true)) ) ``` --- URL: "/ko/guides/develop/projects/inspect/implicit-dependencies" LLMS_URL: "/ko/guides/develop/projects/inspect/implicit-dependencies.md" title: "Implicit imports" titleTemplate: ":title · Inspect · Projects · Develop · Guides · Tuist" description: "Learn how to use Tuist to find implicit dependencies." --- # 암시적 임포트 {#implicit-imports} Apple은 순수 Xcode 프로젝트의 그래프 관리 복잡성을 줄이기 위해, 의존성을 암시적으로 정의할 수 있는 방식으로 빌드 시스템을 설계했습니다. 이는 앱과 같은 프로덕트가 의존성을 명시적으로 선언하지 않아도 프레임워크에 의존성을 가질 수 있다는 뜻입니다. 소규모 프로젝트에서는 문제없을 수 있지만, 프로젝트 그래프의 복잡성이 증가함에 따라 이러한 암시적인 방식 때문에 증분 빌드가 불안정해지거나 프리뷰, 코드 자동완성 같은 에디터 기능이 제대로 작동하지 않을 수 있습니다. 문제는 이러한 암시적 의존성의 발생을 막을 방법이 없다는 것입니다. 어떤 개발자든 `import`문을 Swift 코드에 추가할 수 있으며, 이를 통해 암시적 의존성이 생성되기 때문입니다. 이때 Tuist가 등장합니다. Tuist는 프로젝트 내 코드를 정적 분석하여 암시적 의존성을 검사할 수 있는 명령어를 제공합니다. 다음 명령어는 프로젝트의 암시적 종속성을 출력합니다: ```bash tuist inspect implicit-imports ``` 해당 명령어가 암시적 임포트를 발견할 경우, 0이 아닌 종료 코드와 함께 종료됩니다. > [!TIP] CI에서의 검증 > 새로운 코드가 upstream으로 push될 때마다 이 명령어를 CI(continuous intergration) 명령의 일부로 실행할 것을 강력히 권장합니다. > [!IMPORTANT] 모든 암시적 경우가 감지되는 것은 아닙니다. > Tuist는 암시적 의존성을 감지하기 위해 정적 코드 분석에 의존하므로, 모든 경우를 찾아내지 못할 수 있습니다. 예를 들어, Tuist는 코드에서 컴파일러 지시문(compiler directives)을 통한 조건부 임포트(conditional imports)를 이해할 수 없습니다. --- URL: "/ko/guides/develop/projects/hashing" LLMS_URL: "/ko/guides/develop/projects/hashing.md" title: "Hashing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "바이너리 캐싱과 선택적 테스트 기능의 기반이 되는 Tuist의 해싱 로직에 대해 배워봅니다." --- # Hashing {#hashing} 캐싱이나 스마트 테스트 실행과 같은 기능은 타겟이 변경되었는지 확인하는 방법이 필요합니다. Tuist는 타겟이 변경되었는지 확인하기 위해 의존성 그래프에서 각 타겟의 해시를 계산합니다. 해시는 다음의 속성을 기반으로 계산됩니다: - 타겟의 속성 (예: 이름, 플랫폼, 결과물 등) - 타겟의 파일 - 타겟 의존성의 해시 ### 캐시 속성 {#cache-attributes} 추가로 캐싱에 대한 해시를 계산할 때, 다음 속성도 해시합니다. #### Swift 버전 {#swift-version} `/usr/bin/xcrun swift --version` 명령어를 수행하여 얻은 Swift 버전을 해시하여, 타겟과 바이너리 간의 Swift 버전 불일치로 인한 컴파일 오류를 방지합니다. > [!NOTE] 모듈 안정성 > 이전 버전의 바이너리 캐싱은 `BUILD_LIBRARY_FOR_DISTRIBUTION` 빌드 설정에 의존하여 [모듈 안정성](https://www.swift.org/blog/library-evolution#enabling-library-evolution-support)을 활성화하고 모든 컴파일러 버전에서 바이너리를 사용할 수 있도록 합니다. 하지만 모듈 안정성을 지원하지 않는 타겟을 가지는 프로젝트에서 컴파일 문제가 발생합니다. 생성된 바이너리는 컴파일에 사용한 Swift 버전에 바인딩되고 Swift 버전은 프로젝트를 컴파일하는 버전과 일치해야 합니다. #### 구성 {#configuration} `-configuration` 플래그는 디버그 바이너리가 릴리즈 빌드에서 사용되지 않게 하고 릴리즈 바이너리가 디버그 빌드에서 사용되지 않게 하는 것입니다. 하지만 여전히 프로젝트에서 다른 구성을 제거하여 사용되지 않게 하는 메커니즘은 부족합니다. ## 디버깅 {#debugging} 환경이나 호출 간에 캐싱을 사용할 때 의도치 않은 동작이 발생한다면, 이것은 환경 간의 차이나 해싱 로직의 버그와 관련이 있을 수 있습니다. 문제를 디버그 하기 위해 다음의 동작을 권장합니다: 1. 모든 환경에서 동일한 [구성](#configuration) 과 [Swift 버전](#swift-version) 이 사용되었는지 확인합니다. 2. 두 번 연속으로 `tuist generate`를 호출하여 생성된 Xcode 프로젝트 간의 차이점이나 환경 간의 차이점을 확인합니다. 프로젝트를 비교하기 위해 `diff` 명령어를 사용할 수 있습니다. 생성된 프로젝트에는 해싱 로직이 의도치 않은 동작을 야기시키는 **절대 경로**가 포함될 수 있습니다. > [!NOTE] 디버깅 경험 개선 계획 > 디버깅 경험 개선은 로드맵에 포함되어 있습니다. 차이를 이해하기 어려운 print-hashes 명령어는 해시 간의 차이점을 트리 구조로 보여주는 사용자 친화적인 명령어로 대체될 예정입니다. --- URL: "/ko/guides/develop/projects/editing" LLMS_URL: "/ko/guides/develop/projects/editing.md" title: "Editing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Xcode의 빌드 시스템과 편집기 기능을 활용하여 프로젝트를 선언하기 위한 Tuist의 편집 워크플로우에 대해 배워봅니다." --- # Editing {#editing} Xcode의 UI를 통해 변경되는 기존 Xcode 프로젝트와 Swift Package와 달리, Tuist로 관리되는 프로젝트는 매니페스트 파일에 포함된 Swift 코드로 정의됩니다. Swift Package와 `Package.swift` 파일에 익숙하다면, 이 방식은 매우 유사합니다. 어떤 편집기로도 이 파일을 수정할 수 있지만, 우리는 Tuist에서 제공하는 워크플로우인 `tuist edit`를 사용하길 권장합니다. 이 워크플로우는 모든 매니페스트 파일을 포함하는 Xcode 프로젝트를 생성하고 이를 수정하고 컴파일 할 수 있도록 합니다. Xcode를 사용하면 **코드 완성, 구문 강조, 그리고 오류 검사**의 모든 이점을 얻을 수 있습니다. ## 프로젝트 수정하기 {#edit-the-project} 프로젝트를 수정하려면 Tuist 프로젝트 디렉토리 또는 그 하위 디렉토리에서 다음의 명령어를 수행해야 합니다: ```bash tuist edit ``` 이 명령어는 전역 디렉토리에 Xcode 프로젝트를 생성하고 Xcode에서 이 프로젝트를 엽니다. 프로젝트는 모든 매니페스트가 유효한지 확인하기 위해 빌드 할 수 있는 `Manifests` 디렉토리를 포함합니다. > [!INFO] GLOB-RESOLVED MANIFESTS\ > `tuist edit`는 프로젝트의 루트 디렉토리 (`Tuist.swift` 파일을 포함하는 디렉토리) 에서 glob `**/{Manifest}.swift`를 사용하여 포함될 매니페스트를 해결합니다. 프로젝트 루트에 유효한 `Tuist.swift`가 있는지 확인해야 합니다. ## 워크플로우 수정과 생성 {#edit-and-generate-workflow} 이미 알고 있듯이, 이미 생성된 Xcode 프로젝트는 편집이 불가능 합니다. 이것은 생성된 프로젝트가 Tuist에 의존 하지 않도록 설계되어 있으며, 나중에 Tuist를 쉽게 걷어낼 수 있도록 합니다. 프로젝트를 반복적으로 수정할 때, 터미널 세션에서 `tuist edit`를 수행하여 편집할 수 있는 Xcode 프로젝트를 열고, 다른 터미널 세션에서 `tuist generate`를 수행하길 권장합니다. --- URL: "/ko/guides/develop/projects/dynamic-configuration" LLMS_URL: "/ko/guides/develop/projects/dynamic-configuration.md" title: "Dynamic configuration" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "프로젝트를 동적 구성하기 위해 환경 변수를 사용하는 방법을 배워봅니다." --- # Dynamic configuration {#dynamic-configuration} 프로젝트 생성 시점에 프로젝트를 동적으로 구성 해야하는 경우가 있습니다. 예를 들어, 프로젝트가 생성되는 환경에 따라 앱 이름, 번들 식별자, 또는 배포 타겟을 변경해야 되는 경우가 있습니다. Tuist는 매니페스트 파일에서 접근될 수 있는 환경 변수를 통해 동적 구성을 지원합니다. ## 환경 변수를 통한 구성 {#configuration-through-environment-variables} Tuist는 매니페스트 파일에서 접근될 수 있는 환경 변수를 통해 구성을 전달합니다. 예를 들어: ```bash TUIST_APP_NAME=MyApp tuist generate ``` 여러 환경 변수를 전달하려면 공백으로 구분해서 전달하면 됩니다. 예를 들어: ```bash TUIST_APP_NAME=MyApp TUIST_APP_LOCALE=pl tuist generate ``` ## 매니페스트에서 환경 변수 읽기 {#reading-the-environment-variables-from-manifests} 변수는 `Environment` 타입을 사용하여 접근할 수 있습니다. `TUIST_XXX` 형식으로 환경에 정의되거나 명령어 수행 시 Tuist에 전달되면 `Environment` 타입을 사용하여 접근할 수 있습니다. 다음의 예제는 `TUIST_APP_NAME` 변수에 어떻게 접근하는지 보여줍니다: ```swift func appName() -> String { if case let .string(environmentAppName) = Environment.appName { return environmentAppName } else { return "MyApp" } } ``` 변수에 접근하면 다음의 값 중에 하나인 `Environment.Value?` 타입의 인스턴스를 반환합니다: | Case | Description | | ----------------- | ------------------------------------- | | `.string(String)` | 변수가 문자열을 나타낼 때 사용됩니다. | 아래 정의된 메서드 중 하나를 사용하여 문자열 또는 불리언 `Environment` 변수를 가져올 수 있으며, 이 메서드는 매번 일관된 결과를 얻을 수 있도록 기본값을 전달해야 합니다. 이것은 위에 정의된 appName() 함수를 정의할 필요성을 없애줍니다. ::: code-group ```swift [String] Environment.appName.getString(default: "TuistServer") ``` ```swift [Boolean] Environment.isCI.getBoolean(default: false) ``` ::: --- URL: "/ko/guides/develop/projects/directory-structure" LLMS_URL: "/ko/guides/develop/projects/directory-structure.md" title: "Directory structure" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Tuist 프로젝트의 구조와 이를 구성하는 방법에 대해 배워봅니다." --- # Directory structure {#directory-structure} Tuist 프로젝트는 일반적으로 Xcode 프로젝트를 대체하는데 사용하지만 이 용도로만 제한하지 않습니다. Tuist 프로젝트는 SPM 패키지, 템플릿, 플러그인, 그리고 작업과 같은 다른 종류의 프로젝트를 생성하는데 사용되기도 합니다. 이 문서에서는 Tuist 프로젝트의 구조와 이를 구성하는 방법에 대해 설명합니다. 다음 섹션에서는 템플릿, 플러그인, 그리고 작업을 정의하는 방법을 살펴봅니다. ## 표준 Tuist 프로젝트 {#standard-tuist-projects} Tuist 프로젝트는 **Tuist로 생성하는 가장 일반적인 프로젝트 입니다.** 이 프로젝트는 앱, 프레임워크, 그리고 라이브러리 등을 빌드하는데 사용됩니다. Xcode 프로젝트와 달리, Tuist 프로젝트는 더 유연하고 유지하기 쉬운 Swift로 정의되어 있습니다. Tuist 프로젝트는 이해하고 추론하기 쉽게 더 선언적입니다. 다음 구조는 Xcode 프로젝트를 생성하는 일반적인 Tuist 프로젝트를 보여줍니다. ```bash Tuist.swift Tuist/ Package.swift ProjectDescriptionHelpers/ Projects/ App/ Project.swift Feature/ Project.swift Workspace.swift ``` - **Tuist 디렉토리:** 이 디렉토리에는 두 가지 목적이 있습니다. 먼저, **프로젝트의 루트**를 나타냅니다. This allows constructing paths relative to the root of the project, and also running Tuist commands from any directory within the project. 두 번째로, 다음 파일을 포함하는 컨테이너 입니다: - **ProjectDescriptionHelpers:** 이 디렉토리는 모든 매니페스트 파일에서 공유되는 Swift 코드를 포함합니다. 매니페스트 파일은 이 디렉토리에 정의된 코드를 사용하기 위해 `import ProjectDescriptionHelpers`을 사용할 수 있습니다. 코드 공유는 프로젝트 전체의 중복을 피하고 일관성을 유지하는데 유용합니다. - **Package.swift:** 이 파일은 Tuist가 구성 가능하고 최적화할 수 있는 Xcode 프로젝트와 타겟 (예: [CocoaPods](https://cococapods)) 을 사용하여 통합하기 위한 Swift Package 의존성을 포함합니다. 여기서 더 알아봅니다. - **루트 디렉토리**: `Tuist` 디렉토리도 포함하는 프로젝트의 루트 디렉토리 입니다. - Tuist.swift: 이 파일은 모든 프로젝트, 워크스페이스, 그리고 환경에 공유되는 Tuist에 대한 구성을 포함합니다. 예를 들어, 스킴의 자동 생성을 비활성화 하거나, 프로젝트의 배포 타겟을 정의할 수 있습니다. - Workspace.swift: 이 매니페스트는 Xcode 워크스페이스를 나타냅니다. 다른 프로젝트를 그룹화 하고 파일과 스킴을 추가할 수도 있습니다. - Project.swift: 이 매니페스트는 Xcode 프로젝트를 나타냅니다. 프로젝트의 타겟과 의존성을 정의합니다. 위 프로젝트와 상호작용할 때 명령어는 작업 디렉토리나 `--path` 플래그로 나타낸 디렉토리에 `Workspace.swift` 또는 `Project.swift` 파일을 찾습니다. 이 매니페스트는 프로젝트의 루트 인 `Tuist` 디렉토리를 포함하는 디렉토리나 하위 디렉토리에 위치해야 합니다. > [!TIP] > Xcode 워크스페이스는 병합 충돌을 줄이기 위해 Xcode 프로젝트를 여러개로 나눌 수 있습니다. 이러한 목적이 워크스페이스를 사용하는 목적이라면, Tuist에서는 워크스페이스를 사용할 필요가 없습니다. Tuist는 프로젝트와 의존성을 가진 프로젝트를 포함해 워크스페이스를 자동으로 생성합니다. ## Swift Package {#swift-package-badge-typewarning-textbeta-} Tuist는 SPM 패키지 프로젝트도 지원합니다. SPM 패키지를 작업하고 있다면 아무런 업데이트가 필요하지 않습니다. Tuist는 자동으로 루트 `Package.swift`를 선택하고 Tuist의 모든 기능은 `Project.swift` 매니페스트인 것처럼 동작합니다. 시작하려면, SPM 패키지에서 `tuist install`과 `tuist generate`를 수행합니다. 이제 프로젝트는 Xcode SPM에서 볼 수 있는 동일한 스킴과 파일을 가져야 합니다. 이제 `tuist cache`도 수행할 수 있으며 대부분의 SPM 의존성과 모듈을 미리 컴파일 되어 후속 빌드가 매우 빠르게 진행됩니다. --- URL: "/ko/guides/develop/projects/dependencies" LLMS_URL: "/ko/guides/develop/projects/dependencies.md" title: "Dependencies" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Tuist 프로젝트에서 의존성을 선언하는 방법을 알아보세요." --- # 의존성 {#dependencies} 프로젝트 규모가 커지면 코드를 공유하고, 경계를 명확히 하며, 빌드 시간을 개선하기 위해 여러 타겟으로 나누는 것이 일반적입니다. 여러 target으로 나누게 되면 이들 사이의 의존 관계를 정의하여 **의존성 그래프**가 만들어지며, 여기에는 외부 의존성도 포함될 수 있습니다. ## XcodeProj-codified graphs {#xcodeprojcodified-graphs} Xcode와 XcodeProj의 설계 특성상 의존성 그래프를 관리하는 일은 번거롭고 실수하기 쉬운 작업이 될 수 있습니다. 발생할 수 있는 문제들을 예로 들어보면 다음과 같습니다: - Xcode의 빌드 시스템은 프로젝트의 모든 산출물을 derived data 내 동일한 디렉터리에 저장하기 때문에, 이로 인해 각 타겟이 원래 사용해서는 안 되는 다른 target의 산출물을 import할 수 있습니다. 클린 빌드가 더 흔하게 사용되는 CI 환경이나, 나중에 다른 구성을 사용할 때 컴파일이 실패할 수 있습니다. - 타겟의 전이적 동적 의존성들(transitive dynamic dependencies)은 `LD_RUNPATH_SEARCH_PATHS` 빌드 설정에 포함된 모든 디렉터리에 복사되어야 합니다. 이렇게 해당 의존성들이 복사되지 않으면, 타겟이 런타임에 의존성을 찾을 수 없습니다. 의존성 그래프가 간단할 때는 생각하고 설정하기 쉽지만, 그래프가 복잡해질수록 문제가 됩니다. - target이 정적 [XCFramework](https://developer.apple.com/documentation/xcode/creating-a-multi-platform-binary-framework-bundle)를 링크할 때, Xcode가 번들을 처리하고 현재 플랫폼과 아키텍처에 맞는 바이너리를 추출할 수 있도록 추가 빌드 페이즈(Build Phase)가 필요합니다. 이 build phase는 자동으로 추가되지 않으며, 추가하는 것을 쉽게 잊어버릴 수 있습니다. 위의 내용들은 몇 가지 예시에 불과하며, 우리는 수년간 이보다 더 많은 문제들을 겪어왔습니다. 의존성 그래프를 관리하고 유효성을 검증하기 위해 엔지니어 팀이 필요하다고 상상해보세요. 더 안 좋은 경우는, 제어하거나 커스터마이즈할 수 없는 빌드 시스템(closed-source build system)이 빌드 시점에 이러한 복잡한 세부 사항을 해결하는 경우입니다. 어디서 많이 들어본 것 같지 않나요? 이 방식은 Apple이 Xcode와 XcodeProj에서 채택한 접근 방식이며, Swift Package Manager도 그대로 채택하고 있습니다. 의존성 그래프는 반드시 **명시적**이고 **정적**이어야 합니다. 그래야 **검증**되고 **최적화**될 수 있기 때문이죠. Tuist와 함께라면, 의존 관계를 정의하는데만 집중하세요. 나머지는 저희가 알아서 처리할게요. 복잡한 세부 구현 사항들은 추상화되어 신경 쓸 필요가 없습니다. 다음 섹션에서는 프로젝트에서 의존성을 선언하는 방법을 알아보겠습니다. > [!TIP] 그래프 검증 > Tuist는 프로젝트를 생성할 때 그래프를 검증하여 순환이 없고 모든 의존성이 유효한지 확인합니다. 덕분에 어떤 팀이든 그래프가 깨질 걱정 없이 의존성 그래프를 발전시킬 수 있습니다. ## 로컬 의존성 {#local-dependencies} Target은 같은 프로젝트나 다른 프로젝트의 타겟, 그리고 바이너리에 의존할 수 있습니다. `Target`을 생성할 때, `dependencies ` 아규먼트에 다음과 같은 옵션들을 전달할 수 있습니다: - `Target`: 같은 프로젝트에 있는 타겟을 의존성으로 선언합니다. - `Project`: 다른 프로젝트에 있는 타겟을 의존성으로 선언합니다. - `Framework`: 바이너리 프레임워크에 대한 의존성을 선언합니다. - `Library`: 바이너리 라이브러리에 대한 의존성을 선언합니다. - `XCFramework`: 바이너리 XCFramework에 대한 의존성을 선언합니다. - `SDK`: 시스템 SDK에 대한 의존성을 선언합니다. - `XCTest`: XCTest에 대한 의존성을 선언합니다. > [!NOTE] 의존성 조건 > 모든 의존성 유형은 플랫폼에 따라 의존성을 조건부로 연결하기 위한 `condition` 옵션을 허용합니다. 기본적으로, 타겟이 지원하는 모든 플랫폼에 대해 의존성이 연결됩니다. ## 외부 의존성 {#external-dependencies} Tuist는 프로젝트에서 외부 의존성을 선언할 수 있습니다. ### Swift Packages {#swift-packages} Swift Packages는 프로젝트에서 의존성을 선언하는 권장 방법입니다. Xcode의 기본 통합 메커니즘을 사용하거나 Tuist의 XcodeProj 기반 통합을 통해 이를 통합할 수 있습니다. #### Tuist의 XcodeProj 기반 통합 {#tuists-xcodeprojbased-integration} Xcode의 기본 통합이 가장 편리하긴 하지만, 중간 규모 및 대형 프로젝트에서 필요한 유연성과 제어 기능이 부족합니다. 이를 극복하기 위해 Tuist는 XcodeProj 기반 통합을 제공하여 XcodeProj의 target을 사용해 프로젝트에 Swift 패키지를 통합할 수 있도록 합니다. 덕분에 통합에 대한 더 많은 제어를 제공할 뿐만 아니라, 캐싱스마트 테스트 수행과 같은 워크플로우와도 호환될 수 있습니다. XcodeProj의 통합은 새로운 Swift Package 기능을 지원하거나 더 많은 Package 구성을 처리하는데 시간이 더 걸릴 가능성이 큽니다. 하지만 Swift Packages와 XcodeProj target 간의 매핑 로직은 오픈소스이며, 커뮤니티에서 기여할 수 있습니다. 이는 Apple이 관리하는 비공개 소스인 Xcode의 기본 통합 방식과는 대조됩니다. 외부 의존성을 추가하려면 `Tuist/` 디렉터리나 프로젝트 루트에 `Package.swift` 파일을 생성해야 합니다. ::: code-group ```swift [Tuist/Package.swift] // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription import ProjectDescriptionHelpers let packageSettings = PackageSettings( productTypes: [ "Alamofire": .framework, // default is .staticFramework ] ) #endif let package = Package( name: "PackageName", dependencies: [ .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), ], targets: [ .binaryTarget( name: "Sentry", url: "https://github.com/getsentry/sentry-cocoa/releases/download/8.40.1/Sentry.xcframework.zip", checksum: "db928e6fdc30de1aa97200576d86d467880df710cf5eeb76af23997968d7b2c7" ), ] ) ``` ::: > [!TIP] PACKAGE SETTINGS > 컴파일러 지시문으로 감싼 `PackageSettings` 인스턴스를 통해 패키지 통합 방식을 설정할 수 있습니다. 예를 들어, 위 예시에서는 packages에 사용되는 기본 product type을 재정의하는 데 사용됩니다. 기본적으로는, 필요하지 않을 것입니다. `Package.swift` 파일은 외부 의존성을 선언하기 위한 인터페이스일 뿐, 그 외의 역할은 하지 않습니다. 그래서 package에는 target이나 product를 정의하지 않습니다. 의존성을 정의한 후에는, 다음 명령어를 실행하여 의존성을 `Tuist/Dependencies` 디렉터리에 설정하고 가져올 수 있습니다. ```bash tuist install # Resolving and fetching dependencies. {#resolving-and-fetching-dependencies} # Installing Swift Package Manager dependencies. {#installing-swift-package-manager-dependencies} ``` 눈치채셨겠지만, 저희는 [CocoaPods](https://cocoapods.org)'처럼 의존성 해석을 별도의 명령어로 분리하는 방식을 채택했습니다. 이렇게 하면 사용자가 원하는 시점에 의존성을 해석하고 업데이트할 수 있으며, Xcode에서 프로젝트를 열었을 때 바로 컴파일할 수 있는 상태가 됩니다. 이는 프로젝트가 커질수록 Apple이 제공하는 Swift Package Manager 통합 방식에서 개발자 경험이 저하되는 부분입니다. 프로젝트의 타겟에서 `TargetDependency.external` 의존성 타입을 사용하여 이러한 의존성을 참조할 수 있습니다: ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "App", organizationName: "tuist.io", targets: [ .target( name: "App", destinations: [.iPhone], product: .app, bundleId: "io.tuist.app", deploymentTargets: .iOS("13.0"), infoPlist: .default, sources: ["Targets/App/Sources/**"], dependencies: [ .external(name: "Alamofire"), // [!code ++] ] ), ] ) ``` ::: > [!NOTE] 외부 패키지에 대한 scheme이 생성되지 않음 > Swift Package 프로젝트의 scheme 목록을 깔끔하게 유지하기 위해 **scheme**이 자동으로 생성되지 않습니다. Xcode의 UI를 통해 생성할 수 있습니다. #### Xcode의 기본 통합 {#xcodes-default-integration} Xcode의 기본 통합 메커니즘을 사용하려면 프로젝트를 생성할 때 `packages` 목록을 전달하면 됩니다: ```swift let project = Project(name: "MyProject", packages: [ .remote(url: "https://github.com/krzyzanowskim/CryptoSwift", requirement: .exact("1.8.0")) ]) ``` 그런 다음 target에서 참조하면 됩니다. ```swift let target = .target(name: "MyTarget", dependencies: [ .package(product: "CryptoSwift", type: .runtime) ]) ``` Swift Macro와 Build Tool Plugin의 경우 각각 `.macro`와 `.plugin` type을 사용해야 합니다. > [!WARNING] SPM Build Tool Plugins > Tuist의 [XcodeProj 기반 통합](#tuist-s-xcodeproj-based-integration)을 사용해 프로젝트의 의존성을 관리하더라도, SPM build tool plugin은 반드시 [Xcode의 기본 통합](#xcode-s-default-integration) 메커니즘을 통해 선언해야 합니다. SPM 빌드 도구 플러그인의 실용적인 활용 사례는 Xcode의 'Run Build Tool Plug-ins' Build Phase에서 코드 린팅을 수행하는 것입니다. package manifest에서는 다음과 같이 정의됩니다: ```swift // swift-tools-version: 5.9 import PackageDescription let package = Package( name: "Framework", products: [ .library(name: "Framework", targets: ["Framework"]), ], dependencies: [ .package(url: "https://github.com/SimplyDanny/SwiftLintPlugins", .upToNextMajor(from: "0.56.1")), ], targets: [ .target( name: "Framework", plugins: [ .plugin(name: "SwiftLint", package: "SwiftLintPlugin"), ] ), ] ) ``` build tool plugin이 포함된 Xcode 프로젝트를 생성하려면, 프로젝트 manifest의 `packages` 배열에 package를 선언하고, target의 dependencies에 `.plugin` 타입의 package를 포함해야 합니다. ```swift import ProjectDescription let project = Project( name: "Framework", packages: [ .remote(url: "https://github.com/SimplyDanny/SwiftLintPlugins", requirement: .upToNextMajor(from: "0.56.1")), ], targets: [ .target( name: "Framework", dependencies: [ .package(product: "SwiftLintBuildToolPlugin", type: .plugin), ] ), ] ) ``` ### Carthage {#carthage} [Carthage](https://github.com/carthage/carthage)는 `frameworks` 또는 `xcframeworks`를 생성하므로, `carthage update` 명령어를 실행해 `Carthage/Build` 디렉토리에 의존성들을 생성한 후, `.framework` 또는 `.xcframework` target dependency type을 사용하여 대상에서 의존성을 선언할 수 있습니다. 이 과정을 다음과 같이 스크립트로 작성하여 프로젝트 생성 전에 실행할 수 있습니다. ```bash #!/usr/bin/env bash carthage update tuist generate ``` > [!WARNING] 빌드 및 테스트 > `tuist build`와 `tuist test`를 통해 프로젝트를 빌드하고 테스트하는 경우, `tuist build` 또는 `tuist build`를 실행하기 전에 `carthage update` 명령어를 실행하여 Carthage로 해결된 의존성들이 존재하는지 확인해야 합니다. ### CocoaPods {#cocoapods} [CocoaPods](https://cocoapods.org)은 의존성을 통합하기 위해 Xcode 프로젝트가 필요합니다. Tuist를 사용하여 프로젝트를 생성한 후, `pod install` 명령어를 실행하여 프로젝트와 Pods 의존성이 포함된 workspace를 생성함으로써 의존성을 통합할 수 있습니다. 이 과정을 다음과 같이 스크립트로 작성하여 프로젝트 생성 전에 실행할 수 있습니다. ```bash #!/usr/bin/env bash tuist generate pod install ``` > [!WARNING] > CocoaPods 의존성은 프로젝트 생성 직후 `xcodebuild`를 실행하는 `build` 또는 `test`와 같은 workflow와 호환되지 않습니다. 또한, Pods 의존성을 fingerprinting logic에서 고려하지 않기 때문에, binary caching 및 selective testing과도 호환되지 않습니다. ## Static or dynamic {#static-or-dynamic} Framework와 Library는 정적(static) 또는 동적(dynamic)으로 링크할 수 있으며, **이는 앱 크기와 실행 시간과 같은 부분에 크게 영향을 미칩니다.** 이것은 중요한 결정임에도 불구하고, 대부분은 깊이 고려되지 않고 선택됩니다. 이것은 중요한 결정임에도 불구하고, 대부분은 깊이 고려되지 않고 선택됩니다. **일반적인 규칙**은 빠른 실행 시간을 위해 릴리즈 빌드에서는 최대한 많은 항목을 정적으로 링크하고, 빠른 반복 작업을 위해 디버그 빌드에서는 최대한 많은 항목을 동적으로 링크하는 것입니다. Xcode에서 프로젝트 그래프의 링크 방식(static <-> dynamic)을 변경하는 것은 전체 그래프에 영향을 미치기 때문에 간단하지 않습니다 (예: 라이브러리는 리소스를 포함할 수 없고, 정적 프레임워크는 임베드가 불필요함). Apple은 Swift Package Manager의 정적 및 동적 링크 자동 결정이나 [Mergeable Libraries](https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries)와 같은 컴파일 타임 솔루션을 통해 이 문제를 해결하려고 했습니다. 그러나, 이는 컴파일 그래프에 새로운 동적 변수들을 추가하여 비결정적 요소를 증가시키며, Swift Previews와 같이 컴파일 그래프에 의존하는 기능들이 불안정해질 가능성을 높입니다. 다행히도, Tuist는 정적 및 동적 링크 간의 변경과 관련된 복잡성을 개념적으로 단순화하고, 링크 타입과 관계없이 표준화된 bundle accessors를 생성합니다. 환경 변수를 통한 동적 구성과 함께 사용하면 호출 시점에 링크 타입을 전달할 수 있으며, 이 값을 manifest에서 사용해 target의 product 타입을 설정할 수 있습니다. ```swift // Use the value returned by this function to set the product type of your targets. func productType() -> Product { if case let .string(linking) = Environment.linking { return linking == "static" ? .staticFramework : .framework } else { return .framework } } ``` Tuist는 비용 문제로 인해 암시적 구성(implicit configuration)을 통한 편의성을 기본값으로 제공하지 않는 점을 참고하세요. 이는 최종 바이너리가 올바르게 생성되기 위해 사용자가 직접 링크 타입과 [`-ObjC` linker flag](https://github.com/pointfreeco/swift-composable-architecture/discussions/1657#discussioncomment-4119184) 같은 추가 빌드 설정을 해야한다는 뜻입니다. 따라서, 우리는 주로 문서 형태의 자료를 제공하여 사용자가 올바른 결정을 내릴 수 있도록 돕는 방식을 취하고 있습니다. > [!TIP] 예시: COMPOSABLE ARCHITECTURE > 많은 프로젝트에서 사용하는 Swift Package로는 [Composable Architecture](https://github.com/pointfreeco/swift-composable-architecture)가 있습니다. [여기](https://github.com/pointfreeco/swift-composable-architecture/discussions/1657#discussioncomment-4119184)와 [troubleshooting section](#troubleshooting)에 설명된 대로, package를 정적으로 링크할 때는 `OTHER_LDFLAGS` 빌드 설정을 `$(inherited) -ObjC`로 설정해야 합니다. Tuist의 기본 링크 방식이 정적 링크이기 때문입니다. 다른 방법으로는, package의 product type을 동적으로 override할 수 있습니다. ### 시나리오 {#scenarios} 링크 방식을 전부 정적 또는 동적으로만 설정하는 것이 불가능하거나 적절하지 않은 경우가 있습니다. 다음은 정적 및 동적 링크를 혼합해야 할 수 있는 상황들의 예입니다: - **확장 기능이 포함된 앱:** 앱과 확장 기능이 코드를 공유해야 하기 때문에, target들을 동적으로 만들어야할 수 있습니다. 그렇지 않으면, 동일한 코드가 앱과 확장 기능 모두에 중복되어 바이너리 크기가 커지게 됩니다. - **사전에 컴파일된 외부 의존성**: 때로는 정적 또는 동적으로 미리 컴파일된 바이너리가 제공되기도 합니다. 정적 바이너리는 동적으로 링크하기 위해 동적 프레임워크나 라이브러리로 감쌀 수 있습니다. 그래프를 변경할 때, Tuist는 이를 분석하여 "static side effect"를 감지하면 경고를 표시합니다. 이 경고는 동적 target을 통해 정적 target에 전이적으로 의존하는 target을 정적으로 링크할 때 발생할 수 있는 문제를 식별하는 데 도움을 줍니다. 이러한 side effect는 종종 바이너리 크기 증가로 나타나거나, 최악의 경우 런타임 크래시가 발생할 수 있습니다. ## 문제 해결 {#troubleshooting} ### Objective-C 의존성 {#objectivec-dependencies} Objective-C 의존성을 통합할 때, [Apple Technical Q&A QA1490](https://developer.apple.com/library/archive/qa/qa1490/_index.html)에서 자세히 설명된 대로 런타임 크래시를 방지하기 위해 사용하는 target에 특정 flag를 포함해야 할 수 있습니다. 빌드 시스템과 Tuist는 flag가 필요한지여부를 추론할 수 없고, 이 flag가 잠재적으로 원치 않는 side effect를 발생시킬 수 있기 때문에, Tuist는 이러한 플래그들을 자동으로 적용하지 않습니다. 또한, Swift Package Manager는 `-ObjC`를 `.unsafeFlag`를 통해 포함되는 것으로 간주하기 때문에, 대부분의 package는 필요한 경우에도 이를 기본 링크 설정의 일부로 포함할 수 없습니다. Objective-C 의존성(또는 내부 Objective-C target)을 사용하는 target은 필요할 경우 `OTHER_LDFLAGS`에 `-ObjC` 또는 `-force_load` flag를 설정하여 적용해야 합니다. ### Firebase & Other Google Libraries {#firebase-other-google-libraries} Google의 오픈 소스 라이브러리는 강력하지만, 종종 일반적이지 않은 아키텍처와 기술로 빌드되기 때문에 Tuist에 통합하기 어려울 수 있습니다. 다음은 Firebase와 Google의 다른 Apple 플랫폼 라이브러리들을 통합하기 위해 필요할 수 있는 몇 가지 팁입니다: #### `OTHER_LDFLAGS`에 `-ObjC`가 추가되었는지 확인하세요 {#ensure-objc-is-added-to-other_ldflags} Google의 라이브러리 중 다수는 Objective-C로 작성되어 있습니다. 이로 인해, 사용하는 모든 target은 `OTHER_LDFLAGS` 빌드 설정에 `-ObjC` flag를 포함해야 합니다. 이는 `.xcconfig` 파일에서 설정하거나 Tuist manifest 파일에서 내의 target 설정에서 직접 지정할 수 있습니다. 예시: ```swift Target.target( ... settings: .settings( base: ["OTHER_LDFLAGS": "$(inherited) -ObjC"] ) ... ) ``` 자세한 내용은 [Objective-C Dependencies](#objective-c-dependencies) 섹션을 참고하세요. #### `FBLPromises` product type을 동적 프레임워크로 설정하기 {#set-the-product-type-for-fblpromises-to-dynamic-framework} 일부 Google 라이브러리는 Google의 또 다른 라이브러리인 `FBLPromises`에 의존합니다. `FBLPromises`와 관련된 다음과 같은 크래시가 발생할 수 있습니다: ``` NSInvalidArgumentException. Reason: -[FBLPromise HTTPBody]: unrecognized selector sent to instance 0x600000cb2640. ``` `Package.swift` 파일에서 `FBLPromises`의 product type을 `.framework`로 명시적으로 설정하면 이 문제가 해결될 것입니다: ```swift [Tuist/Package.swift] // swift-tools-version: 5.10 import PackageDescription #if TUIST import ProjectDescription import ProjectDescriptionHelpers let packageSettings = PackageSettings( productTypes: [ "FPLPromises": .framework, ] ) #endif let package = Package( ... ``` ### `.swiftmodule`에서 발생하는 전이적 정적 의존성 문제 {#transitive-static-dependencies-leaking-through-swiftmodule} 동적 프레임워브나 라이브러리가 `import StaticSwiftModule`을 통해 정적 라이브러리에 의존하는 경우, 해당 심볼이 동적 프레임워크나 라이브러리의 `.swiftmodule`에 포함되어 [컴파일 실패를 유발할 가능성](https://forums.swift.org/t/compiling-a-dynamic-framework-with-a-statically-linked-library-creates-dependencies-in-swiftmodule-file/22708/1)이 있습니다. 이를 방지하기 위해서는 [`@_implementationOnly`](https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_implementationonly)를 사용하여 정적 의존성을 import해야 합니다: ```swift @_implementationOnly import StaticModule ``` --- URL: "/ko/guides/develop/projects/cost-of-convenience" LLMS_URL: "/ko/guides/develop/projects/cost-of-convenience.md" title: "The cost of convenience" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Xcode에서 편의성의 비용에 대해 알아보고 Tuist는 이 문제를 어떻게 예방하는지 배워봅니다." --- # The cost of convenience {#the-cost-of-convenience} **작은 프로젝트에서 대규모 프로젝트까지** 사용할 수 있는 코드 편집기를 설계하는 것은 어려운 작업입니다. 이 문제를 해결하기 위해 많은 툴은 솔루션을 계층화하고 확장성을 제공하는 방식으로 접근합니다. 최하위 계층은 매우 저수준이며 기본 빌드 시스템과 밀접하게 연결되어 있고, 최상위 계층은 사용하기 편리하지만 유연성이 떨어지는 고수준 추상화 입니다. 이렇게 함으로써, 간단한 것은 쉽게 만들고 그 외 모든 것을 가능하게 만듭니다. 그러나, **[Apple](https://www.apple.com)은 Xcode에 다른 접근 방식을 취하기로 결정했습니다**. 그 이유는 명확하지 않지만, 대규모 프로젝트에 대해 최적화 하는 것이 목표가 아니었을 확률이 큽니다. 그들은 소규모 프로젝트에 대한 편리성에 과도하게 투자하고, 유연성은 거의 제공하지 않으며, 기본 빌드 시스템과 툴을 강하게 결합시켰습니다. 편리함을 제공하기 위해, Apple은 쉽게 대체할 수 있는 기본 설정을 제공하고, 대규모 프로젝트에서 문제를 일으키는 암시적인 빌드 타임 해석 동작을 추가했습니다. ## 명시성과 규모 {#explicitness-and-scale} 대규모 작업을 할 때, **명시성은 핵심입니다**. 명시성은 빌드 시스템이 사전에 프로젝트 구조와 의존성을 분석하고 이해하도록 하며, 그렇지 않으면 불가능한 최적화 작업을 수행합니다. 동일한 명시성은 [SwiftUI 프리뷰](https://developer.apple.com/documentation/swiftui/previews-in-xcode)나 [Swift Macros](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros/)와 같은 편집기 기능이 신뢰할 수 있고 예측 가능한 방식으로 동작하도록 보장하는데도 핵심입니다. Xcode와 Xcode 프로젝트는 편리성을 위해 암시성을 유효한 설계로 채택했기 때문에, Swift Package Manager도 이 원칙을 계승하였으며, Xcode를 사용할 때의 어려움이 Swift Package Manager에서도 나타납니다. > [!INFO] TUIST의 역할 > Tuist의 역할은 프로젝트의 암시적 정의를 방지하고 명시성을 활용해 더 나은 개발자 경험 (예: 검증, 최적화) 을 제공하는 툴로 요약할 수 있습니다. [Bazel](https://bazel.build)과 같은 툴은 이를 한단계 더 발전시켜 빌드 시스템 수준까지 확장합니다. 이 문제는 커뮤니티에서 거의 언급이 되지 않지만, 중요한 문제입니다. Tuist를 작업하면서, 많은 조직과 개발자들이 현재 직면한 문제를 [Swift Package Manager](https://www.swift.org/documentation/package-manager/)에 의해 해결할 수 있다고 생각하는 것을 발견했지만, 깨닫지 못하는 점은 Swift Package Manager도 동일한 원칙을 기반으로 구축되기 때문에, 잘 알려진 Git 충돌은 해결할 수 있지만, 다른 영역에서 개발자의 경험을 저하시키고 프로젝트 최적화를 어렵게 만든다는 점입니다. 다음 섹션에서 암시적 방식이 개발자 경험과 프로젝트에 어떤 영향을 끼치는지 실제 예제를 통해 다룰 예정입니다. 이 내용이 모든 것을 다루지는 않지만, Xcode 프로젝트나 Swift Package를 작업할 때 직면하는 문제에 대한 해결책을 위한 좋은 아이디어를 제시할 것입니다. ## 편리함이 장애물이 되는 경우 {#convenience-getting-in-your-way} ### 공유된 빌드 결과물 디렉토리 {#shared-built-products-directory} Xcode는 Derived Data 디렉토리 내에 각 결과물의 디렉토리를 사용합니다. 그 안에는 컴파일된 바이너리, dSYM 파일, 그리고 로그와 같은 빌드 산출물이 저장됩니다. 프로젝트의 모든 결과물이 동일한 디렉토리에 저장되며, 기본적으로 다른 타겟에서 보이기 때문에, **타겟들이 서로 암시적으로 의존성을 가질 수 있습니다.** 타겟이 적을 경우 문제가 되지 않지만, 프로젝트가 커지면 이것은 빌드 실패로 이어지고 디버깅하기 어려운 상황이 생길 수 있습니다. 이 설계의 결과로 많은 프로젝트는 명확하게 정의되지 않은 그래프로 컴파일 됩니다. > [!TIP] TUIST의 암시적 의존성 감지 > Tuist는 암시적 의존성을 감지하기 위한 명령어를 제공합니다. 해당 명령어를 사용하여 모든 의존성이 명시적으로 선언되었는지 CI에서 검증할 수 있습니다. ### 스킴에서 암시적 의존성 찾기 {#find-implicit-dependencies-in-schemes} 프로젝트 규모가 커질수록 Xcode에서 의존성 그래프를 정의하고 유지하기 어려워집니다. 어려운 이유는 빌드 단계와 빌드 설정이 `.pbxproj` 파일에 코드화 되어 있고, 그래프를 시각화하고 작업할 수 있는 툴이 없으며, 그래프의 변경 사항 (예: 미리 컴파일된 새로운 동적 프레임워크 추가) 이 상위 구성에서의 변경 (예: 번들에 프레임워크를 복사하기 위한 새로운 빌드 단계 추가) 을 요구할 수 있기 때문입니다. Apple은 어느 시점에 그래프 모델을 더 관리하기 쉬운 형태로 발전시키는 대신에, 빌드 시에 암시적 의존성을 해결하는 옵션을 추가하는 것이 더 합리적이라고 결정하였습니다. 이는 다시 의문점을 남기는데, 빌드 시간이 더 길어지거나 예측할 수 없는 빌드 결과가 나올 수 있기 때문입니다. 예를 들어, 빌드가 로컬에서는 Derive Data의 특정 상태 때문에 정상 동작할 수 있지만, 이 상태는 [singleton](https://en.wikipedia.org/wiki/Singleton_pattern)처럼 동작하므로, 상태가 다른 CI에서는 컴파일이 실패할 수 있습니다. > [!TIP] > 우리는 프로젝트 스킴에서 이 기능을 비활성화 하고, 의존성 그래프 관리가 용이한 Tuist 같은 툴을 사용하길 권장합니다. ### SwiftUI 프리뷰와 정적 라이브러리/프레임워크 {#swiftui-previews-and-static-librariesframeworks} SwiftUI 프리뷰나 Swift Macro와 같은 일부 편집기 기능은 수정된 파일에 의존성 그래프의 컴파일을 필요로 합니다. 편집기의 이런 통합은 빌드 시스템이 모든 암시성을 해결하도록 요구하고 해당 기능이 제대로 동작하도록 올바른 산출물을 출력하도록 요구합니다. 당연히 **그래프가 더 암시적이면 빌드 시스템의 작업은 더 어려워지고**, 이러한 기능 대부분이 제대로 동작하지 않는 것은 놀라운 일이 아닙니다. 개발자들이 SwiftUI 프리뷰가 제대로 동작하지 않아 오래전에 사용을 중지했다는 얘기를 자주 듣습니다. 대신 해당 기능을 사용하기 위해 예제 앱을 사용하거나, 정적 라이브러리나 스크립트 빌드 단계를 사용하는 것을 피해 기능을 사용하고 있습니다. ### Mergeable libraries {#mergeable-libraries} 작업을 더 유연하고 쉽게 하는 동적 프레임워크는 앱의 실행 시간에 안좋은 영향을 줍니다. 반면에, 정적 라이브러리는 더 빠른 실행 시간을 가지지만, 컴파일 시간에 영향을 주고 복잡한 그래프 환경에서 작업하기 어렵게 만듭니다. _구성에 따라 둘 중 하나로 변경할 수 있다면 좋지 않을까요?_ 아마 Apple은 Mergeable libraries 작업을 하면서 그 생각을 가졌을 것입니다. 하지만 다시 한 번 Apple은 더 많은 빌드 시간 추론을 빌드 시간으로 옮겼습니다. 의존성 그래프에 대해 추론해야 한다면 타겟의 정적 또는 동적 특성이 일부 타겟의 빌드 설정을 기반으로 빌드 시간이 결정된다고 상상해 봅시다. SwiftUI 프리뷰 기능이 안정적으로 동작하게 하는 것은 쉽지 않습니다. **많은 사용자가 Mergeable libraries를 사용하기 위해 Tuist를 찾지만 우리의 대답은 항상 같습니다. 그럴 필요가 없습니다.** 생성 시점에 타겟의 정적 또는 동적 특성을 제어할 수 있으며, 이를 통해 컴파일 전에 의존성 그래프를 미리 알 수 있는 프로젝트를 만들 수 있습니다. 빌드 시점에 해결해야 될 변수는 없습니다. ```bash # The value of TUIST_DYNAMIC can be read from the project {#the-value-of-tuist_dynamic-can-be-read-from-the-project} # to set the product as static or dynamic based on the value. {#to-set-the-product-as-static-or-dynamic-based-on-the-value} TUIST_DYNAMIC=1 tuist generate ``` ## 명시적, 명시적, 그리고 명시적 {#explicit-explicit-and-explicit} Xcode로 개발을 확장하려는 모든 개발자나 조직에게 권장하는 중요한 비공식 원칙이 있다면, 그것은 명시성을 채택하는 것입니다. 그리고 명시성을 본래 Xcode 프로젝트에서 관리하기 어려워진다면, [Tuist](https://tuist.io)나 [Bazel](https://bazel.build)과 같은 다른 방법을 고려해볼 필요가 있습니다. **그러면 신뢰성, 예측 가능성, 그리고 최적화가 가능해 집니다.** ## 미래 {#future} Apple의 위의 문제를 해결하기 위해 어떤 조치를 취할지는 알 수 없습니다. Xcode와 Swift Package Manager에 반영된 Apple의 지속적인 내용은 Apple은 해당 문제를 해결할 의향이 없음을 시사합니다. 암시적 구성을 유효한 상태로 허용하면, **기존 호환성을 깨는 변경을 도입하지 않으면 이 상태를 벗어나기 어렵습니다.** 기본 원칙으로 돌아가 툴의 설계를 재구성하면, 여러 해동안 우연히 컴파일이 되던 많은 Xcode 프로젝트가 더이상 정상적으로 동작 하지 않을 수 있습니다. 이런 일이 일어난다면, 커뮤니티에 일어날 소란을 상상해 보시기 바랍니다. Apple은 닭이 먼저냐, 달걀이 먼저냐의 문제에 직면해 있습니다. 편리함은 개발자가 빠르게 시작하고 Apple 생태계에서 더 많은 앱을 만들 수 있도록 도와줍니다. 하지만 편리한 경험을 위한 결정이 Xcode의 일부 기능이 신뢰성 있게 동작하기 어렵게 만듭니다. 미래는 알 수 없기 때문에, 우리는 **업계 표준과 Xcode 프로젝트에 최대한 부합하도록** 노력합니다. 우리는 위의 문제를 방지하고, 우리가 가진 지식을 활용해 더 나은 개발자 경험을 제공합니다. 이론적으로는 프로젝트 생성 할 필요가 없지만, Xcode와 Swift Package Manager의 확장성 부족으로 인해 프로젝트 생성이 유일한 방법입니다. 그리고 이런 방법이 Tuist 프로젝트에 문제가 생기면 Xcode 프로젝트도 문제가 생기기 때문에 안전한 방법입니다. 이론적으로는 **빌드 시스템이 더 확장 가능**해야 하지만, 암묵적인 방식과 결합되는 플러그인/확장을 두는 것이 더 나쁜 아이디어가 아닐까요? 이것은 좋은 생각이 아닌 것 같습니다. 그래서 더 나은 개발자 경험을 제공하기 위해 Tuist나 [Bazel](https://bazel.build)과 같은 외부 툴이 필요합니다. 아니면 Apple이 우리 모두를 놀라게 하고 Xcode를 더 확장 가능하고 명시적으로 만들어 줄지도 모릅니다... 그 일이 일어나기 전까지는, Xcode의 편리함을 받아들이고 그에 따르는 부채를 감수할지, 아니면 더 나은 개발자 경험을 제공하기 위해 우리를 믿고 따라올지 결정해야 합니다. 우린 당신을 실망시키지 않습니다. --- URL: "/ko/guides/develop/projects/code-sharing" LLMS_URL: "/ko/guides/develop/projects/code-sharing.md" title: "Code sharing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "manifest 파일 간의 코드 공유를 통해 중복을 줄이고 일관성을 유지하는 방법을 알아보세요" --- # Code sharing {#code-sharing} Xcode를 대규모 프로젝트에서 사용할 때의 한계점 중 하나는 `.xcconfig` 파일을 통한 빌드 설정 외에는 프로젝트의 다른 요소들을 재사용할 수 없다는 점입니다. 프로젝트 정의를 재사용할 수 있으면 다음과 같은 장점이 있습니다: - 변경 사항을 한 곳에서 적용하면 모든 프로젝트에 자동으로 반영되므로 **유지보수**가 수월해집니다. - 새로운 프로젝트들이 따를 수 있는 규칙을 정의할 수 있습니다. - 프로젝트가 더 `일관성`을 갖게 되어, 불일치로 인한 빌드 실패의 가능성이 줄어듭니다. - 기존 로직을 재사용할 수 있어 새로운 프로젝트 추가가 쉬워집니다. **project description helpers**를 통해 Tuist에서는 manifest 파일 간에 코드를 재사용할 수 있습니다. > [!TIP] Tuist만의 독자적인 가치 > 많은 조직들이 Tuist를 선호하는 이유는 project description helpers가 플랫폼 팀에게 자신들만의 convention을 코드화하고, 프로젝트를 설명하는 독자적인 언어를 정의할 수 있는 기반을 제공하기 때문입니다. 예를 들어, YAML 기반 project generator들은 자체 YAML 기반 독점 템플릿 솔루션을 만들거나, 조직이 이를 기반으로 도구를 구축하도록 강요해야 합니다. ## Project description helpers {#project-description-helpers} Project description helpers는 컴파일되어 manifest 파일에서 가져올 수 있는 `ProjectDescriptionHelpers` 모듈로 변환되는 Swift 파일입니다. 이 모듈은 `Tuist/ProjectDescriptionHelpers` 디렉토리에 있는 모든 파일을 모아 컴파일됩니다. manifest 파일 맨 위에 import 문을 추가하여 이를 가져올 수 있습니다: ```swift // Project.swift import ProjectDescription import ProjectDescriptionHelpers ``` `ProjectDescriptionHelpers`는 다음 manifest에서 사용할 수 있습니다: - `Project.swift` - `Package.swift` (`#TUIST` compiler flag 사용 시에만) - `Workspace.swift` ## 예시 {#example} 아래 코드 스니펫은 `Proejct` 모델을 확장하여 static constructor를 추가하고, 이를 `Project.swift` 파일에서 사용하는 예시입니다: ::: code-group ```swift [Tuist/Project+Templates.swift] import ProjectDescription extension Project { public static func featureFramework(name: String, dependencies: [TargetDependency] = []) -> Project { return Project( name: name, targets: [ .target( name: name, destinations: .iOS, product: .framework, bundleId: "io.tuist.\(name)", infoPlist: "\(name).plist", sources: ["Sources/\(name)/**"], resources: ["Resources/\(name)/**",], dependencies: dependencies ), .target( name: "\(name)Tests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.\(name)Tests", infoPlist: "\(name)Tests.plist", sources: ["Sources/\(name)Tests/**"], resources: ["Resources/\(name)Tests/**",], dependencies: [.target(name: name)] ) ] ) } } ``` ```swift {2} [Project.swift] import ProjectDescription import ProjectDescriptionHelpers let project = Project.featureFramework(name: "MyFeature") ``` ::: > [!TIP] 컨벤션(CONVENTION)을 정립하는 도구 > 함수를 통해 target 이름, bundle identifier, 폴더 구조에 대한 컨벤션을 정의하는 방식을 주목하세요. --- URL: "/ko/guides/develop/projects/best-practices" LLMS_URL: "/ko/guides/develop/projects/best-practices.md" title: "Best practices" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Tuist와 Xcode 프로젝트를 다룰 때의 모범 사례들을 알아보세요." --- # Best practices {#best-practices} 다양한 팀 및 프로젝트와의 수년간 협업 경험을 바탕으로, Tuist와 Xcode 프로젝트를 다룰 때 권장하는 모범 사례들을 정리했습니다. 이러한 모범 사례들은 필수 사항은 아니지만, 프로젝트를 보다 유지보수하기 쉽고 확장 가능한 방식으로 구조화하는데 도움이 될 수 있습니다. ## Xcode {#xcode} ### 권장하지 않는 패턴 {#discouraged-patterns} #### 원격 환경을 모델링하기 위한 설정 {#configurations-to-model-remote-environments} 많은 조직이 다양한 원격 환경을 모델링하기 위해 빌드 설정을 사용합니다 (예: `Debug-Production` or `Release-Canary`). 하지만 이 접근 방식에는 몇 가지 단점이 있습니다: - **불일치:** 그래프 전반에 걸쳐 설정이 일관되지 않으면, 빌드 시스템이 일부 타겟에 대해 잘못된 설정을 사용할 수 있습니다. - **복잡성:** 프로젝트의 로컬 설정과 원격 환경 설정들이 많아질수록 이해하고 관리하기 어려워질 수 있습니다. 다양한 환경을 모델링해야 할 때는 스킴(Scheme)을 사용하여 해결할 수 있습니다. 다양한 환경을 모델링해야 할 때는 스킴(Scheme)을 사용하여 해결할 수 있습니다. - **Debug 빌드:** 앱의 개발 환경에서 접근할 수 있어야 하는 모든 설정 (예: 엔드포인트) 을 포함할 수 있고, 이를 런타임에 전환할 수 있습니다. 전환 방식은 스킴 환경 변수를 사용하거나 앱 내의 UI를 통해 전환할 수 있습니다. - **Release 빌드:** 릴리즈 빌드에 필요한 설정만 포함되어야 하고 컴파일러 지시문을 사용하여 설정 전환을 위한 런타임 로직을 포함하지 않도록 해야 합니다. --- URL: "/ko/guides/develop/projects/adoption/swift-package" LLMS_URL: "/ko/guides/develop/projects/adoption/swift-package.md" title: "Use Tuist with a Swift Package" titleTemplate: ":title · Adoption · Projects · Develop · Guides · Tuist" description: "Swift Package와 함께 Tuist를 사용하는 방법에 대해 알아봅니다." --- # Using Tuist with a Swift Package {#using-tuist-with-a-swift-package-badge-typewarning-textbeta-} Tuist는 프로젝트에 DSL로 `Package.swift` 사용을 지원하고 패키지 타겟을 Xcode 프로젝트와 타겟으로 변환합니다. > [!WARNING]\ > 이 기능의 목적은 개발자가 Swift Package에 Tuist를 도입했을 때 영향도를 쉽게 파악하기 위함입니다. 따라서 우리는 Swift Package Manager의 모든 기능을 지원하거나 프로젝트 설명 도우미와 같은 Tuist의 기능을 패키지 분야에 제공할 계획도 없습니다. > [!NOTE] 루트 디렉토리\ > Tuist 명령어는 특정 디렉토리 구조를 요구하며 루트는 `Tuist` 또는 `.git` 디렉토리로 식별됩니다. ## Swift Package와 함께 Tuist 사용 {#using-tuist-with-a-swift-package} Swift Package가 포함된 [TootSDK Package](https://github.com/TootSDK/TootSDK) 리포지토리와 함께 Tuist를 사용해 봅니다. 가장 먼저 해야 할 일은 리포지토리를 복제하는 것입니다: ```bash git clone https://github.com/TootSDK/TootSDK cd TootSDK ``` 리포지토리 디렉토리에서 Swift Package Manager 의존성을 설치해야 합니다: ```bash tuist install ``` `tuist install`은 패키지의 의존성을 해결하고 가져오기 위해 Swift Package Manager를 사용합니다. 완료되면 프로젝트를 생성할 수 있습니다. ```bash tuist generate ``` Voilà! 열고 작업을 시작할 수 있는 Xcode 프로젝트가 생성됐습니다. --- URL: "/ko/guides/develop/projects/adoption/new-project" LLMS_URL: "/ko/guides/develop/projects/adoption/new-project.md" title: "Create a new project" titleTemplate: ":title · Adoption · Projects · Develop · Guides · Tuist" description: "Tuist로 새로운 프로젝트를 어떻게 생성하는지 배웁니다." --- # Create a new project {#create-a-new-project} Tuist로 새로운 프로젝트를 시작하는 가장 간단한 방법은 `tuist init` 명령어를 사용하는 것입니다. 이 명령어는 프로젝트 설정을 도와주는 CLI를 실행합니다. 안내에 따라 진행할 때, 반드시 "generated project"를 생성하는 옵션을 선택해야 합니다. 생성된 파일 중 하나 인 `Project.swift` 는 프로젝트의 정의를 포함하고 있습니다. Swift Package Manager에 익숙하다면 Xcode 프로젝트에서 사용하는 `Package.swift`라고 생각하면 됩니다. `tuist edit`을 수행하여 프로젝트를 수정할 수 있으며, 해당 프로젝트를 수정할 수 있게 Xcode가 열립니다. ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "MyApp", targets: [ .target( name: "MyApp", destinations: .iOS, product: .app, bundleId: "io.tuist.MyApp", infoPlist: .extendingDefault( with: [ "UILaunchScreen": [ "UIColorName": "", "UIImageName": "", ], ] ), sources: ["MyApp/Sources/**"], resources: ["MyApp/Resources/**"], dependencies: [] ), .target( name: "MyAppTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyAppTests", infoPlist: .default, sources: ["MyApp/Tests/**"], resources: [], dependencies: [.target(name: "MyApp")] ), ] ) ``` ::: > [!NOTE]\ > 유지 보수를 최소화 하기 위해 템플릿은 가능한 짧게 유지합니다. 프레임워크와 같이 애플리케이션이 아닌 프로젝트를 생성하고 싶으면, `tuist init`을 사용하여 생성된 프로젝트를 필요에 따라 수정할 수 있습니다. ## 수동으로 프로젝트 생성 {#manually-creating-a-project} 수동으로도 프로젝트를 생성할 수 있습니다. Tuist와 그 개념에 익숙한 경우에만 해당 내용을 수행하도록 추천합니다. 먼저, 프로젝트 구조에 대한 디렉토리를 생성해야 합니다: ```bash mkdir MyFramework cd MyFramework ``` 그런 다음에 Tuist 구성과 프로젝트의 루트 디렉토리를 결정하는 `Tuist.swift` 파일과 프로젝트를 선언하는 `Project.swift` 파일을 생성합니다: ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "MyFramework", targets: [ .target( name: "MyFramework", destinations: .macOS, product: .framework, bundleId: "io.tuist.MyFramework", sources: ["MyFramework/Sources/**"], dependencies: [] ) ] ) ``` ```swift [Tuist.swift] import ProjectDescription let tuist = Tuist() ``` ::: > [!IMPORTANT]\ > Tuist는 `Tuist/` 디렉토리를 사용하여 프로젝트의 루트를 결정하고, 그 디렉토리에서 다른 매니페스트 파일을 찾습니다. 원하는 편집기로 해당 파일을 생성하고, `tuist edit`를 사용하여 Xcode로 프로젝트를 수정할 수 있습니다. --- URL: "/ko/guides/develop/projects/adoption/migrate/xcodegen-project" LLMS_URL: "/ko/guides/develop/projects/adoption/migrate/xcodegen-project.md" title: "Migrate an XcodeGen project자동" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "XcodeGen에서 Tuist로 프로젝트를 마이그레이션 하는 방법을 배웁니다." --- # Migrate an XcodeGen project {#migrate-an-xcodegen-project} [XcodeGen](https://github.com/yonaskolb/XcodeGen)은 Xcode 프로젝트를 정의하기 위해 [구성 포맷](https://github.com/yonaskolb/XcodeGen/blob/master/Docs/ProjectSpec.md)으로 YAML을 사용하는 프로젝트 생성 툴입니다. 많은 조직에서 **Xcode 프로젝트로 작업하면 빈번하게 발생하는 Git 충돌을 벗어나기 위해 채택했습니다.** 그러나 Git 충돌은 조직에서 경험하는 많은 문제 중 하나에 불과합니다. Xcode는 개발자에게 많은 복잡성과 암시적 구성을 노출시켜서 대규모 프로젝트를 유지하고 최적화 하기 어렵게 만듭니다. XcodeGen은 프로젝트 관리 도구가 아닌 Xcode 프로젝트를 생성하는 툴이므로 설계상 부족한 점이 있습니다. Xcode 프로젝트 생성하는 것 이상의 툴이 필요하다면 Tuist를 고려해 보시기 바랍니다. > [!TIP] SWIFT OVER YAML\ > 많은 조직에서 구성 포맷으로 Swift를 사용하기 때문에 프로젝트 생성 툴로 Tuist를 선호합니다. Swift는 개발자에게 친숙한 프로그래밍 언어이며 Xcode의 자동 완성, 타입 검사, 그리고 기능 검증을 편리하게 사용할 수 있습니다. 다음은 XcodeGen에서 Tuist로 프로젝트를 마이그레이션 하는데 도움이 되는 몇 가지 고려사항과 지침입니다. ## 프로젝트 생성 {#project-generation} Tuist와 XcodeGen은 프로젝트 선언을 Xcode 프로젝트와 워크스페이스로 변환하는 `generate` 명령어를 제공합니다. ::: code-group ```bash [XcodeGen] xcodegen generate ``` ```bash [Tuist] tuist generate ``` ::: 차이점은 편집에 있습니다. Tuist를 사용하면 Xcode 프로젝트를 생성하고 열어서 작업을 시작할 수 있는 `tuist edit` 명령어를 수행할 수 있습니다. 이 기능은 프로젝트를 빠르게 변경할 때 유용합니다. ## `project.yaml` {#projectyaml} XcodeGen의 `project.yaml` 파일이 `Project.swift` 파일이 됩니다. 게다가 프로젝트를 워크스페이스에서 그룹화하는 방식을 사용자 정의할 수 있는 `Workspace.swift`를 가질 수 있습니다. 다른 프로젝트의 타겟을 참조하는 타겟을 가지는 `Project.swift`를 가질 수도 있습니다. 이런 경우 Tuist는 모든 프로젝트를 포함하는 Xcode Workspace를 생성합니다. ::: code-group ```bash [XcodeGen directory structure] / project.yaml ``` ```bash [Tuist directory structure] / Tuist.swift Project.swift Workspace.swift ``` ::: > [!TIP] XCODE의 언어 > XcodeGen과 Tuist 모두 Xcode의 언어와 개념을 수용합니다. 그러나 Tuist의 Swift 기반 구성은 Xcode의 자동 완성, 타입 검사, 그리고 기능 검증을 쉽게 사용하도록 제공합니다. ## 스펙 템플릿 {#spec-templates} 프로젝트 구성으로 YAML 언어의 단점 중 하나는 YAML 파일은 재사용을 지원하지 않습니다. 이것은 프로젝트를 설명할 때 흔히 존재하는 것으로 XcodeGen은 \*"templates"\*이라는 자체 솔루션으로 이를 해결해야 했습니다. Tuist의 재사용성은 Swift 언어 자체에 내장되어 있으며, <0>project description helpers라는 Swift 모듈을 통해 모든 매니페스트 파일에서 재사용할 수 있습니다. ::: code-group ```swift [Tuist/ProjectDescriptionHelpers/Target+Features.swift] import ProjectDescription extension Target { /** This function is a factory of targets that together represent a feature. */ static func featureTargets(name: String) -> [Target] { // ... } } ``` ```swift [Project.swift] import ProjectDescription import ProjectDescriptionHelpers // [!code highlight] let project = Project(name: "MyProject", targets: Target.featureTargets(name: "MyFeature")) // [!code highlight] ``` --- URL: "/ko/guides/develop/projects/adoption/migrate/xcode-project" LLMS_URL: "/ko/guides/develop/projects/adoption/migrate/xcode-project.md" title: "기존 Xcode 프로젝트를 Tuist로 전환하기" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Xcode 프로젝트에서 Tuist 프로젝트로 변환하는 방법을 알아봅니다." --- # Migrate an Xcode project {#migrate-an-xcode-project} Tuist로 새로운 프로젝트를 생성하면 자동으로 모든 것이 구성되지만 그렇지 않으면, Tuist의 기본 요소를 사용해서 Xcode 프로젝트를 정의해야 합니다. 이 과정이 얼마나 번거로운지는 프로젝트의 복잡도에 따라 다릅니다. 이미 알고 있지만, Xcode 프로젝트는 시간이 지날 수록 복잡하고 정리가 안될 수 있습니다: 디렉토리 구조와 맞지않는 그룹, 여러 타겟에서 공유되는 파일 또는 존재하지 않는 파일에 대한 참조가 그 예시입니다. 그렇게 쌓인 복잡성은 프로젝트를 신뢰성 있게 마이그레이션하는 명령어를 제공하는 것이 어렵습니다. 게다가 수동 마이그레이션은 프로젝트를 정리하고 단순화하는데 매우 좋은 연습입니다. 개발자들 뿐만 아니라 Xcode도 프로젝트의 처리와 인덱싱하는 속도가 빨라져서 감사할 것입니다. Tuist를 완전히 도입하면, 프로젝트가 일관되게 정의되고 단순하게 유지되도록 보장합니다. 해당 작업을 수월하게 하기 위해 우리는 사용자에게 받은 피드백을 기반으로 한 몇 가지 지침을 제공합니다. ## 프로젝트 생성 {#create-project-scaffold} 먼저, 다음의 Tuist 파일들을 생성해서 프로젝트의 구조를 잡아줍니다: ::: code-group ```js [Tuist.swift] import ProjectDescription let tuist = Tuist() ``` ```js [Project.swift] import ProjectDescription let project = Project( name: "MyApp-Tuist", targets: [ /** Targets will go here **/ ] ) ``` ```js [Tuist/Package.swift] // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies ] ) ``` ::: `Project.swift`는 프로젝트를 정의하는 매니페스트 파일이며, `Package.swift`는 의존성을 정의하는 매니페스트 파일입니다. `Tuist.swift` 파일은 현재 프로젝트의 Tuist 설정을 정의할 수 있습니다. > [!TIP] -TUIST 접미사가 붙은 프로젝트 이름\ > 기존에 Xcode 프로젝트의 충돌을 방지하기 위해 프로젝트 이름에 `-Tuist` 접미사를 추가하는 것이 좋습니다. 프로젝트를 Tuist로 완전히 마이그레이션 하면 삭제할 수 있습니다. ## CI에서 Tuist 프로젝트 빌드와 테스트 {#build-and-test-the-tuist-project-in-ci} 각 변경 사항의 마이그레이션 유효성을 보장하기 위해, Tuist가 매니페스트 파일로 생성한 프로젝트를 빌드하고 테스트하는 CI를 확장하는 것이 좋습니다. ```bash tuist install tuist generate tuist build -- ...{xcodebuild flags} # or tuist test ``` ## `.xcconfig` 파일로 프로젝트 빌드 설정 추출 {#extract-the-project-build-settings-into-xcconfig-files} 프로젝트를 더 가볍고 마이그레이션하기 쉽게 만들기 위해 `.xcconfig` 파일로 프로젝트 빌드 설정을 추출합니다. `.xcconfig` 파일로 프로젝트 빌드 설정을 추출하기 위해 아래의 명령어를 사용할 수 있습니다: ```bash mkdir -p xcconfigs/ tuist migration settings-to-xcconfig -p MyApp.xcodeproj -x xcconfigs/MyApp-Project.xcconfig ``` 그런 다음 `Project.swift` 파일에 생성된 `.xcconfig` 파일의 위치를 작성합니다: ```swift import ProjectDescription let project = Project( name: "MyApp", settings: .settings(configurations: [ .debug(name: "Debug", xcconfig: "./xcconfigs/MyApp-Project.xcconfig"), // [!code ++] .release(name: "Release", xcconfig: "./xcconfigs/MyApp-Project.xcconfig"), // [!code ++] ]), targets: [ /** Targets will go here **/ ] ) ``` 그런 다음 CI 파이프라인을 확장하여 다음 명령어를 수행해서 빌드 설정의 변경 사항을 직접 `.xcconfig` 파일에 작성하도록 합니다: ```bash tuist migration check-empty-settings -p Project.xcodeproj ``` ## 패키지 의존성 추출 {#extract-package-dependencies} 프로젝트의 모든 의존성을 `Tuist/Package.swift` 파일로 추출합니다: ```swift // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies .package(url: "https://github.com/onevcat/Kingfisher", .upToNextMajor(from: "7.12.0")) // [!code ++] ] ) ``` > [!TIP] PRODUCT TYPES\ > `PackageSettings` 구조체의 `productTypes` 딕셔너리에 특정 패키지에 대한 제품 타입을 재정의할 수 있습니다. 기본적으로 Tuist는 모든 패키지가 정적 프레임워크로 간주합니다. ## 마이그레이션 순서 {#determine-the-migration-order} 가장 의존도가 높은 타겟에서 가장 의존도가 낮은 타겟 순으로 마이그레이션 하는 것이 좋습니다. 다음 명령어를 수행하면 프로젝트의 타겟이 의존도에 따라 정렬되어 나타납니다: ```bash tuist migration list-targets -p Project.xcodeproj ``` 위에 있는 타겟이 의존도가 높으므로 이 타겟부터 마이그레이션 합니다. ## 타겟 마이그레이션 {#migrate-targets} 타겟을 하나씩 마이그레이션 합니다. 변경 사항을 병합하기 전에 검토되고 테스트 되기위해 각 타겟에 대해 Pull Request를 수행하는 것이 좋습니다. ### 타겟 빌드 설정을 `.xcconfig` 파일로 추출 {#extract-the-target-build-settings-into-xcconfig-files} 프로젝트 빌드 설정과 마찬가지로, 타겟을 더 가볍고 마이그레이션하기 쉽게 만들기 위해 타겟 빌드 설정을 `.xcconfig` 파일로 추출합니다. 다음 명령어를 수행하면 타겟의 빌드 설정을 `.xcconfig` 파일로 추출합니다: ```bash tuist migration settings-to-xcconfig -p MyApp.xcodeproj -t TargetX -x xcconfigs/TargetX.xcconfig ``` ### `Project.swift` 파일에 타겟 정의 {#define-the-target-in-the-projectswift-file} `Project.targets` 에 타겟을 정의합니다: ```swift import ProjectDescription let project = Project( name: "MyApp", settings: .settings(configurations: [ .debug(name: "Debug", xcconfig: "./xcconfigs/Project.xcconfig"), .release(name: "Release", xcconfig: "./xcconfigs/Project.xcconfig"), ]), targets: [ .target( // [!code ++] name: "TargetX", // [!code ++] destinations: .iOS, // [!code ++] product: .framework, // [!code ++] // or .staticFramework, .staticLibrary... bundleId: "io.tuist.targetX", // [!code ++] sources: ["Sources/TargetX/**"], // [!code ++] dependencies: [ // [!code ++] /** Dependencies go here **/ // [!code ++] /** .external(name: "Kingfisher") **/ // [!code ++] /** .target(name: "OtherProjectTarget") **/ // [!code ++] ], // [!code ++] settings: .settings(configurations: [ // [!code ++] .debug(name: "Debug", xcconfig: "./xcconfigs/TargetX.xcconfig"), // [!code ++] .debug(name: "Release", xcconfig: "./xcconfigs/TargetX.xcconfig"), // [!code ++] ]) // [!code ++] ), // [!code ++] ] ) ``` > [!NOTE] TEST TARGETS > 타겟과 연관있는 테스트 타겟이 존재하면, `Project.swift` 파일에 동일하게 해당 타겟을 정의해야 합니다. ### 타겟 마이그레이션 검증 {#validate-the-target-migration} `tuist build`와 `tuist test`를 수행해서 프로젝트 빌드와 테스트가 통과되는지 확인합니다. 또한, [xcdiff](https://github.com/bloomberg/xcdiff)을 사용하여 생성된 Xcode 프로젝트와 기존 프로젝트의 변경사항이 맞는지 비교할 수 있습니다. ### 반복 {#repeat} 모든 타겟이 마이그레이션 할 때까지 반복합니다. 작업이 완료되면, Tuist가 제공하는 속도와 안정성의 이점을 위해 `tuist build`와 `tuist test` 명령어를 사용하여 프로젝트 빌드와 테스트를 할 수 있게 CI와 CD 파이프라인을 업데이트 하는 것이 좋습니다. ## 문제 해결 {#troubleshooting} ### 파일 누락으로 인한 컴파일 오류 파일 누락으로 인한 컴파일 오류 파일 누락으로 인한 컴파일 오류 {#compilation-errors-due-to-missing-files} 파일 누락으로 인한 컴파일 오류 파일 누락으로 인한 컴파일 오류 {#compilation-errors-due-to-missing-files} 파일 누락으로 인한 컴파일 오류 파일 누락으로 인한 컴파일 오류 {#compilation-errors-due-to-missing-files} Xcode 프로젝트 타겟에 연관된 파일이 마이그레이션 한 타겟 파일 시스템 디렉토리에 포함되지 않으면, 그 프로젝트는 컴파일되지 않습니다. Tuist로 프로젝트를 생성한 후에 Xcode 프로젝트의 파일 목록과 일치하는지 확인하고, 타겟 구조와 파일 구조를 일치 시킵니다. --- URL: "/ko/guides/develop/projects/adoption/migrate/swift-package" LLMS_URL: "/ko/guides/develop/projects/adoption/migrate/swift-package.md" title: "Migrate a Swift Package" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "프로젝트를 관리하는 Swift Package Manager를 Tuist 프로젝트로 마이그레이션 하는 방법을 알아봅니다." --- # Migrate a Swift Package {#migrate-a-swift-package} Swift Package Manager는 원래 Swift 코드의 의존성을 관리하기 위해 등장했지만, 결과적으로 프로젝트 전체를 관리하고 Objective-C 같은 다른 프로그래밍 언어를 지원하는 문제까지 해결하게 되었습니다. 이 도구는 원래 다른 목적으로 설계되었기 때문에, 대규모 프로젝트를 관리하기 데에는 Tuist가 제공할수 있는 유연성이나 성능 측면에서 부족함이 있었습니다. 이것은 [Bumble의 iOS 확장](https://medium.com/bumble-tech/scaling-ios-at-bumble-239e0fa009f2) 이라는 글에서 잘 설명되어 있으며, 해당 글에는 Swift Package Manager와 순수 Xcode 프로젝트간의 성능을 비교한 다음과 같은 표도 포함되어 있습니다. A table that compares the regression in performance when using SPM over native Xcode projects 우리는 종종 Swift Package Manager도 유사한 프로젝트 관리 역할을 수행할 수 있다는 이유로 Tuist의 필요성에 의문을 제기하는 개발자와 조직을 마주하고는 합니다. 일부 개발자들은 Swift Package Manager을 이용한 마이그레이션을 시도하지만, 결국 개발자 경험이 크게 저하되었다는 사실을 깨닫게 됩니다. 예를 들어, 파일의 이름을 변경하면 다시 인덱싱하기 위해 15초나 걸리는 경우도 있습니다. 무려 15초요! **Apple이 Swift Package Manager를 대규모 프로젝트에 적합한 프로젝트 관리 도구로 발전시킬지는 여전히 불확실합니다.** 하지만, 현재로서는 그런 방향으로 나아간다는 뚜렷한 조짐은 보이지 않습니다. 사실 우리는 그와는 정반대의 흐름을 보고 있습니다. Apple은 Xcode에서 영감을 받아, 암시적 구성을 통해 편리함을 추구하는 방향으로 움직이고 있습니다. 하지만 이는 여러분도 아시다시피 규모가 커질수록 복잡성을 초래하는 주된 원인입니다. 우리는 Apple이 근본적인 원칙으로 되돌아가, 의존성 관리자로서는 타당했지만 프로젝트 관리자로서는 적절하지 않은 결정들, 예를 들어 컴파일된 언어를 프로젝트 정의의 인터페이스로 사용하는 방식 등을 재검토할 필요가 있다고 보고 있습니다. > [!TIP] SPM은 의존성 관리도구로만 활용하자 \ > Tuist는 Swift Package Manager를 의존성 관리 도구로만 취급하며, 이런 용도로는 아주 훌륭합니다. 우리는 SPM을 의존성 해결 및 빌드를 위해서만 사용합니다. 프로젝트 정의를 위해서는 사용하지 않습니다. ## Swift Package Manager에서 Tuist로 마이그레이션 {#migrating-from-swift-package-manager-to-tuist} Swift Package Manager와 Tuist는 유사한 점이 많기 때문에, 마이그레이션 과정은 간단합니다. 주요 차이점은 `Package.swift` 대신 Tuist의 DSL을 사용하여 프로젝트를 정의한다는 점입니다. 먼저, `Package.swift` 파일과 같은 위치에 `Project.swift` 파일을 생성하세요. 다음은 단일 타겟을 가진 프로젝트를 정의한 `Project.swift` 파일의 예시입니다, 다음은 단일 타겟을 가진 프로젝트를 정의한 `Project.swift` 파일의 예시입니다. ```swift import ProjectDescription let project = Project( name: "App", targets: [ .target( name: "App", destinations: .iOS, product: .app, bundleId: "io.tuist.App", sources: ["Sources/**/*.swift"]* ), ] ) ``` 몇 가지 주목 할 사항: - **ProjectDescription**: `PackageDescription` 대신에 `ProjectDescription`을 사용합니다. - **Project:** `package` 인스턴스를 내보내는 대신에 `project` 인스턴스를 내보냅니다. - **Xcode 언어:** 프로젝트를 정의하는데 사용하는 기본 요소는 Xcode의 언어를 따르므로 스킴, 타겟, 그리고 Build Phases 등을 찾을 수 있습니다. 그 다음에 아래와 같은 내용을 담은 `Tuist.swift` 파일을 생성하세요. ```swift import ProjectDescription let tuist = Tuist() ``` `Tuist.swift` 파일은 프로젝트의 구성 정보를 담고 있으며, 이 파일의 위치는 프로젝트 루트를 판단하는 기준이 됩니다. Tuist 프로젝트의 구조에 대해 더 알고 싶다면 디렉토리 구조 문서를 참고하세요. ## 프로젝트 편집하기 {#editing-the-project} 이 명령어는 Xcode 프로젝트를 생성하며, 이를 열어 바로 작업을 시작할 수 있게 합니다. `tuist edit` 명령어를 사용하면 Xcode에서 프로젝트를 편집할 수 있습니다. ```bash tuist edit ``` 프로젝트 규모에 따라, 한 번에 전체를 마이그레이션 할 수도 있고 점진적으로 진행할 수도 있습니다. 먼저 작은 프로젝트를 통해 DSL과 작업흐름에 익숙해지는 것을 권장합니다. 항상 가장 많은 의존성을 가진 타겟부터 시작해서 최상위 타겟까지 순차적으로 전환하는 방식이 바람직합니다. --- URL: "/ko/guides/develop/projects/adoption/migrate/bazel-project" LLMS_URL: "/ko/guides/develop/projects/adoption/migrate/bazel-project.md" title: "Migrate a Bazel project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Bazel에서 Tuist로 프로젝트를 마이그레이션 하는 방법을 배웁니다." --- # Migrate a Bazel project {#migrate-a-bazel-project} [Bazel](https://bazel.build)은 Google이 2015년에 오픈소스로 공개한 빌드 시스템입니다. Bazel은 어떤 크기의 소프트웨어에서도 빠르고 안정적으로 빌드와 테스트할 수 있는 강력한 툴입니다. [Spotify](https://engineering.atspotify.com/2023/10/switching-build-systems-seamlessly/), [Tinder](https://medium.com/tinder/bazel-hermetic-toolchain-and-tooling-migration-c244dc0d3ae), 또는 [Lyft](https://semaphoreci.com/blog/keith-smiley-bazel)와 같은 일부 대규모 조직에서는 Bazel을 사용하지만, Bazel을 도입하고 유지하는데 초기 투자 (즉, 기술 학습) 와 지속적인 투자 (즉, Xcode 업데이트 유지) 가 필요합니다. 일부 조직에서는 이를 범용적인 문제로 다루어 효과를 볼 수 있지만, 제품 개발에만 집중하길 원하는 조직에서는 최선의 선택이 아닐 수 있습니다. 예를 들어, iOS 플랫폼 팀이 Bazel을 도입했는데 이를 주도했던 개발자들이 회사를 떠난 후에 이를 포기해야 했던 조직을 본 적이 있습니다. 애플의 Xcode와 빌드 시스템 간의 강한 결합성도 Bazel 프로젝트를 유지하는데 어렵게 만드는 또 다른 요인입니다. > [!TIP] TUIST의 독창성은 섬세함에 있다 > Tuist는 Xcode와 Xcode 프로젝트에 맞서기 보다는 그것을 받아들입니다. Tuist는 동일한 개념 (즉, 타겟, 스킴, 빌드 설정), 익숙한 언어 (즉, Swift), 그리고 프로젝트를 유지하고 확장하는 것을 iOS 플랫폼 팀 뿐만 아니라 모든 팀에게 간단하고 즐거운 경험을 제공합니다. ## 규칙 {#rules} Bazel은 소프트웨어를 빌드하고 테스트하는 방식을 정의하는 규칙을 사용합니다. 이 규칙은 Python과 유사한 언어인 [Starlark](https://github.com/bazelbuild/starlark)로 작성되어 있습니다. Tuist는 구성 언어로 Swift를 사용하므로 개발자는 Xcode의 자동 완성, 타입 검사, 그리고 기능 검증을 사용할 수 있습니다. 예를 들어, 다음은 Bazel이 Swift 라이브러리를 빌드하는 규칙을 나타냅니다: ::: code-group ```txt [BUILD (Bazel)] swift_library( name = "MyLibrary.library", srcs = glob(["**/*.swift"]), module_name = "MyLibrary" ) ``` ```swift [Project.swift (Tuist)] let project = Project( // ... targets: [ .target(name: "MyLibrary", product: .staticLibrary, sources: ["**/*.swift"]) ] ) ``` ::: 다음은 Bazel과 Tuist에서 단위 테스트를 정의하는 방법을 비교한 또 다른 예시 입니다: :::code-group ```txt [BUILD (Bazel)] ios_unit_test( name = "MyLibraryTests", bundle_id = "io.tuist.MyLibraryTests", minimum_os_version = "16.0", test_host = "//MyApp:MyLibrary", deps = [":MyLibraryTests.library"], ) ``` ```swift [Project.swift (Tuist)] let project = Project( // ... targets: [ .target( name: "MyLibraryTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyLibraryTests", sources: "Tests/MyLibraryTests/**", dependencies: [ .target(name: "MyLibrary"), ] ) ] ) ``` ::: ## Swift Package Manager 의존성 {#swift-package-manager-dependencies} Bazel에서 Swift Package를 의존성으로 사용하기 위해 [`rules_swift_package_manager`](https://github.com/cgrindel/rules_swift_package_manager) [Gazelle](https://github.com/bazelbuild/bazel-gazelle/blob/master/extend.md) 플로그인을 사용할 수 있습니다. 이 플러그인은 의존성에 대한 진실 공급원으로 `Package.swift`를 요구합니다. Tuist의 인터페이스는 Bazel과 유사합니다. `tuist install` 명령어를 사용하여 패키지의 의존성을 해결하고 가져올 수 있습니다. 의존성 해결이 완료되면 `tuist generate` 명령어로 프로젝트를 생성할 수 있습니다. ```bash tuist install # Fetch dependencies defined in Tuist/Package.swift tuist generate # Generate an Xcode project ``` ## 프로젝트 생성 {#project-generation} 커뮤니티는 Bazel로 선언된 프로젝트를 Xcode 프로젝트를 생성하기 위해 [rules_xcodeproj](https://github.com/MobileNativeFoundation/rules_xcodeproj)라는 규칙을 제공합니다. `BUILD` 파일에 이불 구성을 추가해야 하는 Bazel과 달리, Tuist는 이런 구성이 필요하지 않습니다. 프로젝트의 루트 디렉토리에서 `tuist generate`를 수행하면 Tuist는 Xcode 프로젝트를 생성합니다. --- URL: "/ko/guides/develop/projects" LLMS_URL: "/ko/guides/develop/projects.md" title: "Projects" titleTemplate: ":title · Develop · Guides · Tuist" description: "Xcode 프로젝트를 정의하는 Tuist의 DSL에 대해 배워봅니다." --- # Projects {#projects} Generated는 복잡성과 비용을 적절하게 유지하면서 문제를 해결하는데 도움이 되는 해결책입니다. 이것은 Xcode 프로젝트를 기본 요소로 고려하여 Xcode 업데이트를 대응하고, Xcode 프로젝트 생성을 활용하여 팀에게 모듈화 중심의 선언적 API를 제공합니다. Tuist는 프로젝트 선언을 사용하여 모듈화\*\*의 복잡성을 단순화하고, 여러 환경에서의 빌드나 테스트와 같은 워크플로우를 최적화하고, Xcode 프로젝트의 발전과 관리에 대한 접근성을 넓힙니다. ## 어떻게 동작하나요? {#how-does-it-work} 생성된 프로젝트를 시작하려면 \*\*Tuist의 Domain Specific Language(DSL)\*\*을 사용하여 프로젝트를 정의하면 됩니다. 여기에서 `Workspace.swift` 또는 `Project.swift`와 같은 매니페스트 파일을 사용하여 프로젝트를 정의합니다. 이전에 Swift Package Manager를 사용해본 적이 있다면 그 접근 방식과 유사합니다. 프로젝트를 정의한 후, Tuist는 프로젝트를 관리하고 상호 작용할 수 있는 다양한 워크플로우를 제공합니다: - **Generate:** 이것은 기본 워크플로우입니다. 이를 사용하면 Xcode와 호환되는 Xcode 프로젝트를 생성합니다. - **Build:** 이 워크플로우는 Xcode 프로젝트를 생성할 뿐만 아니라 `xcodebuild`를 사용하여 프로젝트를 컴파일 합니다. - **<0>Test:** 빌드 워크플로우와 유사하게 동작하고, Xcode 프로젝트를 생성할 뿐만 아니라 `xcodebuild`를 활용하여 프로젝트를 테스트 합니다. ## Xcode 프로젝트의 문제 {#challenges-with-xcode-projects} Xcode 프로젝트가 커짐에 따라 신뢰할 수 없는 증분 빌드, 개발자가 문제 해결을 위해 Xcode의 글로벌 캐시를 자주 지우는 것, 그리고 불안정한 프로젝트 구성과 같은 여러가지 이유로 **조직은 생산성 저하를 경험합니다**. 빠른 기능 개발을 유지하기 위해 조직은 일반적으로 다양한 전략을 검토합니다. 일부 조직은 [React Native](https://reactnative.dev/)와 같은 JavaScript 기반의 동적 런타임을 사용하여 플랫폼을 추상화해 컴파일러를 우회하는 방법을 선택합니다. 이런 접근 방식을 효율적일 수 있지만, [플랫폼의 네이티브 기능에 접근하는 것을 복잡하게 만듭니다](https://shopify.engineering/building-app-clip-react-native). 다른 조직은 명확한 경계를 설정해 코드베이스 작업을 더 쉽게 하고 빌드를 더 안정적으로 개선하는 **코드베이스 모듈화**를 선택합니다. 하지만 Xcode 프로젝트 형식은 모듈화를 염두에 두고 설계하지 않았으므로, 이해하기 어려운 암시적 구성과 잦은 충돌이 발생합니다. 이로 인해 프로젝트의 복잡성을 증가시켜 파악하기 어렵게 만들고, 증분 빌드가 향상될 수는 있지만 빌드 실패 시, Xcode의 빌드 캐시 (즉, Derived Data) 를 자주 지워야 할 수도 있습니다. 이런 문제를 해결하기 위해 일부 조직은 **Xcode 빌드 시스템을 포기**하고 대안으로 [Buck](https://buck.build/) 또는 [Bazel](https://bazel.build/)을 적용합니다. 하지만 이 방식은 [복잡성과 유지보수 부담](https://bazel.build/migrate/xcode)을 동반합니다. ## 대안 {#alternatives} ### Swift Package Manager {#swift-package-manager} Swift Package Manager (SPM) 이 의존성 관리에 집중하는 반면에 Tuist는 다른 접근 방식을 제공합니다. Tuist에서는 SPM 통합을 위한 패키지 정의 뿐만 아니라 프로젝트, 워크스페이스, 타겟, 그리고 스킴과 같은 익숙한 개념을 사용하여 프로젝트를 생성할 수 있습니다. ### XcodeGen {#xcodegen} [XcodeGen](https://github.com/yonaskolb/XcodeGen)은 Xcode 프로젝트의 충돌을 줄이고 Xcode의 내부 동작의 복잡성을 단순화하기 위해 설계된 프로젝트 생성기 입니다. 하지만 프로젝트는 [YAML](https://yaml.org/)과 같은 형식을 사용하여 정의됩니다. Swift와 다르게 XcodeGen은 추가적인 툴 없이는 추상화나 검증을 기반으로 작업할 수 없습니다. XcodeGen은 의존성을 검증하고 최적화하기 위해 내부 표현으로 매핑하는 방법을 제공하지만, 여전히 Xcode의 세부 사항이 노출됩니다. 이것은 Bazel 커뮤니티에서 볼 수 있듯이, XcodeGen이 다른 [빌드 도구](https://github.com/MobileNativeFoundation/rules_xcodeproj)를 위한 기반으로 사용될 수는 있지만, 건강하고 생산적인 환경을 유지하고 포용적인 프로젝트 발전을 목표로 하는 것에는 맞지 않습니다. ### Bazel {#bazel} [Bazel](https://bazel.build)은 원격 캐싱 기능으로 잘 알려진 빌드 시스템이며, 주로 이 기능 덕분에 Swift 커뮤니티에서 인기를 얻고 있습니다. 하지만 Xcode와 Xcode의 빌드 시스템이 확장에 제한적이므로, 이를 Bazel의 시스템으로 전환하려면 상당한 노력과 유지 관리가 필요합니다. 풍부한 리소스를 보유한 몇 회사만이 이러한 부담을 감당할 수 있으며, Bazel을 Xcode와 통합하는데 막대한 투자를 하는 회사 목록에서 이를 확인할 수 있습니다. 흥미롭게도 커뮤니티는 Bazel의 XcodeGen을 활용하여 Xcode 프로젝트를 생성하는 [툴](https://github.com/MobileNativeFoundation/rules_xcodeproj)을 만들었습니다. 이로 인해 복잡한 변환 과정이 발생: Bazel 파일에서 XcodeGen YAML로 변환하고 마지막으로 Xcode Project로 변환합니다. 이런 계층화된 간접성은 문제를 진단하고 해결하기 어렵게 만듭니다. --- URL: "/ko/guides/develop/insights" LLMS_URL: "/ko/guides/develop/insights.md" title: "Insights" titleTemplate: ":title · Develop · Guides · Tuist" description: "개발 환경을 유지하기 위해 프로젝트에 대한 인사이트를 얻으세요." --- # Insights {#insights} > [!IMPORTANT] 요구사항 > > - Tuist 계정과 프로젝트 대규모 프로젝트 작업이 부담스럽게 느껴지면 안됩니다. 사실, 2주 전에 시작한 프로젝트처럼 즐거워야 합니다. 그렇지 못한 이유 중에 하나는 프로젝트가 커짐에 따라 개발자 경험이 나빠지기 때문입니다. 빌드 시간이 길어지고 테스트가 느리고 불안정해집니다. 이러한 문제들은 심각해질 때까지 무시되곤 하지만 그 시점이 되면 해결하기가 어렵습니다. Tuist Insights는 프로젝트의 상태를 모니터링하고, 프로젝트가 커져도 생산적인 개발 환경을 유지할 수 있도록 도와주는 툴을 제공합니다. 다시 말해, Tuist Insights는 다음과 같은 질문에 답을 얻을 수 있게 도와줍니다: - 지난주에 빌드 시간이 크게 증가했나요? - 테스트가 더 느려졌나요? 어떤 테스트가 그런가요? > [!NOTE]\ > Tuist Insights는 아직 개발 단계입니다. ## 빌드 {#builds} CI 워크플로우의 성능에 대한 지표는 가지고 있을 수 있지만, 로컬 개발 환경에 대한 내용은 부족할 수 있습니다. 그러나 빌드 시간은 개발자 경험에 대한 중요한 요소 중 하나입니다. 로컬 빌드 시간을 추적하려면 `tuist inspect build` 명령어를 스킴의 후속 작업으로 추가하여 활용할 수 있습니다: ![빌드 검사용 후속 작업](/images/guides/develop/insights/inspect-build-scheme-post-action.png) [Mise](https://mise.jdx.dev/)를 사용하는 경우에, 후속 작업 환경에 `tuist`를 활성화하도록 스크립트를 설정해야 합니다: ```sh # -C ensures that Mise loads the configuration from the Mise configuration # file in the project's root directory. eval "$($HOME/.local/bin/mise activate -C $SRCROOT bash --shims)" tuist inspect build ``` 이제 Tuist 계정에 로그인된 상태에서는 로컬 빌드가 추적됩니다. 이제 Tuist 대시보드에서 빌드 시간을 확인하고 시간이 지남에 따라 어떻게 변하는지 살펴볼 수 있습니다: > [!TIP]\ > 대시보드에 빠르게 접근하려면, CLI에서 `tuist project show-web` 명령어를 수행하세요. ![빌드 인사이트 대시보드](/images/guides/develop/insights/builds-dashboard.png) ## Projects {#projects} > [!NOTE]\ > 자동 생성된 스킴에는 `tuist inspect build` 후속 작업이 자동으로 포함됩니다. > > 자동 생성된 스킴에서 빌드 인사이트 추적이 필요하지 않으면, buildInsightsDisabled 생성 옵션을 사용하여 비활성화할 수 있습니다. 생성된 프로젝트를 사용하면, 다음과 같이 커스텀 스킴을 사용하여 커스텀 <0>빌드 후속 작업을 설정할 수 있습니다: ```swift let project = Project( name: "MyProject", targets: [ // Your targets ], schemes: [ .scheme( name: "MyApp", shared: true, buildAction: .buildAction(targets: ["MyApp"]), testAction: .testAction(targets: ["MyAppTests"]), runAction: .runAction(configuration: "Debug"), postActions: [ .postAction( name: "Inspect Build", scriptText: """ eval \"$($HOME/.local/bin/mise activate -C $SRCROOT bash --shims)\" tuist inspect build """ ) ] ) ] ) ``` Mise를 사용하지 않는다면, 스크립트를 다음과 같이 간단하게 작업할 수 있습니다: ```swift .postAction( name: "Inspect Build", script: "tuist inspect build", execution: .always ) ``` ## Continuous integration {#continuous-integration} CI에서 빌드 시간을 추적하려면, CI가 인증되었는지 확인해야 합니다. 추가로, 다음의 내용도 필요합니다: - `xcodebuild` 작업을 실행할 때는 `tuist xcodebuild` 명령어를 사용합니다. - `xcodebuild` 실행 시에 `-resultBundlePath`를 추가합니다. `-resultBundlePath`없이 `xcodebuild`로 빌드를 수행하면, `.xcactivitylog` 파일이 생성되지 않습니다. 하지만 `tuist inspect build` 후속 작업은 빌드를 분석하기 위해 해당 파일이 생성되어야 합니다. --- URL: "/ko/guides/develop/cache" LLMS_URL: "/ko/guides/develop/cache.md" title: "Cache" titleTemplate: ":title · Develop · Guides · Tuist" description: "컴파일된 바이너리를 캐싱하고 다양한 환경 간에 공유하여 빌드 시간을 최적화 하세요." --- # Cache {#cache} > [!IMPORTANT] 요구사항 > > - 생성된 프로젝트 > - Tuist 계정과 프로젝트 Xcode의 빌드 시스템은 [증분 빌드](https://en.wikipedia.org/wiki/Incremental_build_model)를 제공하여 일반적인 상황에서 효율을 높입니다. 하지만 이 기능은 증분 빌드에 필요한 데이터가 서로 다른 빌드에서 공유되지 않으므로, [Continuous Integration (CI) 환경](https://en.wikipedia.org/wiki/Continuous_integration)에서는 적절하지 않습니다. 게다가 **개발자는 복잡한 컴파일 문제를 해결하기 위해 로컬에서 이 데이터를 초기화 하므로**, 클린 빌드가 자주 발생하게 됩니다. 팀은 이것으로 인해 로컬 빌드가 완료되거나 Continuous Integration 파이프라인이 Pull Request에 대한 피드백을 제공할 때까지 과도한 시간을 기다려야 합니다. 더욱이 이러한 환경에서 빈번한 컨텍스트 전환은 생산성을 더욱 악화시킵니다. Tuist는 캐싱 기능으로 이 문제를 효과적으로 해결합니다. 이 툴은 컴파일된 바이너리를 캐시 하여 빌드 과정을 최적화하고, 로컬 개발 환경과 CI 환경 모두에서 빌드 시간을 크게 단축 시킵니다. 이 접근 방식은 피드백 순환을 가속화할 뿐만 아니라 컨텍스트 전환을 최소화하여 생산성을 극대화합니다. ## 워밍 {#warming} Tuist는 각 타겟에 대한 의존성 그래프 변화를 감지하기 위해 효율적으로 해시를 활용합니다. 이 데이터를 활용하여, Tuist는 타겟의 바이너리에 고유 식별자를 생성하고 할당합니다. 이 데이터를 활용하여, Tuist는 타겟의 바이너리에 고유 식별자를 생성하고 할당합니다. 그래프가 생성될 때, Tuist는 기존 타겟을 바이너리로 원할하게 대체합니다. 이런 작업을 \*"워밍"\*이라 하며, Tuist를 통해 로컬 사용이나 팀원과 CI 환경에서 공유할 수 있는 바이너리를 생성합니다. 캐시 워밍 과정은 간단하며 단순한 명령어로 시작할 수 있습니다: ```bash tuist cache ``` 이 명령어는 더 빠르게 진행하기 위해 바이너리를 재사용합니다. ## 사용 {#usage} 기본적으로 Tuist 명령어는 프로젝트를 생성할 때, 캐시에 바이너리가 있는 경우 자동으로 의존성을 해당 바이너리로 대체합니다. 추가적으로 특정 타겟을 지정하면 Tuist는 해당 타겟에 의존하는 타겟도 캐시된 바이너리가 있는 경우 대체합니다. 다른 접근 방식을 선호하면, 특정 플래그를 사용하여 해당 동작을 완전히 비활성화 할 수 있습니다: ::: code-group ```bash [Project generation] tuist generate # Only dependencies tuist generate Search # Dependencies + Search dependencies tuist generate Search Settings # Dependencies, and Search and Settings dependencies tuist generate --no-binary-cache # No cache at all ``` ```bash [Testing] tuist test ``` ::: > [!WARNING]\ > 바이너리 캐싱은 시뮬레이터나 디바이스에서 앱을 실행하거나 테스트를 실행하는 등의 개발 워크플로우를 위해 설계된 기능입니다. 이것은 릴리즈 빌드를 위한 기능이 아닙니다. 앱을 아카이브 할 때는, `--no-binary-cache` 플래그를 사용하여 소스가 포함된 프로젝트를 생성해야 합니다. ## 지원하는 결과물 {#supported-products} 다음의 타겟 결과물만 Tuist에 의해 캐시될 수 있습니다: - [XCTest](https://developer.apple.com/documentation/xctest)를 의존하지 않는 프레임워크 (정적 프레임워크와 동적 프레임워크) - 번들 - Swift Macros 현재 XCTest를 의존하는 라이브러리와 타겟을 지원하도록 작업 중입니다. > [!NOTE] 상위 의존성\ > 타겟이 캐시가 불가능하면 해당 타겟에 의존하는 타겟도 캐시가 불가능합니다. 예를 들어, A가 B를 의존하고 `A > B`라는 의존성 그래프라면, B가 캐시가 불가능하면 A도 캐시가 불가능합니다. ## 효율성 {#efficiency} 바이너리 캐싱으로 인해 달성할 수 있는 효율성 수준은 그래프 구조에 강하게 의존합니다. 가장 좋은 결과를 달성하기 위해 다음을 권장합니다: 1. 과도하게 중첩된 의존성 그래프를 피합니다. 그래프는 얕을 수록 더 좋습니다. 2. 프로토콜/인터페이스 타겟으로 의존성을 정의하고, 최상위 타겟에서 의존성 주입을 구현합니다. 3. 자주 수정되는 타겟은 변경 가능성이 적은 타겟으로 나눕니다. 위의 제안은 바이너리 캐싱의 이점 뿐만 아니라 Xcode의 기능을 최대한 활용할 수 있게 프로젝트를 구조화 하는 방식을 제시하는 [The Modular Architecture](https://docs.tuist.dev/ko/guides/develop/projects/tma-architecture)의 일부분입니다. ## 권장 설정 {#recommended-setup} 우리는 캐시를 미리 준비하기 위해 **main 브랜치에 커밋이 될 때마다 수행하는** CI 작업을 가지는 것을 권장합니다. 이렇게 하면 캐시는 항상 `main` 브랜치의 변경사항에 대한 바이너리를 포함하므로 로컬 및 CI 브랜치에서 점진적으로 빌드할 수 있습니다. > [!TIP] 캐시 워밍은 바이너리를 사용합니다\ > `tuist cache` 명령어는 캐시 워밍을 빠르게 하기 위해 바이너리 캐시를 사용합니다. 다음은 일반적인 워크플로우의 예제입니다: ### 개발자가 새로운 기능 작업을 시작 {#a-developer-starts-to-work-on-a-new-feature} 1. `main`에서 새로운 브랜치를 생성합니다. 2. `tuist generate`를 수행합니다. 3. Tuist는 `main`에서 최근 바이너리를 가져와 프로젝트를 생성합니다. ### 개발자가 변경사항을 푸시 {#a-developer-pushes-changes-upstream} 1. CI 파이프라인은 프로젝트 빌드나 프로젝트 테스트를 위해 `tuist build` 또는 `tuist test`를 수행합니다. 2. 이 워크플로우는 `main`에서 최근 바이너리를 가져와 프로젝트를 생성합니다. 3. 그런 다음에 프로젝트를 점진적으로 빌드나 테스트를 진행합니다. ## 문제 해결 {#troubleshooting} ### 내 타겟에 대해 바이너리를 사용하지 않음 {#it-doesnt-use-binaries-for-my-targets} 같은 환경과 프로젝트 실행에서 해시는 항상 동일해야 합니다. 예를 들어, 절대 경로를 사용하는 것과 같이 프로젝트가 환경에 대한 참조를 포함하고 있을 때 발생할 수 있습니다. `diff` 명령어를 사용하여 두 번의 `tuist generate`를 통해 생성된 프로젝트나 환경 또는 프로젝트 실행 차이를 비교할 수 있습니다. 또한 타겟이 직접적으로나 간접적으로 캐시가 불가능한 타겟에 의존하지 않도록 확인해야 합니다. --- URL: "/ko/guides/develop/build" LLMS_URL: "/ko/guides/develop/build.md" title: "Build" titleTemplate: ":title · Develop · Guides · Tuist" description: "프로젝트를 효율적으로 빌드하기 위해 Tuist를 어떻게 사용하는지 배워봅니다." --- # Build {#build} 프로젝트는 보통 빌드 시스템이 제공하는 CLI (예: `xcodebuild`) 를 통해 빌드됩니다. Tuist는 사용자 경험을 개선하고 최적화와 분석 기능을 제공하기 위해 이런 CLI를 래핑하여 플랫폼과 워크플로우를 통합합니다. 필요한 경우 `tuist generate`로 프로젝트를 생성하고 플랫폼별 CLI로 빌드 하는 것보다 `tuist build`를 사용하는 차이가 무엇인지 궁금할 수 있습니다. 다음은 그 차이에 대한 이유를 나타냅니다: - **단일 명령어:** `tuist build`는 프로젝트 컴파일 전에 프로젝트를 생성합니다. - **보기좋은 출력:** Tuist는 출력을 더 사용자 친화적으로 만들어 주는 [xcbeautify](https://github.com/cpisciotta/xcbeautify)와 같은 툴을 사용하여 출력합니다. - <0><1>캐시: 원격 캐시에서 빌드 artifact를 재사용하여 빌드를 최적화 합니다. - **분석:** 다른 데이터 포인트와 연관된 지표를 수집하고 보고하여, 정보에 기반한 결정을 내릴 수 있게 도와줍니다. ## 사용법 {#usage} `tuist build`는 필요하면 프로젝트를 생성한 다음에 플랫폼별 빌드 툴을 사용하여 빌드합니다. `--` 구분자를 사용하여 이후의 모든 인자를 직접 하위 빌드 툴로 전달하는 것을 지원합니다. 이것은 `tuist build`에서는 지원하지 않지만 하위 빌드 툴에서 지원하는 경우, 인자를 전달할 때 유용합니다. ::: code-group ```bash [Build a scheme] tuist build MyScheme ``` ```bash [Build a specific configuration] tuist build MyScheme -- -configuration Debug ``` ```bash [Build all schemes without binary cache] tuist build --no-binary-cache ``` ::: --- URL: "/ko/guides/automate/continuous-integration" LLMS_URL: "/ko/guides/automate/continuous-integration.md" title: "Continuous Integration (CI)" titleTemplate: ":title · Automate · Guides · Tuist" description: "CI 워크플로우에서 Tuist를 사용하는 방법에 대해 알아보세요." --- # Continuous Integration (CI) {#continuous-integration-ci} Tuist를 [CI(Continuous Integration)](https://en.wikipedia.org/wiki/Continuous_integration) 환경에서 사용할 수 있습니다. 다음 섹션에서는 다양한 CI 플랫폼에서 이를 수행하는 방법에 대한 예시를 제공합니다. ## Examples {#examples} CI 워크플로우에서 Tuist 명령어를 실행하려면, CI 환경에 Tuist를 설치해야 합니다. ### Xcode Cloud {#xcode-cloud} [Xcode Cloud](https://developer.apple.com/xcode-cloud/)에서는 Xcode 프로젝트를 진실 공급원(source of truth)으로 사용하기 때문에, [post-clone](https://developer.apple.com/documentation/xcode/writing-custom-build-scripts#Create-a-custom-build-script) 스크립트를 추가하여 Tuist를 설치하고 필요한 명령어를 실행해야 합니다. 예를 들어 `tuist generate` 명령어를 실행합니다: :::code-group ```bash [Mise] #!/bin/sh curl https://mise.jdx.dev/install.sh | sh mise install # Installs the version from .mise.toml # Runs the version of Tuist indicated in the .mise.toml file {#runs-the-version-of-tuist-indicated-in-the-misetoml-file} mise exec -- tuist generate ``` ```bash [Homebrew] #!/bin/sh brew install --formula tuist@x.y.z tuist generate ``` ::: ### Codemagic {#codemagic} [Codemagic](https://codemagic.io)에서 워크플로우에 Tuist를 설치하는 추가 단계를 다음과 같이 추가할 수 있습니다: ::: code-group ```yaml [Mise] workflows: lint: name: Build max_build_duration: 30 environment: xcode: 15.0.1 scripts: - name: Install Mise script: | curl https://mise.jdx.dev/install.sh | sh mise install # Installs the version from .mise.toml - name: Build script: mise exec -- tuist build ``` ```yaml [Homebrew] workflows: lint: name: Build max_build_duration: 30 environment: xcode: 15.0.1 scripts: - name: Install Tuist script: | brew install --formula tuist@x.y.z - name: Build script: tuist build ``` ::: ### GitHub Actions {#github-actions} On [GitHub Actions](https://docs.github.com/en/actions) you can add an additional step to install Tuist, and in the case of managing the installation of Mise, you can use the [mise-action](https://github.com/jdx/mise-action), which abstracts the installation of Mise and Tuist: ::: code-group ```yaml [Mise] name: Build Application on: pull_request: branches: - main push: branches: - main jobs: build: runs-on: macos-latest steps: - uses: actions/checkout@v3 - uses: jdx/mise-action@v2 - run: tuist build ``` ```yaml [Homebrew] name: test on: pull_request: branches: - main push: branches: - main jobs: lint: runs-on: macos-latest steps: - uses: actions/checkout@v3 - run: brew install --formula tuist@x.y.z - run: tuist build ``` ::: :::tip Tuist 프로젝트에서 환경 간 Tuist 버전을 고정하려면 `mise use -pin` 명령어를 사용하는 것을 권장합니다. 이 명령어는 Tuist의 버전을 포함하는 `.tool-version` 파일을 생성합니다. ::: ## 인증 {#authentication} cache와 같은 server-side 기능을 사용할 때, CI 워크플로우에서 서버로 가는 요청을 인증할 방법이 필요합니다. 이를 위해, 다음 명령어를 실행하여 프로젝트 범위의 토큰을 생성할 수 있습니다. ```bash tuist project tokens create my-handle/MyApp ``` 이 명령어는 `my-account/my-project`라는 전체 핸들을 가진 프로젝트에 대한 토큰을 생성합니다. 해당 값을 CI 환경의 `TUIST_CONFIG_TOKEN` 환경 변수로 설정하고, secret으로 설정하여 노출되지 않도록 합니다. > [!IMPORTANT] CI 환경 감지 > Tuist는 CI 환경에서 실행 중임을 감지할 때만 토큰을 사용합니다. CI 환경이 감지되지 않는 경우, 환경 변수 `CI`를 `1`로 설정하여 토큰 사용을 강제할 수 있습니다. --- URL: "/ko/guides/ai/mcp" LLMS_URL: "/ko/guides/ai/mcp.md" title: "Model Context Protocol (MCP)" titleTemplate: ":title · AI · Guides · Tuist" description: "Tuist의 MCP 서버를 사용하여 앱 개발 환경에서 언어 기반 인터페이스를 활용하는 방법을 배워봅니다." --- # Model Context Protocol (MCP) [Model Context Protocol (MCP)](https://www.claudemcp.com)은 LLM이 개발 환경과 상호작용할 수 있도록 [Claude](https://claude.ai)에서 제안한 표준입니다. 이것을 LLM의 USB-C로 생각할 수 있습니다. 컨테이너 운송이 화물과 운송을 더 상호 운용 가능하게 만들었듯이, TCP 프로토콜이 애플리케이션 계층을 전송 계층과 분리했듯이, MCP는 [Claude](https://claude.ai/)와 같은 LLM 기반 애플리케이션과 [Zed](https://zed.dev)나 [Cursor](https://www.cursor.com)와 같은 편집기가 다른 도메인과 상호 운용될 수 있도록 합니다. Tuist는 CLI를 통해 로컬 서버를 제공하여 _앱 개발 환경_과 상호작용할 수 있습니다. 클라이언트 앱을 이 서버에 연결하면 언어를 사용해 프로젝트와 상호작용할 수 있습니다. 이 페이지에서는 MCP 서버의 설정 방법과 기능에 대해 알아볼 수 있습니다. > [!NOTE]\ > Tuist MCP 서버는 Xcode의 최신 프로젝트를 기준으로 상호작용할 프로젝트를 결정합니다. ## 설정 ### [Claude](https://claude.ai) [Claude desktop](https://claude.ai/download)을 사용한다면, Claude 환경을 구성하기위해 tuist mcp setup claude 명령어를 수행할 수 있습니다. 또한 `~/Library/Application\ Support/Claude/claude_desktop_config.json`의 파일을 직접 수정하여 Tuist MCP 서버를 추가할 수 있습니다: :::code-group ```json [Global Tuist installation (e.g. Homebrew)] { "mcpServers": { "tuist": { "command": "tuist", "args": ["mcp", "start"] } } } ``` ```json [Mise installation] { "mcpServers": { "tuist": { "command": "mise", "args": ["x", "tuist@latest", "--", "tuist", "mcp", "start"] // Or tuist@x.y.z to fix the version } } } ``` ::: ## Capabilities 다음 섹션에서 Tuist MCP 서버의 기능에 대해 배워봅니다. ### 리소스 #### 최근 프로젝트와 워크스페이스 Tuist는 최근에 작업한 Xcode 프로젝트와 워크스페이스를 기록하여, 애플리케이션의 의존성 그래프에 접근이 가능하기 때문에 더 나은 분석이 가능합니다. 이런 데이터를 조회하여 다음과 같이 프로젝트의 구조와 관계에 대해 자세히 알 수 있습니다: - 특정 타겟의 직접 의존성 및 전이적 의존성은 무엇입니까? - 가장 많은 소스 파일을 포함한 타겟과 얼마나 많은 파일을 포함하고 있습니까? - 그래프 내에 모든 정적 제품(예: 정적 라이브러리, 정적 프레임워크)는 무엇입니까? - 모든 타겟을 알파벳 순으로 정렬하고, 이름 및 제품 타입(예: 앱, 프레임워크, 유닛 테스트)과 함께 나열할 수 있습니까? - 특정 프레임워크나 외부에 의존하는 타겟은 무엇입니까? - 프로젝트의 모든 타겟에 포함된 총 소스 파일의 갯수는 몇 개입니까? - 타겟 간의 순환 의존성이 존재하며, 어디에 있습니까? - 특정 리소스(예: 이미지, plist 파일)를 사용하는 타겟은 무엇입니까? - 그래프에서 가장 깊은 의존성 체인은 무엇이며 어떤 타겟이 포함됩니까? - 모든 테스트 타겟과 그 타겟과 연관된 앱이나 프레임워크 타겟을 보여줄 수 있습니까? - 최근 빌드 시간을 기준으로 빌드 시간이 가장 오래 걸린 타겟은 무엇입니까? - 특정 두 타겟의 의존성 차이는 무엇입니까? - 프로젝트에서 사용하지 않는 소스 파일이나 리소스가 있습니까? - 어떤 타겟이 공통된 의존성을 공유하며, 그 의존성은 무엇입니까? Tuist를 사용하면, Xcode 프로젝트를 더 깊이 알 수 있으며, 이를 통해 복잡한 설정도 쉽게 이해하고 최적화하며 효과적으로 관리할 수 있습니다! --- URL: "/ko/contributors/translate" LLMS_URL: "/ko/contributors/translate.md" title: "Translate" titleTemplate: ":title · Contributors · Tuist" description: "이 문서는 Tuist의 개발을 위한 원칙을 설명합니다." --- # Translate {#translate} 언어는 이해의 장벽이 될 수 있습니다. 우리는 Tuist가 가능한 많은 사람들에게 접근 가능하도록 하려고 합니다. Tuist에서 지원하지 않는 언어를 사용한다면, Tuist를 번역하여 우리를 도울 수 있습니다. 번역을 유지하는 것은 지속적인 노력이 필요하므로, 번역을 도와 줄 기여자가 있다면 해당 언어를 추가합니다. 현재 지원하고 있는 언어는 다음과 같습니다: - 영어 - 한국어 - 일본어 - 러시아어 > [!TIP] 새로운 언어 요청\ > Tuist에 새로운 언어를 지원해야 된다면, 커뮤니티에 의논할 수 있도록 [커뮤니티 포럼에 주제](https://community.tuist.io/c/general/4)를 새로 생성해 주세요. ## 번역 방법 우리는 번역을 관리하기 위해 [Crowdin](https://crowdin.com/)을 사용합니다. 먼저, 기여하고 싶은 프로젝트로 이동합니다: - [Documentation](https://crowdin.com/project/tuist-documentation) - [Website](https://crowdin.com/project/tuist-documentation) 번역을 시작하려면 계정이 필요합니다. GitHub으로 로그인 할 수 있습니다. 접근 권한이 생기면 번역을 시작할 수 있습니다. 번역 가능한 리소스 목록을 확인할 수 있습니다. 리소스를 클릭하면 편집창이 열리고, 기존 언어는 왼편에 번역한 언어는 오른편에 나타나는 화면을 볼 수 있습니다. 오른편에 번역 내용을 작성하고 변경 사항을 저장합니다. 번역이 업데이트 되면 Crowdin은 자동으로 리포지토리에 저장하여 Pull Request를 열고, 메인테이너는 해당 내용을 검토하여 병합합니다. > [!IMPORTANT] 번역 언어의 리소스를 수정하지 마세요 > Crowdin은 기존 언어와 번역 언어를 결합 하기 위해 파일을 분할합니다. 기존 언어를 수정하면 바인딩이 끊어지고 예상치 못한 결과를 가져올 수 있습니다. ## 가이드라인 번역할 때 따라야 하는 가이드라인은 다음과 같습니다. ### 커스텀 컨테이너와 GitHub alert [커스텀 컨테이너 (Custom Container)](https://vitepress.dev/guide/markdown#custom-containers) 또는 [GitHub Alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts)을 번역할 때는 **경고 타입은 번역하지 않고** 제목과 내용만 번역합니다. :::details GitHub Alert 예제 ````markdown > [!WARNING] 루트 변수 > 매니페스트의 루트에 있어야 하는 변수는... // Instead of > [!주의] 루트 변수 > 매니페스트의 루트에 있어야 하는 변수는... ``` ::: ::: details Example with custom container ```markdown ::: warning 루트 변수\ 매니페스트의 루트에 있어야 하는 변수는... ::: # Instead of ::: 주의 루트 변수\ 매니페스트의 루트에 있어야 하는 변수는... ::: ```` ::: ### 제목 제목을 번역할 때, ID는 번역하지 않고 제목만 번역합니다. 예를 들어, 다음의 제목을 번역할 때: ```markdown # Add dependencies {#add-dependencies} ``` 이것은 다음과 같이 번역합니다 (ID는 번역하지 않습니다): ```markdown # 의존성 추가하기 {#add-dependencies} ``` --- URL: "/ko/contributors/principles" LLMS_URL: "/ko/contributors/principles.md" title: "Principles" titleTemplate: ":title · Contributors · Tuist" description: "이 문서는 Tuist의 개발을 위한 원칙을 설명합니다." --- # Principles {#principles} 이 페이지는 Tuist의 디자인과 개발의 기둥이 되는 원칙을 설명합니다. 이것은 프로젝트와 함께 발전하고 프로젝트 기반과 잘 부합하는 지속 가능한 성장을 보장하기 위한 것입니다. ## 기본 규칙 {#default-to-conventions} Tuist가 존재하는 이유 중에 하나는 Xcode가 규칙이 약해 확장과 유지 보수가 어려운 복잡한 프로젝트를 생성하기 때문입니다. 이런 이유로 Tuist는 간단하고 철저하게 설계된 규칙을 기본적으로 사용합니다. **개발자는 규칙을 제외할 수 있지만, 그것은 자연스럽지 않습니다.** 예를 들어, 제공된 인터페이스를 사용하여 타겟의 의존성을 정의하는 규칙이 있습니다. 이를 통해 Tuist는 제대로 된 구성으로 프로젝트가 생성되도록 보장합니다. 개발자는 빌드 설정으로 의존성을 정의할 수 있지만, 이것은 암묵적으로 정의하므로 일부 규칙을 따라야 하는 `tuist graph` 또는 `tuist cache` 와 같은 Tuist 기능의 규칙에 어긋납니다. 규칙을 따라야 하는 이유는 개발자를 대신해서 많은 결정을 해주면 개발자는 앱의 기능을 만드는데 더 집중할 수 있기 때문입니다. 많은 프로젝트에서 규칙이 없다면, 이전에 결정한 사항과 일관성 없는 결정을 하게 되고 결과적으로 관리하기 어렵게 됩니다. ## Manifest는 진실 공급원 {#manifests-are-the-source-of-truth} 여러 층을 가지는 구성과 그 구성 간의 계약은 프로젝트 설정을 이해하고 유지하기 어렵게 만듭니다. 일반적인 프로젝트를 생각해 봅시다. 프로젝트의 정의는 `.xcodeproj ` 디렉토리, 스크립트 (예: `Fastfiles `) 에 CLI, 파이프라인에 CI 로직이 있습니다. 이 세 가지 층은 서로의 계약을 유지해야 합니다. _프로젝트에서 무언가 변경하고 일주일 후에 릴리즈 스크립트가 손상되었다고 깨달은 적이 얼마나 자주 있었습니까?_ 우리는 단일 진실 공급원 인 Manifest 파일로 이것을 단순하게 변경할 수 있습니다. 이 파일은 개발자가 Xcode 프로젝트를 생성하는데 필요한 정보를 Tuist에 제공합니다. 게다가, 로컬 또는 CI 환경에서 프로젝트를 빌드하기 위한 표준 명령어를 사용할 수 있습니다. **Tuist는 복잡한 설정을 관리하고 가능한 프로젝트를 명확하게 설명하기 위해 간단하고 안전하며 즐거운 인터페이스를 제공해야 합니다.** ## 암시적인 것을 명시적으로 만들기 {#make-the-implicit-explicit} Xcode는 함축적 구성을 제공합니다. 그 좋은 예는 암시적으로 정의된 의존성을 유추하는 것입니다. 암시성은 구성이 간단한 작은 프로젝트에서는 좋지만, 프로젝트가 커질 수록 느리거나 이상한 동작을 야기 시킵니다. Tuist는 Xcode의 함축적 동작에 대해 명시적으로 API를 제공해야 합니다. 또한, Xcode 암시적 정의를 지원하지만 개발자가 명시적 접근 방식을 선택하도록 구현해야 합니다. Xcode 암시성과 복잡성을 모두 제공하면 Tuist 채택이 용이해지고, 나중에 팀은 암시성을 제거하는 시간을 할애할 수 있습니다. 이것의 좋은 예는 의존성 정의입니다. 개발자는 Build Settings과 Build Phases에서 의존성 정의를 할 수 있지만, Tuist는 더 좋은 API를 제공합니다. **API를 명시적으로 설계하면 그렇지 않은 프로젝트에서 불가능한 일부 검사와 최적화를 Tuist가 수행할 수 있습니다.** 게다가, 의존성 그래프를 표현하는 `tuist graph` 또는 바이너리로 모든 타겟을 캐시하는 `tuist cache` 와 같은 기능을 사용할 수 있습니다. > [!TIP]\ > Xcode의 기능을 이식해 달라는 요청은 간단하고 명확한 API를 통해 개념을 단순화할 수 있는 기회로 여겨야 합니다. ## 단순함을 유지 {#keep-it-simple} Xcode 프로젝트를 확장할 때 주요 과제 중 하나는 **Xcode가 사용자에게 많은 복잡성을 보인다는** 사실에서 비롯됩니다. 이로 인해, 팀은 높은 버스 팩터를 가지고 팀에 일부 인원만 프로젝트와 빌드 시스템에서 발생하는 오류를 이해합니다. 이러한 상황은 팀이 소수 인원에 의지하게 되므로 좋지 않은 상황입니다. Xcode는 훌륭한 툴이지만, 수 년간의 개선과 새로운 플랫폼, 그리고 프로그래밍 언어가 반영되면서 단순함을 유지하는데 어려움을 겪었습니다. Tuist는 간단한 업무는 재미와 동기부여를 하기 때문에 단순함을 유지해야 합니다. 어느 누구도 컴파일 마지막에 발생한 오류를 디버깅 하거나 단말에 앱이 실행되지 않는 이유를 이해하는데 시간을 할애하고 싶어 하지 않습니다. Xcode는 해당 작업을 빌드 시스템에 위임하고 어떤 경우에는 오류를 실행 가능하도록 변환하는데 매우 형편 없습니다. _"framework X not found"_ 오류가 발생하여 어떻게 해야 할지 모르는 경우가 있었습니까? 해당 버그에 대해 가능한 근본 원인을 받았다고 상상해 보시기 바랍니다. ## 개발자의 경험에서 시작 {#start-from-the-developers-experience} Xcode의 혁신이 부족하거나 다른 프로그래밍 환경보다 많지 않은 이유 중 하나는 **기존 해결책에서 문제 분석을 시작하기** 때문입니다. 그 결과로 오늘날 찾은 대부분의 해결책은 동일한 아이디어와 워크플로우를 중심으로 돌아갑니다. 기존 해결책을 포함하는 것은 좋지만, 이것이 우리의 창의성을 제한하면 안됩니다. 우리는 [Tom Preston](https://tom.preston-werner.com/)이 [팟캐스트](https://tom.preston-werner.com/)에서 말한 것처럼 생각하고 싶어합니다: _"대부분의 일은 머릿속에 있는 것은 무엇이든 우주의 제약 내에서 코드로 이룰 수 있습니다"._ **개발자 경험이 어떤지 상상한다면** 실현하는 것은 시간 문제입니다 — 개발자 경험으로 문제를 분석하기 시작하면 사용자가 사용하고 싶은 해결책을 찾을 수 있습니다. 모든 사람이 계속해서 불평을 가지는 불편함이더라도 우리는 모두가 하는 것을 따르고 싶은 유혹을 느낄 수 있습니다. 그러면 안됩니다. 앱을 아카이브 한다고 상상해 보시기 바랍니다. 어떻게 해야 할까요? 코드 서명은 어떻게 하면 좋을까요? Tuist로 어떤 프로세스를 간소화 할 수 있을까요? 예를 들어, [Fastlane](https://fastlane.tools/)을 추가하는 것은 이 문제에 대한 해결책 입니다. 우리는 "왜"라는 질문을 통해 문제의 근원을 찾을 수 있습니다. 이 질문으로 범위가 좁혀지면 Tuist가 어떻게 도움을 줄 수 있는지 생각해 볼 수 있습니다. 해결책이 Fastlane과 통합하는 것일 수도 있지만, 절충안을 결정하기 전에 제시할 수 있는 다른 해결책을 무시하지 않는 것이 중요합니다. ## 오류 발생 가능성 {#errors-can-and-will-happen} 개발자들은 오류가 발생 여부를 무시하고 싶은 유혹이 있습니다. 그 결과, 이상적인 시나리오로 소프트웨어를 설계하고 테스트 합니다. Swift와 잘 설계된 코드는 일부 오류를 막는데 도움을 주지만, 일부 오류는 제어가 불가능하기 때문에 모든 오류를 막을 수는 없습니다. 사용자가 항상 인터넷이 연결되어 있거나 시스템 명령이 성공적으로 반환할 것이라고 생각할 수 없습니다. Tuist가 실행되는 환경은 우리가 제어할 수 있는 샌드박스 환경이 아니므로 Tuist가 어떻게 변경되고 영향을 받을 수 있는지 이해해야 합니다. 오류를 제대로 처리하지 못하면 사용자에게 안좋은 경험을 제공하고, 프로젝트의 신뢰를 잃을 수 있습니다. 우리는 Tuist의 오류를 표현하는 방식을 포함하여 모든 부분을 사용자가 즐기길 바랍니다. 우리는 사용자의 입장에서 생각해야 하고, 무슨 오류가 나타날지 생각해야 합니다. 프로그래밍 언어는 오류를 전파하는 통신 채널이고 오류의 목적지가 사용자라면 오류는 타겟 (사용자) 이 사용하는 언어로 작성되어야 합니다. 무엇이 일어나는지 알 수 있게 충분한 정보를 포함해야 하며 상관없는 정보는 숨겨야 합니다. 또한 사용자에게 복구하기 위해 취할 수 있는 단계를 알려줌으로써 조치가 가능해야 합니다. 그리고 마지막으로 테스트 케이스로 실패 시나리오를 고려해야 합니다. 그것은 오류를 제대로 처리하는지 확인할 뿐만 아니라, 개발자가 해당 논리를 깨는 것을 방지합니다. --- URL: "/ko/contributors/issue-reporting" LLMS_URL: "/ko/contributors/issue-reporting.md" title: "Issue reporting" titleTemplate: ":title · Contributors · Tuist" description: "버그 리포트를 통해 Tuist에 어떻게 기여하는지 알아봅니다." --- # Issue reporting {#issue-reporting} Tuist를 사용하면 버그나 예상치 못한 동작을 경험할 수 있습니다. 이런 경우, 문제를 리포트 해주면 수정할 수 있습니다. ## GitHub 이슈는 티켓팅 플랫폼 {#github-issues-is-our-ticketing-platform} 문제는 Slack 이나 다른 플랫폼이 아닌 [GitHub issues](https://github.com/tuist/tuist/issues)에 리포트 해야 합니다. GitHub는 문제를 추적하고 관리하는데 유용하고, 코드베이스에 가까우며, 문제 진행 사항을 추적할 수 있게 합니다. 또한, GitHub는 문제에 대해 더 생각하고 더 많은 내용을 제공하도록 문제의 설명을 자세하게 적도록 유도합니다. ## 설명의 중요성 {#context-is-crucial} 충분한 설명이 없는 문제는 불완전한 것으로 간주되고 추가 내용을 제공해야 될 수도 있습니다. 추가 내용을 제공하지 않으면, 문제는 종료될 수 있습니다. 다음과 같이 생각해보시기 바랍니다: 더 많은 내용을 제공하면 문제를 더 쉽게 이해하고 해결할 수 있습니다. 그래서 문제가 해결되길 원하면, 가능한 자세한 내용을 제공해야 합니다. 다음 질문에 답변 해보시기 바랍니다: - 무엇을 하려고 했나요? - 그래프는 어떻게 생겼나요? - Tuist의 어떤 버전을 사용하고 있나요? - 이것이 당신을 방해하나요? 우리는 또한 최소한의 **재현 가능한 프로젝트**를 요구할 수도 있습니다. ## 재현 가능한 프로젝트 {#reproducible-project} ### 재현 가능한 프로젝트란 무엇입니까? {#what-is-a-reproducible-project} 재현 가능한 프로젝트는 문제를 보여주는 작은 Tuist 프로젝트 입니다 - 이런 문제는 Tuist의 버그일 수도 있습니다. 재현 가능한 프로젝트는 버그를 명확하게 보여주는 최소한의 기능을 포함해야 합니다. ### 왜 재현 가능한 테스트 케이스를 만들어야 합니까? {#why-should-you-create-a-reproducible-test-case} 재현 가능한 프로젝트로 문제의 원인을 분리할 수 있으며, 이는 문제를 해결하는 첫 번째 단계입니다. 버그 리포트에서 가장 중요한 것은 버그를 재현하는 경로를 정확하게 설명하는 것입니다. 재현 가능한 프로젝트는 버그를 유발하는 특정 환경을 공유하는 좋은 방법입니다. 재현 가능한 프로젝트는 문제 해결에 도움을 줄 수 있는 사람들에게 도움을 주는 가장 좋은 방법입니다. ### 재현 가능한 프로젝트를 생성하는 방법 {#steps-to-create-a-reproducible-project} - 새로운 git 리포지터리를 생성합니다. - 리포지터리 디렉토리에서 `tuist init`을 사용하여 프로젝트를 초기화 합니다. - 발견된 오류를 재현하는데 필요한 코드를 추가합니다. - 코드를 게시 (당신의 GitHub 계정이 이 작업을 수행하기 좋습니다) 한 다음에 이슈를 생성할 때 링크를 게시합니다. ### 재현 가능한 프로젝트의 이점 {#benefits-of-reproducible-projects} - **더 작은 크기:** 오류만 남기고 모두 삭제했으므로, 버그를 찾기 위해 복잡한 과정을 수행하지 않아도 됩니다. - **비밀 코드를 게시할 필요가 없음:** 많은 이유로 사이트에 게시할 수 없을 수 있습니다. 작은 부분으로 재현 가능한 테스트 케이스를 만들면 비밀 코드를 노출하지 않고 문제를 확인할 수 있습니다. - **버그 증명:** 버그는 컴퓨터의 설정 조합으로 인해 발생할 수 있습니다. 재현 가능한 테스트 케이스는 기여자가 해당 빌드를 다운 받아 빌드하고 테스트할 수 있습니다. 이것은 문제의 원인을 확인하고 범위를 좁히는데 도움을 줍니다. - **버그 해결에 대한 도움 받기:** 문제를 재현할 수 있으면, 문제를 해결할 가능성이 있습니다. 버그를 재현하지 않고는 문제를 해결하는 것은 거의 불가능 합니다. --- URL: "/ko/contributors/get-started" LLMS_URL: "/ko/contributors/get-started.md" title: "Get started" titleTemplate: ":title · Contributors · Tuist" description: "다음 가이드를 통해 Tuist 기여를 시작합니다." --- # Get started {#get-started} iOS 처럼 Apple 플랫폼의 앱을 빌드해 본 경험이 있다면, Tuist 에 코드를 추가하는 것은 다르지 않습니다. 앱 개발과 비교해서 두 가지 차이점이 있습니다: - **CLI와의 상호작용은 터미널을 통해 일어납니다.** 사용자가 원하는 작업을 Tuist로 실행하면 성공 또는 상태 코드를 반환합니다. 실행하는 동안 사용자는 동작 내용과 오류에 대한 정보를 확인할 수 있습니다. 제스처 또는 그래픽 상호작용은 없고, 사용자의 의도만 존재합니다. - **입력을 기다리면서 프로세스를 활성 상태로 유지하는 런루프가 존재하지 않습니다.** 이것은 시스템 또는 사용자 이벤트를 수신할 때 iOS 앱과 유사합니다. CLI는 동일 프로세스로 실행되고 작업이 완료되면 종료됩니다. 비동기 작업은 [DispatchQueue](https://developer.apple.com/documentation/dispatch/dispatchqueue) 또는 [structured concurrency](https://developer.apple.com/tutorials/app-dev-training/managing-structured-concurrency) 와 같은 시스템 API를 사용하여 수행할 수 있지만, 비동기 작업이 수행되는 동안 프로세스가 실행 중인지 확인해야 합니다. 그렇지 않으면, 프로세스는 비동기 작업을 종료합니다. Swift에 대한 경험이 없다면, 언어와 Foundation API에서 자주 사용하는 요소에 대해 익숙해 지도록 [Apple’s official book](https://docs.swift.org/swift-book/)을 추천합니다. ## 최소 요구 사항 {#minimum-requirements} Tuist에 기여하기 위해 최소 요구 사항은 다음과 같습니다: - macOS 14.0+ - Xcode 16.3+ ## 로컬에 프로젝트 설정하기 {#set-up-the-project-locally} 프로젝트에 작업을 시작하려면 다음과 같습니다: - `git clone git@github.com:tuist/tuist.git` 수행하여 리포지터리를 복사합니다. - 개발 환경을 위해 Mise 를 [설치](https://mise.jdx.dev/getting-started.html) 합니다. - `mise install` 을 실행하여 Tuist에 필요한 시스템 종속성을 설치합니다. - `tuist install` 을 실행하여 Tuist에 필요한 외부 종속성을 설치합니다. - (선택 사항) `tuist auth login`을 실행하여 Tuist Cache에 접근합니다. - `tuist generate` 를 실행하여 Tuist를 사용하는 Tuist Xcode 프로젝트를 생성합니다. **생성된 프로젝트는 자동으로 열립니다**. 프로젝트 생성 없이 프로젝트를 열려면, `open Tuist.xcworkspace` 를 실행하거나 Finder 를 사용합니다. > [!NOTE] XED. > `xed .`를 사용하여 프로젝트를 열면, Tuist로 생성한 프로젝트가 열리지 않고, 패키지가 열립니다. Tuist로 생성한 프로젝트를 사용하는 것을 권장합니다. ## 프로젝트 수정하기 {#edit-the-project} 의존성을 추가하거나 타겟을 조정하는 것과 같이 프로젝트 수정이 필요한 경우, `tuist edit` 명령어를 사용할 수 있습니다. 거의 사용되지 않지만, 이런 명령어가 존재한다는 것을 알아두면 좋습니다. ## Tuist 실행하기 {#run-tuist} ### Xcode {#from-xcode} 생성된 Xcode 프로젝트에서 `tuist`를 실행하려면, `tuist` 스킴을 수정하고 명령어에 전달할 인수를 설정합니다. 예를 들어, `tuist generate` 명령어를 실행하려면, 프로젝트 생성 후에 프로젝트가 열리지 않도록 `generate --no-open` 인수를 설정할 수 있습니다. ![Tuist로 generate 명령어를 실행하기 위한 스킴 구성의 예](/images/contributors/scheme-arguments.png) 또한 생성되는 프로젝트의 루트를 작업 디렉토리로 설정해야 합니다. 모든 명령어를 적용하는 `--path` 인수를 사용할 수도 있고, 아래와 같이 스킴에 작업 디렉토리를 구성할 수도 있습니다: ![Tuist를 실행 하기위해 작업 디렉토리를 설정하는 예](/images/contributors/scheme-working-directory.png) > [!WARNING] PROJECTDESCRIPTION COMPILATION\ > `tuist` CLI는 빌드된 디렉토리에 `ProjectDescription` 프레임워크의 존재에 따라 달라집니다. `ProjectDescription` 프레임워크를 찾을 수 없어 `tuist` 실행이 실패하면 먼저 `Tuist-Workspace` 스킴을 빌드합니다. ### Terminal {#from-the-terminal} Tuist의 `run` 명령어를 통해 `tuist`를 수행할 수 있습니다: ```bash tuist run tuist generate --path /path/to/project --no-open ``` 또한 Swift Package Manager를 통해 직접 실행할 수도 있습니다: ```bash swift build --product ProjectDescription swift run tuist generate --path /path/to/project --no-open ``` --- URL: "/ko/contributors/code-reviews" LLMS_URL: "/ko/contributors/code-reviews.md" title: "Code reviwes" titleTemplate: ":title · Contributors · Tuist" description: "코드 리뷰를 통해 Tuist에 어떻게 기여하는지 알아봅니다." --- # Code reviwes {#code-reviews} Pull Reqeust 검토는 코드에 기여하는 일반적인 방식입니다. CI (Continuous Integration) 를 통해 코드의 동작을 확인하더라도 이 방식은 충분하지 않습니다. 디자인, 코드 구조 및 아키텍처, 테스트 품질, 또는 오타는 기여 특성을 자동화 할 수 없습니다. 다음 섹션에서 코드 리뷰 프로세스의 다양한 부분을 설명합니다. ## 가독성 {#readability} 코드가 의도를 명확하게 표현하고 있는지? **코드를 파악하는데 많은 시간이 소요된다면, 이 코드 구현을 개선해야 합니다.** 이런 경우, 코드를 이해하기 쉽게 더 작은 추상화로 분리하는 것을 제안합니다. 다른 방법으로는, 코드 마지막에 구현에 대한 설명을 주석으로 추가할 수 있습니다. PR 설명과 같이 짧은 시간에 코드를 이해할 수 있는지 자문해 보시기 바랍니다. ## 작은 PR {#small-pull-requests} 큰 PR은 검토하기 어렵고 세부 내용을 놓치기 쉽습니다. PR이 너무 커서 관리하기 어렵다면, 작성자에게 작게 나누도록 제안합니다. > [!NOTE] 예외\ > 변경 사항이 밀접하게 결합되어 있어 분리할 수 없는 경우처럼, PR을 분리할 수 없는 몇 가지 사항이 존재합니다. 이러한 경우 작성자는 변경 사항과 그 이유를 자세히 설명해야 합니다. ## 일관성 {#consistency} 변경 사항이 프로젝트의 다른 부분과 일관성을 유지하는 것이 중요합니다. 일관성을 유지하지 않으면 유지 보수에 복잡성을 증대하므로 이를 피해야 합니다. 사용자에게 메세지를 출력하거나 오류를 보고하는 접근 방식이 있다면 이 방식을 유지해야 합니다. 작성자가 이러한 프로젝트의 표준을 동의하지 않으면, 더 자세히 논의할 수 있는 이슈 (Issue) 생성을 제안합니다. ## 테스트 {#tests} 테스트는 변경된 코드 자격을 증명합니다. PR의 코드는 테스트 되어야 하고 모두 통과해야 합니다. 좋은 테스트는 일관되게 같은 결과를 도출하고 이해하고 유지하기 쉬운 테스트 입니다. 검토자 (Reviewer) 는 코드 구현 리뷰에 많은 시간을 할애하지만, 테스트도 코드이므로 마찬가지로 중요합니다. ## 단절적 변경 사항 {#breaking-changes} 단절적 변경 사항 (Breaking change) 은 Tuist 사용자에게 안 좋은 사용자 경험입니다. 코드 기여는 중대한 변경 사항이 없으면, 단절적 변경 사항 (Breaking change) 은 피해야 합니다. 단절적 변경 사항 (Breaking change) 없이 Tuist의 인터페이스를 발전시킬 수 있는 많은 언어적 특성이 있습니다. 변경 사항이 단절적 변경 사항 (Breaking change) 인지 아닌지 명확하지 않을 수 있습니다. 변경 사항이 단절적 변경 사항 (Breaking change) 인지 아닌지 확인하는 방법은 fixtures 디렉토리에 있는 fixture 프로젝트에 대해 Tuist를 실행해보는 것입니다. 단절적 변경 사항 (Breaking change) 은 사용자의 입장에서 생각하고 변경 사항이 사용자에게 어떠한 영향을 줄지 생각해야 합니다. --- URL: "/ko/contributors/cli/logging" LLMS_URL: "/ko/contributors/cli/logging.md" title: "로깅" titleTemplate: ":title · CLI · Contributors · Tuist" description: "코드 리뷰를 통해 Tuist에 어떻게 기여하는지 알아봅니다." --- # 로깅 {#logging} CLI의 로깅은 [swift-log](https://github.com/apple/swift-log)의 인터페이스를 차용하고 있습니다. 이 패키지는 로깅의 세부 구현 사항을 추상화하여 CLI가 로깅 백엔드에 종속되지 않도록 합니다. 로거는 [swift-service-context](https://github.com/apple/swift-service-context)를 사용하여 의존성 주입되고 다음을 사용하여 어디서든 접근 가능합니다: ```bash ServiceContext.current?.logger ``` > [!NOTE]\ > `swift-service-context`는 `Dispatch`를 사용할 때 값을 전파하지 않는 [task locals](https://developer.apple.com/documentation/swift/tasklocal)를 사용하여 인스턴스를 전달하므로, `Dispatch`를 사용하여 비동기 코드를 실행할 때는 컨텍스트에서 인스턴스를 가져와 비동기 작업에 전달해야 합니다. ## 무엇을 로깅하는 것이 좋을까요? {#what-to-log} 로그는 CLI의 UI가 아닙니다. 로그는 이슈가 발생하였을 때 진단을 도와주는 도구입니다. 그렇기 때문에, 많은 정보를 제공할 수록 더 좋은 결과를 얻을 수 있습니다. 새로운 기능을 만들 때 자신을 예상하지 못한 동작을 발견한 개발자라고 생각하고, 어떠한 정보들을 그 개발자들에게 제공해준다면 도움이 될지 생각해보면 좋습니다. 적절한 [log level](https://www.swift.org/documentation/server/guides/libraries/log-levels.html)을 사용하고 있는 지 확인하십시요. 그렇지 않으면 개발자들이 불필요한 정보들을 필터링하기 어려워집니다. --- URL: "/ko/cli/shell-completions" LLMS_URL: "/ko/cli/shell-completions.md" title: "Shell completions" titleTemplate: ":title · CLI · Tuist" description: "Tuist 명령어를 자동으로 완성하도록 셀을 구성하는 방법에 대해 배워봅니다." --- # Shell completions Tuist가 **전역으로 설치된 경우** (예: Homebrew), 명령어와 옵션을 자동으로 완성시키기 위해 Bash와 Zsh용 셀 자동 완성을 설치할 수 있습니다. :::warning WHAT IS A GLOBAL INSTALLATION Global installation는 Shell의 `$PATH` 환경 변수에 포함된 설치를 말합니다. 즉, 터미널의 모든 디렉토리에서 `tuist`를 실행할 수 있습니다.이것은 Homebrew의 기본 설치 방법입니다. 이것은 Homebrew의 기본 설치 방법입니다. ::: #### Zsh {#zsh} [oh-my-zsh](https://ohmyz.sh)가 설치되어 있다면, 이미 자동으로 로드되는 완성 스크립트(completion script)들이 저장된 디렉터리인 `.oh-my-zsh/completions`가 있습니다. 새로운 완성 스크립트를 해당 디렉터리의 `_tuist`라는 새 파일에 복사합니다. ```bash tuist --generate-completion-script > ~/.oh-my-zsh/completions/_tuist ``` `oh-my-zsh`가 없는 경우 함수 경로에 완성 스크립트 경로를 추가하고, 완성 스크립트 자동 로딩을 설정해야 합니다. 먼저 `~/.zshrc`에 다음 줄을 추가합니다 ```bash fpath=(~/.zsh/completion $fpath) autoload -U compinit compinit ``` 그런 다음, `~/.zsh/complication`에 디렉터리를 생성한 후, 완성 스크립트를 해당 디렉터리의 `_tuist`라는 파일에 복사합니다. ```bash tuist --generate-completion-script > ~/.zsh/completion/_tuist ``` #### Bash {#bash} bash-complement](https://github.com/scop/bash-completion)가 설치되어 있다면, 새로운 완성 스크립트를 `/usr/local/etc/bash_complement.d/_tuist` 파일에 복사하기만 하면 됩니다. ```bash tuist --generate-completion-script > /usr/local/etc/bash_completion.d/_tuist ``` bash-completion이 없으면 완성 스크립트를 직접 불러와야 합니다. 해당 스크립트를 `~/.bash_completions/`와 같은 디렉터리로 복사한 다음 `~/.bash_profile` 또는 `~/.bashrc`에 다음 줄을 추가합니다. ```bash source ~/.bash_completions/example.bash ``` #### Fish {#fish} [fish shell](https://fishshell.com)을 사용하는 경우 `~/.config/fish/completions/tuist.fish`에 새로운 자동완성 스크립트를 복사할 수 있습니다: ```bash mkdir -p ~/.config/fish/completions tuist --generate-completion-script > ~/.config/fish/completions/tuist.fish ``` --- URL: "/ko/cli/logging" LLMS_URL: "/ko/cli/logging.md" title: "로깅" titleTemplate: ":title · CLI · Tuist" description: "Tuist의 로깅 활성화와 설정 방법 배우기." --- # 로깅 {#logging} CLI는 내부적으로 메세지를 기록하여 문제 확인에 도움을 줍니다. ## 로깅 사용하여 문제 진단하기 {#diagnose-issues-using-logs} 명령어 수행이 원하는 결과를 가져오지 못한다면, 로그를 살펴보면서 문제의 원인을 파악해 볼 수 있습니다. CLI가 로그를 [OSLog](https://developer.apple.com/documentation/os/oslog)와 파일 시스템으로 전달해줍니다. 실행 시 마다, `$XDG_STATE_HOME/tuist/logs/{uuid}.log`경로에 로그 파일을 생성합니다. 환경 변수가 설정되어 있지 않다면, `$XDG_STATE_HOME`는 `~/.local/state`로 되어 있습니다. 예기치 않게 실행이 종료되었을 때, 기본적으로 CLI는 로그 경로를 출력합니다. 만일 로그 경로가 출력되지 않았다면, 위에 명시된 경로에서 로그(가장 최근의 로그)를 확인할 수 있습니다. > [!중요] > 민감한 정보는 지워지지 않으니, 로그를 공유할 때 주의하세요. ### 지속적인 통합 {#diagnose-issues-using-logs-ci} 환경 설정이 일회용인 CI에서, Tuist 로그를 추출하기 위해서 CI 파이프라인 설정을 할 수 있습니다. 아티팩트(artifacts) 추출은 CI 서비스에서 일반적으로 사용되는 기능이고, 서비스맏 설정이 다릅니다. 예를 들어, 깃헙 액션(GitHub Actions)에서는 `actions/upload-artifact` 액션을 사용해서 로그를 아티팩트로 업로드할 수 있습니다: ```yaml name: Node CI on: [push] env: XDG_STATE_HOME: /tmp jobs: build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 # ... other steps - run: tuist generate # ... do something with the project - name: Export Tuist logs uses: actions/upload-artifact@v4 with: name: tuist-logs path: /tmp/tuist/logs/*.log ``` --- URL: "/ko/cli/[command]" LLMS_URL: "/ko/cli/[command].md" editLink: false titleTemplate: ":title · CLI · Tuist" --- --- URL: "/ja/server/on-premise/metrics" LLMS_URL: "/ja/server/on-premise/metrics.md" title: "Metrics" titleTemplate: ":title | On-premise | Server | Tuist" description: "Optimize your build times by caching compiled binaries and sharing them across different environments." --- # Metrics {#metrics} You can ingest metrics gathered by the Tuist server using [Prometheus](https://prometheus.io/) and a visualization tool such as [Grafana](https://grafana.com/) to create a custom dashboard tailored to your needs. The Prometheus metrics are served via the `/metrics` endpoint on port 9091. The Prometheus' [scrape_interval](https://prometheus.io/docs/introduction/first_steps/#configuring-prometheus) should be set as less than 10_000 seconds (we recommend keeping the default of 15 seconds). ## Elixir metrics {#elixir-metrics} By default we include metrics of the Elixir runtime, BEAM, Elixir, and some of the libraries we use. The following are some of the metrics you can expect to see: - [Application](https://hexdocs.pm/prom_ex/PromEx.Plugins.Application.html) - [BEAM](https://hexdocs.pm/prom_ex/PromEx.Plugins.Beam.html) - [Phoenix](https://hexdocs.pm/prom_ex/PromEx.Plugins.Phoenix.html) - [Phoenix LiveView](https://hexdocs.pm/prom_ex/PromEx.Plugins.PhoenixLiveView.html) - [Ecto](https://hexdocs.pm/prom_ex/PromEx.Plugins.Ecto.html) - [Oban](https://hexdocs.pm/prom_ex/PromEx.Plugins.Oban.html) We recommend checking those pages to know which metrics are available and how to use them. ## Runs metrics {#runs-metrics} A set of metrics related to Tuist Runs. ### `tuist_runs_total` (counter) {#tuist_runs_total-counter} The total number of Tuist Runs. #### Tags {#tuist-runs-total-tags} | Tag | Description | | -------- | ------------------------------------------------------------------------------------------- | | `name` | The name of the `tuist` command that was run, such as `build`, `test`, etc. | | `is_ci` | A boolean indicating if the executor was a CI or a developer's machine. | | `status` | `0` in case of `success`, `1` in case of `failure`. | ### `tuist_runs_duration_milliseconds` (histogram) {#tuist_runs_duration_milliseconds-histogram} The total duration of each tuist run in milliseconds. #### Tags {#tuist-runs-duration-miliseconds-tags} | Tag | Description | | -------- | ------------------------------------------------------------------------------------------- | | `name` | The name of the `tuist` command that was run, such as `build`, `test`, etc. | | `is_ci` | A boolean indicating if the executor was a CI or a developer's machine. | | `status` | `0` in case of `success`, `1` in case of `failure`. | ## Cache metrics {#cache-metrics} A set of metrics related to the Tuist Cache. ### `tuist_cache_events_total` (counter) {#tuist_cache_events_total-counter} The total number of binary cache events. #### Tags {#tuist-cache-events-total-tags} | Tag | Description | | ------------ | ---------------------------------------------------------------------- | | `event_type` | Can be either of `local_hit`, `remote_hit`, or `miss`. | ### `tuist_cache_uploads_total` (counter) {#tuist_cache_uploads_total-counter} The number of uploads to the binary cache. ### `tuist_cache_uploaded_bytes` (sum) {#tuist_cache_uploaded_bytes-sum} The number of bytes uploaded to the binary cache. ### `tuist_cache_downloads_total` (counter) {#tuist_cache_downloads_total-counter} The number of downloads to the binary cache. ### `tuist_cache_downloaded_bytes` (sum) {#tuist_cache_downloaded_bytes-sum} The number of bytes downloaded from the binary cache. --- ## Previews metrics {#previews-metrics} A set of metrics related to the previews feature. ### `tuist_previews_uploads_total` (sum) {#tuist_previews_uploads_total-counter} The total number of previews uploaded. ### `tuist_previews_downloads_total` (sum) {#tuist_previews_downloads_total-counter} The total number of previews downloaded. --- ## Storage metrics {#storage-metrics} A set of metrics related to the storage of artifacts in a remote storage (e.g. s3). > [!TIP] > These metrics are useful to understand the performance of the storage operations and to identify potential bottlenecks. ### `tuist_storage_get_object_size_size_bytes` (histogram) {#tuist_storage_get_object_size_size_bytes-histogram} The size (in bytes) of an object fetched from the remote storage. #### Tags {#tuist-storage-get-object-size-size-bytes-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_size_duration_miliseconds` (histogram) {#tuist_storage_get_object_size_duration_miliseconds-histogram} The duration (in milliseconds) of fetching an object size from the remote storage. #### Tags {#tuist-storage-get-object-size-duration-miliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_size_count` (counter) {#tuist_storage_get_object_size_count-counter} The number of times an object size was fetched from the remote storage. #### Tags {#tuist-storage-get-object-size-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_delete_all_objects_duration_milliseconds` (histogram) {#tuist_storage_delete_all_objects_duration_milliseconds-histogram} The duration (in milliseconds) of deleting all objects from the remote storage. #### Tags {#tuist-storage-delete-all-objects-duration-milliseconds-tags} | Tag | Description | | -------------- | -------------------------------------------------------------------------------- | | `project_slug` | The project slug of the project whose objects are being deleted. | ### `tuist_storage_delete_all_objects_count` (counter) {#tuist_storage_delete_all_objects_count-counter} The number of times all project objects were deleted from the remote storage. #### Tags {#tuist-storage-delete-all-objects-count-tags} | Tag | Description | | -------------- | -------------------------------------------------------------------------------- | | `project_slug` | The project slug of the project whose objects are being deleted. | ### `tuist_storage_multipart_start_upload_duration_milliseconds` (histogram) {#tuist_storage_multipart_start_upload_duration_milliseconds-histogram} The duration (in milliseconds) of starting an upload to the remote storage. #### Tags {#tuist-storage-multipart-start-upload-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_multipart_start_upload_duration_count` (counter) {#tuist_storage_multipart_start_upload_duration_count-counter} The number of times an upload was started to the remote storage. #### Tags {#tuist-storage-multipart-start-upload-duration-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_as_string_duration_milliseconds` (histogram) {#tuist_storage_get_object_as_string_duration_milliseconds-histogram} The duration (in milliseconds) of fetching an object as a string from the remote storage. #### Tags {#tuist-storage-get-object-as-string-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_as_string_count` (count) {#tuist_storage_get_object_as_string_count-count} The number of times an object was fetched as a string from the remote storage. #### Tags {#tuist-storage-get-object-as-string-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_check_object_existence_duration_milliseconds` (histogram) {#tuist_storage_check_object_existence_duration_milliseconds-histogram} The duration (in milliseconds) of checking the existence of an object in the remote storage. #### Tags {#tuist-storage-check-object-existence-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_check_object_existence_count` (count) {#tuist_storage_check_object_existence_count-count} The number of times the existence of an object was checked in the remote storage. #### Tags {#tuist-storage-check-object-existence-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_generate_download_presigned_url_duration_milliseconds` (histogram) {#tuist_storage_generate_download_presigned_url_duration_milliseconds-histogram} The duration (in milliseconds) of generating a download presigned URL for an object in the remote storage. #### Tags {#tuist-storage-generate-download-presigned-url-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_generate_download_presigned_url_count` (count) {#tuist_storage_generate_download_presigned_url_count-count} The number of times a download presigned URL was generated for an object in the remote storage. #### Tags {#tuist-storage-generate-download-presigned-url-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_multipart_generate_upload_part_presigned_url_duration_milliseconds` (histogram) {#tuist_storage_multipart_generate_upload_part_presigned_url_duration_milliseconds-histogram} The duration (in milliseconds) of generating a part upload presigned URL for an object in the remote storage. #### Tags {#tuist-storage-multipart-generate-upload-part-presigned-url-duration-milliseconds-tags} | Tag | Description | | ------------- | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `part_number` | The part number of the object being uploaded. | | `upload_id` | The upload ID of the multipart upload. | ### `tuist_storage_multipart_generate_upload_part_presigned_url_count` (count) {#tuist_storage_multipart_generate_upload_part_presigned_url_count-count} The number of times a part upload presigned URL was generated for an object in the remote storage. #### Tags {#tuist-storage-multipart-generate-upload-part-presigned-url-count-tags} | Tag | Description | | ------------- | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `part_number` | The part number of the object being uploaded. | | `upload_id` | The upload ID of the multipart upload. | ### `tuist_storage_multipart_complete_upload_duration_milliseconds` (histogram) {#tuist_storage_multipart_complete_upload_duration_milliseconds-histogram} The duration (in milliseconds) of completing an upload to the remote storage. #### Tags {#tuist-storage-multipart-complete-upload-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `upload_id` | The upload ID of the multipart upload. | ### `tuist_storage_multipart_complete_upload_count` (count) {#tuist_storage_multipart_complete_upload_count-count} The total number of times an upload was completed to the remote storage. #### Tags {#tuist-storage-multipart-complete-upload-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `upload_id` | The upload ID of the multipart upload. | --- ## Projects metrics {#projects-metrics} A set of metrics related to the projects. ### `tuist_projects_total` (last_value) {#tuist_projects_total-last_value} The total number of projects. --- ## Accounts metrics {#accounts-metrics} A set of metrics related to accounts (users and organizations). ### `tuist_accounts_organizations_total` (last_value) {#tuist_accounts_organizations_total-last_value} The total number of organizations. ### `tuist_accounts_users_total` (last_value) {#tuist_accounts_users_total-last_value} The total number of users. ## Database metrics {#database-metrics} A set of metrics related to the database connection. ### `tuist_repo_pool_checkout_queue_length` (last_value) {#tuist_repo_pool_checkout_queue_length-last_value} The number of database queries that are sitting in a queue waiting to be assigned to a database connection. ### `tuist_repo_pool_ready_conn_count` (last_value) {#tuist_repo_pool_ready_conn_count-last_value} The number of database connections that are ready to be assigned to a database query. ### `tuist_repo_pool_db_connection_connected` (counter) {#tuist_repo_pool_db_connection_connected-counter} The number of connections that have been established to the database. ### `tuist_repo_pool_db_connection_disconnected` (counter) {#tuist_repo_pool_db_connection_disconnected-counter} The number of connections that have been disconnected from the database. ## HTTP metrics {#http-metrics} A set of metrics related to Tuist's interactions with other services via HTTP. ### `tuist_http_request_count` (counter) {#tuist_http_request_count-last_value} The number of outgoing HTTP requests. ### `tuist_http_request_duration_nanosecond_sum` (sum) {#tuist_http_request_duration_nanosecond_sum-last_value} The sum of the duration of the outgoing requests (including the time that they spent waiting to be assigned to a connection). ### `tuist_http_request_duration_nanosecond_bucket` (distribution) {#tuist_http_request_duration_nanosecond_bucket-distribution} The distribution of the duration of outgoing requests (including the time that they spent waiting to be assigned to a connection). ### `tuist_http_queue_count` (counter) {#tuist_http_queue_count-counter} The number of requests that have been retrieved from the pool. ### `tuist_http_queue_duration_nanoseconds_sum` (sum) {#tuist_http_queue_duration_nanoseconds_sum-sum} The time it takes to retrieve a connection from the pool. ### `tuist_http_queue_idle_time_nanoseconds_sum` (sum) {#tuist_http_queue_idle_time_nanoseconds_sum-sum} The time a connection has been idle waiting to be retrieved. ### `tuist_http_queue_duration_nanoseconds_bucket` (distribution) {#tuist_http_queue_duration_nanoseconds_bucket-distribution} The time it takes to retrieve a connection from the pool. ### `tuist_http_queue_idle_time_nanoseconds_bucket` (distribution) {#tuist_http_queue_idle_time_nanoseconds_bucket-distribution} The time a connection has been idle waiting to be retrieved. ### `tuist_http_connection_count` (counter) {#tuist_http_connection_count-counter} The number of connections that have been established. ### `tuist_http_connection_duration_nanoseconds_sum` (sum) {#tuist_http_connection_duration_nanoseconds_sum-sum} The time it takes to establish a connection against a host. ### `tuist_http_connection_duration_nanoseconds_bucket` (distribution) {#tuist_http_connection_duration_nanoseconds_bucket-distribution} The distribution of the time it takes to establish a connection against a host. ### `tuist_http_send_count` (counter) {#tuist_http_send_count-counter} The number of requests that have been sent once assigned to a connection from the pool. ### `tuist_http_send_duration_nanoseconds_sum` (sum) {#tuist_http_send_duration_nanoseconds_sum-sum} The time that it takes for requests to complete once assigned to a connection from the pool. ### `tuist_http_send_duration_nanoseconds_bucket` (distribution) {#tuist_http_send_duration_nanoseconds_bucket-distribution} The distribution of the time that it takes for requests to complete once assigned to a connection from the pool. ### `tuist_http_receive_count` (counter) {#tuist_http_receive_count-counter} The number of responses that have been received from sent requests. ### `tuist_http_receive_duration_nanoseconds_sum` (sum) {#tuist_http_receive_duration_nanoseconds_sum-sum} The time spent receiving responses. ### `tuist_http_receive_duration_nanoseconds_bucket` (distribution) {#tuist_http_receive_duration_nanoseconds_bucket-distribution} The distribution of the time spent receiving responses. ### `tuist_http_queue_available_connections` (last_value) {#tuist_http_queue_available_connections-last_value} The number of connections available in the queue. ### `tuist_http_queue_in_use_connections` (last_value) {#tuist_http_queue_in_use_connections-last_value} The number of queue connections that are in use. --- URL: "/ja/server/on-premise/install" LLMS_URL: "/ja/server/on-premise/install.md" title: "Installation" titleTemplate: ":title | On-premise | Server | Tuist" description: "Learn how to install Tuist on your infrastructure." --- # On-premise installation {#onpremise-installation} We offer a self-hosted version of the Tuist server for organizations that require more control over their infrastructure. This version allows you to host Tuist on your own infrastructure, ensuring that your data remains secure and private. > [!IMPORTANT] ENTERPRISE CUSTOMERS ONLY > The on-premise version of Tuist is available only for organizations on the Enterprise plan. If you are interested in this version, please reach out to [contact@tuist.dev](mailto:contact@tuist.dev). ## Release cadence {#release-cadence} The Tuist server is **released every Monday** and the version name follows the convention name `{MAJOR}.YY.MM.DD`. The date component is used to warn the CLI user if their hosted version is 60 days older than the release date of the CLI. It's crucial that on-premise organizations keep up with Tuist updates to ensure their developers benefit from the most recent improvements and that we can drop deprecated features with the confidence that we are not breaking any of the on-premise setups. The major component of the CLI is used to flag breaking changes in the Tuist server that will require coordination with the on-premise users. You should not expect us to use it, and in case we needed, rest asure we'll work with you in making the transition smooth. > [!NOTE] RELEASE NOTES > You'll be given access to a `tuist/registry` repository associated with the registry where images are published. Every new released will be published in that repository as a GitHub release and will contain release notes to inform you about what changes come with it. ## Runtime requirements {#runtime-requirements} This section outlines the requirements for hosting the Tuist server on your infrastructure. ### Running Docker-virtualized images {#running-dockervirtualized-images} We distribute the server as a [Docker](https://www.docker.com/) image via [GitHub’s Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry). To run it, your infrastructure must support running Docker images. Note that most infrastructure providers support it because it’s become the standard container for distributing and running software in production environments. ### Postgres database {#postgres-database} In addition to running the Docker images, you’ll need a [Postgres database](https://www.postgresql.org/) to store relational data. Most infrastructure providers include Posgres databases in their offering (e.g., [AWS](https://aws.amazon.com/rds/postgresql/) & [Google Cloud](https://cloud.google.com/sql/docs/postgres)). For performant analytics, we use a [Timescale Postgres extension](https://www.timescale.com/). You need to make sure that TimescaleDB is installed on the machine running the Postgres database. Follow the installation instructions [here](https://docs.timescale.com/self-hosted/latest/install/) to learn more. If you are unable to install the Timescale extension, you can set up your own dashboard using the Prometheus metrics. > [!INFO] MIGRATIONS > The Docker image's entrypoint automatically runs any pending schema migrations before starting the service. ### Storage {#storage} You’ll also need a solution to store files (e.g. framework and library binaries). Currently we support any storage that's S3-compliant. ## Configuration {#configuration} The configuration of the service is done at runtime through environment variables. Given the sensitive nature of these variables, we advise encrypting and storing them in secure password management solutions. Rest assured, Tuist handles these variables with utmost care, ensuring they are never displayed in logs. > [!NOTE] LAUNCH CHECKS > The necessary variables are verified at startup. If any are missing, the launch will fail and the error message will detail the absent variables. ### License configuration {#license-configuration} As an on-premise user, you'll receive a license key that you'll need to expose as an environment variable. This key is used to validate the license and ensure that the service is running within the terms of the agreement. | Environment variable | Description | Required | Default | Example | | -------------------- | -------------------------------------------------------------- | -------- | ------- | -------- | | `TUIST_LICENSE` | The license provided after signing the service level agreement | Yes | | `******` | > [!IMPORTANT] EXPIRATION DATE > Licenses have an expiration date. Users will receive a warning while using Tuist commands that interact with the server if the license expires in less than 30 days. If you are interested in renewing your license, please reach out to [contact@tuist.dev](mailto:contact@tuist.dev). ### Base environment configuration {#base-environment-configuration} | Environment variable | Description | Required | Default | Example | | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------ | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | | `TUIST_APP_URL` | The base URL to access the instance from the Internet | Yes | | https://cloud.tuist.io | | | `TUIST_SECRET_KEY_BASE` | The key to use to encrypt information (e.g. sessions in a cookie) | Yes | | | `c5786d9f869239cbddeca645575349a570ffebb332b64400c37256e1c9cb7ec831345d03dc0188edd129d09580d8cbf3ceaf17768e2048c037d9c31da5dcacfa` | | `TUIST_SECRET_KEY_PASSWORD` | Pepper to generate hashed passwords | No | `$TUIST_SECRET_KEY_BASE` | | | | `TUIST_SECRET_KEY_TOKENS` | Secret key to generate random tokens | No | `$TUIST_SECRET_KEY_BASE` | | | | `TUIST_USE_IPV6` | When `1` it configures the app to use IPv6 addresses | No | `0` | `1` | | | `TUIST_LOG_LEVEL` | The log level to use for the app | No | `info` | [Log levels](https://hexdocs.pm/logger/1.12.3/Logger.html#module-levels) | | | `TUIST_GITHUB_APP_PRIVATE_KEY` | The private key used for the GitHub app to unlock extra functionality such as posting automatic PR comments | No | `-----BEGIN RSA...` | | | | `TUIST_OPS_USER_HANDLES` | A comma-separated list of user handles that have access to the operations URLs | No | | `user1,user2` | | ### Database configuration {#database-configuration} The following environment variables are used to configure the database connection: | Environment variable | Description | Required | Default | Example | | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | ---------------------------------------------------------------------- | | `DATABASE_URL` | The URL to access the Postgres database. Note that the URL should contain the authentication information | Yes | | `postgres://username:password@cloud.us-east-2.aws.test.com/production` | | `TUIST_USE_SSL_FOR_DATABASE` | When true, it uses [SSL](https://en.wikipedia.org/wiki/Transport_Layer_Security) to connect to the database | No | `1` | `1` | | `TUIST_DATABASE_POOL_SIZE` | The number of connections to keep open in the connection pool | No | `10` | `10` | | `TUIST_DATABASE_QUEUE_TARGET` | The interval (in miliseconds) for checking if all the connections checked out from the pool took more than the queue interval [(More information)](https://hexdocs.pm/db_connection/DBConnection.html#start_link/2-queue-config) | No | `300` | `300` | | `TUIST_DATABASE_QUEUE_INTERVAL` | The threshold time (in miliseconds) in the queue that the pool uses to determine if it should start dropping new connections [(More information)](https://hexdocs.pm/db_connection/DBConnection.html#start_link/2-queue-config) | No | `1000` | `1000` | ### Authentication environment configuration {#authentication-environment-configuration} We facilitate authentication through [identity providers (IdP)](https://en.wikipedia.org/wiki/Identity_provider). To utilize this, ensure all necessary environment variables for the chosen provider are present in the server's environment. **Missing variables** will result in Tuist bypassing that provider. #### GitHub {#github} We recommend authenticating using a [GitHub App](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps) but you can also use the [OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app). Make sure to include all essential environment variables specified by GitHub in the server environment. Absent variables will cause Tuist to overlook the GitHub authentication. To properly set up the GitHub app: - In the GitHub app's general settings: - Copy the `Client ID` and set it as `TUIST_GITHUB_APP_CLIENT_ID` - Create and copy a new `client secret` and set it as `TUIST_GITHUB_APP_CLIENT_SECRET` - Set the `Callback URL` as `http://YOUR_APP_URL/users/auth/github/callback`. `YOUR_APP_URL` can also be your server's IP address. - In the `Permissions and events`'s `Account permissions` section, set the `Email addresses` permission to `Read-only`. You'll then need to expose the following environment variables in the environment where the Tuist server runs: | Environment variable | Description | Required | Default | Example | | -------------------------------- | --------------------------------------- | -------- | ------- | ------------------------------------------ | | `TUIST_GITHUB_APP_CLIENT_ID` | The client ID of the GitHub application | Yes | | `Iv1.a629723000043722` | | `TUIST_GITHUB_APP_CLIENT_SECRET` | The client secret of the application | Yes | | `232f972951033b89799b0fd24566a04d83f44ccc` | #### Google {#google} You can set up authentication with Google using [OAuth 2](https://developers.google.com/identity/protocols/oauth2). For that, you'll need to create a new credential of type OAuth client ID. When creating the credentials, select "Web Application" as application type, name it `Tuist`, and set the redirect URI to `{base_url}/users/auth/google/callback` where `base_url` is the URL your hosted-service is running at. Once you create the app, copy the client ID and secret and set them as environment variables `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` respectively. > [!NOTE] CONSENT SCREEN SCOPES > You might need to create a consent screen. When you do so, make sure to add the `userinfo.email` and `openid` scopes and mark the app as internal. #### Okta {#okta} You can enable authentication with Okta through the [OAuth 2.0](https://oauth.net/2/) protocol. You'll have to [create an app](https://developer.okta.com/docs/en/guides/implement-oauth-for-okta/main/#create-an-oauth-2-0-app-in-okta) on Okta with the following configuration: - **App integration name:** `Tuist` - **Grant type:** Enable _Authorization Code_ for _Client acting on behalf of a user_ - **Sign-in redirect URL:** `{url}/users/auth/okta/callback` where `url` is the public URL your service is accessed through. - **Assignments:** This configuration will depend on your security team requirements. Once the app is created you'll need to set the following environment variables: | Environment variable | Description | Required | Default | Example | | -------------------------- | ---------------------------------------------- | -------- | ------- | --------------------------- | | `TUIST_OKTA_SITE` | The URL of your Okta organization | Yes | | `https://your-org.okta.com` | | `TUIST_OKTA_CLIENT_ID` | The client ID to authenticate against Okta | Yes | | | | `TUIST_OKTA_CLIENT_SECRET` | The client secret to authenticate against Okta | Yes | | | ### Storage environment configuration {#storage-environment-configuration} Tuist needs storage to house artifacts uploaded through the API. It's **essential to configure one of the supported storage solutions** for Tuist to operate effectively. #### S3-compliant storages {#s3compliant-storages} You can use any S3-compliant storage provider to store artifacts. The following environment variables are required to authenticate and configure the integration with the storage provider: | Environment variable | Description | Required | Default | Example | | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | ------------------------------------------ | | `TUIST_ACCESS_KEY_ID` or `AWS_ACCESS_KEY_ID` | The access key ID to authenticate against the storage provider | Yes | | `AKIAIOSFOD` | | `TUIST_SECRET_ACCESS_KEY` or `AWS_SECRET_ACCESS_KEY` | The secret access key to authenticate against the storage provider | Yes | | `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` | | `TUIST_S3_REGION` or `AWS_REGION` | The region where the bucket is located | Yes | | `us-west-2` | | `TUIST_S3_ENDPOINT` or `AWS_ENDPOINT` | The endpoint of the storage provider | Yes | | `https://s3.us-west-2.amazonaws.com` | | `TUIST_S3_BUCKET_NAME` | The name of the bucket where the artifacts will be stored | Yes | | `tuist-artifacts` | | `TUIST_S3_REQUEST_TIMEOUT` | The timeout (in seconds) for requests to the storage provider | No | `30` | `30` | | `TUIST_S3_POOL_TIMEOUT` | The timeout (in seconds) for the connection pool to the storage provider | No | `5` | `5` | | `TUIST_S3_POOL_COUNT` | The number of pools to use for connections to the storage provider | No | `1` | `1` | | `TUIST_S3_PROTOCOL` | The protocol to use when connecting to the storage provider (`http1` or `http2`) | No | `http2` | `http2` | | `TUIST_S3_VIRTUAL_HOST` | Whether the URL should be constructed with the bucket name as a sub-domain (virtual host). | No | No | `1` | > [!NOTE] AWS authentication with Web Identity Token from environment variables > If your storage provider is AWS and you'd like to authenticate using a web identity token, you can set the environment variable `TUIST_S3_AUTHENTICATION_METHOD` to `aws_web_identity_token_from_env_vars`, and Tuist will use that method using the conventional AWS environment variables. #### Google Cloud Storage {#google-cloud-storage} For Google Cloud Storage, follow [these docs](https://cloud.google.com/storage/docs/authentication/managing-hmackeys) to get the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` pair. The `AWS_ENDPOINT` should be set to `https://storage.googleapis.com`. Other environment variables are the same as for any other S3-compliant storage. ### Git platform configuration {#git-platform-configuration} Tuist can integrate with Git platforms to provide extra features such as automatically posting comments in your pull requests. #### GitHub {#platform-github} You will need to [create a GitHub app](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps). You can reuse the one you created for authentication, unless you created an OAuth GitHub app. In the `Permissions and events`'s `Repository permissions` section, you will need to additionally set the `Pull requests` permission to `Read and write`. On top of the `TUIST_GITHUB_APP_CLIENT_ID` and `TUIST_GITHUB_APP_CLIENT_SECRET`, you will need the following environment variables: | Environment variable | Description | Required | Default | Example | | ------------------------------ | ----------------------------------------- | -------- | ------- | ------------------------------------ | | `TUIST_GITHUB_APP_PRIVATE_KEY` | The private key of the GitHub application | Yes | | `-----BEGIN RSA PRIVATE KEY-----...` | ## Deployment {#deployment} On-premise users are granted access to the repository located at [tuist/registry](https://github.com/cloud/registry) which has a linked container registry for pulling images. Currently, the container registry allows authentication only as an individual user. Therefore, users with repository access must generate a **personal access token** within the Tuist organization, ensuring they have the necessary permissions to read packages. After submission, we will promptly approve this token. > [!IMPORTANT] USER VS ORGANIZATION-SCOPED TOKENS > Using a personal access token presents a challenge because it's associated with an individual who might eventually depart from the enterprise organization. GitHub recognizes this limitation and is actively developing a solution to allow GitHub apps to authenticate with app-generated tokens. ### Pulling the Docker image {#pulling-the-docker-image} After generating the token, you can retrieve the image by executing the following command: ```bash echo $TOKEN | docker login ghcr.io -u USERNAME --password-stdin docker pull ghcr.io/tuist/tuist:latest ``` ### Deploying the Docker image {#deploying-the-docker-image} The deployment process for the Docker image will differ based on your chosen cloud provider and your organization's continuous deployment approach. Since most cloud solutions and tools, like [Kubernetes](https://kubernetes.io/), utilize Docker images as fundamental units, the examples in this section should align well with your existing setup. We recommend establishing a deployment pipeline that that runs **every Tuesday**, pulling and deploying fresh images. This ensures you consistently benefit from the latest improvements. > [!IMPORTANT] > If your deployment pipeline needs to validate that the server is up and running, you can send a `GET` HTTP request to `/ready` and assert a `200` status code in the response. #### Fly {#fly} To deploy the app on [Fly](https://fly.io/), you'll require a `fly.toml` configuration file. Consider generating it dynamically within your Continuous Deployment (CD) pipeline. Below is a reference example for your use: ```toml app = "tuist" primary_region = "fra" kill_signal = "SIGINT" kill_timeout = "5s" [experimental] auto_rollback = true [env] # Your environment configuration goes here # Or exposed through Fly secrets [processes] app = "/usr/local/bin/hivemind /app/Procfile" [[services]] protocol = "tcp" internal_port = 8080 auto_stop_machines = false auto_start_machines = false processes = ["app"] http_options = { h2_backend = true } [[services.ports]] port = 80 handlers = ["http"] force_https = true [[services.ports]] port = 443 handlers = ["tls", "http"] [services.concurrency] type = "connections" hard_limit = 100 soft_limit = 80 [[services.http_checks]] interval = 10000 grace_period = "10s" method = "get" path = "/ready" protocol = "http" timeout = 2000 tls_skip_verify = false [services.http_checks.headers] [[statics]] guest_path = "/app/public" url_prefix = "/" ``` Then you can run `fly launch --local-only --no-deploy` to launch the app. On subsequent deploys, instead of running `fly launch --local-only`, you will need to run `fly deploy --local-only`. Fly.io doesn't allow to pull private Docker images, which is why we need to use the `--local-only` flag. ### Docker Compose {#docker-compose} Below is an example of a `docker-compose.yml` file that you can use as a reference to deploy the service: ```yaml version: '3.8' services: db: image: timescale/timescaledb-ha:pg16 restart: always environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - PGDATA=/var/lib/postgresql/data/pgdata ports: - '5432:5432' volumes: - db:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 pgweb: container_name: pgweb restart: always image: sosedoff/pgweb ports: - "8081:8081" links: - db:db environment: PGWEB_DATABASE_URL: postgres://postgres:postgres@db:5432/postgres?sslmode=disable depends_on: - db tuist: image: ghcr.io/tuist/tuist:latest container_name: tuist depends_on: - db ports: - "80:80" - "8080:8080" - "443:443" expose: - "80" - "8080" - "443:443" environment: # Base Tuist Env - https://docs.tuist.io/en/guides/dashboard/on-premise/install#base-environment-configuration TUIST_USE_SSL_FOR_DATABASE: "0" TUIST_LICENSE: # ... DATABASE_URL: postgres://postgres:postgres@db:5432/postgres?sslmode=disable TUIST_APP_URL: https://localhost:8080 TUIST_SECRET_KEY_BASE: # ... WEB_CONCURRENCY: 80 # Auth - one method # GitHub Auth - https://docs.tuist.io/en/guides/dashboard/on-premise/install#github TUIST_GITHUB_OAUTH_ID: TUIST_GITHUB_APP_CLIENT_SECRET: # Okta Auth - https://docs.tuist.io/en/guides/dashboard/on-premise/install#okta TUIST_OKTA_SITE: TUIST_OKTA_CLIENT_ID: TUIST_OKTA_CLIENT_SECRET: TUIST_OKTA_AUTHORIZE_URL: # Optional TUIST_OKTA_TOKEN_URL: # Optional TUIST_OKTA_USER_INFO_URL: # Optional TUIST_OKTA_EVENT_HOOK_SECRET: # Optional # Storage AWS_ACCESS_KEY_ID: # ... AWS_SECRET_ACCESS_KEY: # ... AWS_S3_REGION: # ... AWS_ENDPOINT: # https://amazonaws.com TUIST_S3_BUCKET_NAME: # ... # Other volumes: db: driver: local ``` ## Operations {#operations} Tuist provides a set of utilities under `/ops/` that you can use to manage your instance. > [!IMPORTANT] Authorization > Only people whose handles are listed in the `TUIST_OPS_USER_HANDLES` environment variable can access the `/ops/` endpoints. - **Errors (`/ops/errors`):** You can view unexpected errors that ocurred in the application. This is useful for debugging and understanding what went wrong and we might ask you to share this information with us if you're facing issues. - **Dashboard (`/ops/dashboard`):** You can view a dashboard that provides insights into the application's performance and health (e.g. memory consumption, processes running, number of requests). This dashboard can be quite useful to understand if the hardware you're using is enough to handle the load. --- URL: "/ja/server/introduction/why-a-server" LLMS_URL: "/ja/server/introduction/why-a-server.md" title: "Why a server?" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn why Tuist has a server and how it can help scale your app development." --- # Why a server? {#why-a-server} At a certain scale, optimizing a project and developers' interactions with them require access to data that changes over time, and integrations with other internet services where teams collaborate. This is only possible with **a server that can store data in a database, process it asynchonously, and integrate it with other services.** While the role of a server is common in other ecosystems, it's not that common in app development. Teams leaned heavily on open source solutions that leveraged the capabilities of CI services to approximate the capabilities of a server. However, as the complexity of the projects and the number of developers working on them increased, the limitations of these solutions became more evident. We believe teams shouldn't have to worry about setting up and maintaining a server to scale their projects. That's why we built a server that Tuist and [Xcode projects](https://developer.apple.com/documentation/xcode/creating-an-xcode-project-for-an-app) can integrate with to scale their projects and teams. > [!TIP] GIVING YOUR PROJECTS AND WORKFLOWS SUPERPOWERS > A way of thinking about the server is as a superpower that you can give to your projects and workflows. > Some superpowers like binary caching require you to have a Tuist project but others just work with vanilla Xcode projects. --- URL: "/ja/server/introduction/integrations" LLMS_URL: "/ja/server/introduction/integrations.md" title: "Integrations" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn how to connect Tuist to other tools and services." --- # Integrations {#integrations} We strongly believe we should meet developers where they are, and let's be honest, developers spend time outside of their coding environments, such as reviewing pull request on [GitHub](https://github.com) or communicating with their team on [Slack](https://slack.com). That's why we've built integrations with popular tools and services to make it easier for you to use Tuist in your workflows. This page lists the integrations we currently support. ## Git platforms {#git-platforms} Git repositories are the centerpiece of the vast majority of software projects out there. We integrate with your Git platform to provide Tuist insights right in your pull requests or to save you some configuration such as syncing your default branch. ### GitHub {#github} Install the [Tuist GitHub app](https://github.com/marketplace/tuist). Once installed, you will need to tell Tuist the URL of your repository, such as: ```sh tuist project update tuist/tuist --repository-url https://github.com/tuist/tuist ``` --- URL: "/ja/server/introduction/authentication" LLMS_URL: "/ja/server/introduction/authentication.md" title: "Authentication" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn how to authenticate with the Tuist server from the CLI." --- # Authentication {#authentication} To interact with the server, the CLI needs to authenticate the requests using [bearer authentication](https://swagger.io/docs/specification/authentication/bearer-authentication/). The CLI supports authenticating as a user or as a project. ## As a user {#as-a-user} When using the CLI locally on your machine, we recommend authenticating as a user. To authenticate as a user, you need to run the following command: ```bash tuist auth login ``` The command will take you through a web-based authentication flow. Once you authenticate, the CLI will store a long-lived refresh token and a short-lived access token under `~/.config/tuist/credentials`. Each file in the directory represents the domain you authenticated against, which by default should be `cloud.tuist.io.json`. The information stored in that directory is sensitive, so **make sure to keep it safe**. The CLI will automatically look up the credentials when making requests to the server. If the access token is expired, the CLI will use the refresh token to get a new access token. ### Organization SSO {#organization-sso} If you have a Google Workspace organization and you want any developer who signs in with the same Google hosted domain to be added to your Tuist organization, you can set it up with: ```bash tuist organization update sso my-organization --provider google --organization-id my-google-domain.com ``` For on-premise customers that have Okta set up, you can get the same behavior as for Google by running: ```bash tuist organization update sso my-organization --provider okta --organization-id my-okta-domain.com ``` > [!IMPORTANT] > You must be authenticated with Google using an email tied to the organization whose domain you are setting up. ## As a project {#as-a-project} In non-interactive environments like continuous integrations', you can't authenticate through an interactive flow. For those environments, we recommend authenticating as a project by using a project-scoped token: ```bash tuist project tokens create ``` The CLI expects the token to be defined as the environment variable `TUIST_CONFIG_TOKEN`, and the `CI=1` environment variable to be set. The CLI will use the token to authenticate the requests. > [!IMPORTANT] LIMITED SCOPE > The permissions of the project-scoped token are limited to the actions that we consider safe for projects to perform from a CI environment. We plan to document the permissions that the token has in the future. --- URL: "/ja/server/introduction/accounts-and-projects" LLMS_URL: "/ja/server/introduction/accounts-and-projects.md" title: "Accounts and projects" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn how to create and manage accounts and projects in Tuist." --- # Accounts and projects {#accounts-and-projects} ## Accounts {#accounts} To use the server, you'll need an account. There are two types of accounts: - **Personal account:** Those accounts are created automaticaly when you sign up and are identified by a handle that's obtained either from the identity provider (e.g. GitHub) or the first part of the email address. - **Organization account:** Those accounts are manually created and are identified by a handle that's defined by the developer. Organizations allow inviting other members to collaborate on projects. If you are familiar with [GitHub](https://github.com), the concept is similar to theirs, where you can have personal and organization accounts, and they are identified by a _handle_ that's used when constructing URLs. > [!NOTE] CLI-FIRST > Most operations to manage accounts and projects are done through the CLI. We are working on a web interface that will make it easier to manage accounts and projects. You can manage the organization through the subcommands under `tuist organization`. To create a new organization account, run: ```bash tuist organization create {account-handle} ``` ## Projects {#projects} Your projects, either Tuist's or raw Xcode's, need to be integrated with your account through a remote project. Continuing with the comparison with GitHub, it's like having a local and a remote repository where you push your changes. You can use the `tuist project` to create and manage projects. Projects are identified by a full handle, which is the result of concatenating the organization handle and the project handle. For example, if you have an organization with the handle `tuist`, and a project with the handle `tuist`, the full handle would be `tuist/tuist`. The binding between the local and the remote project is done through the configuration file. If you don't have any, create it at `Tuist.swift` and add the following content: ```swift let tuist = Tuist(fullHandle: "{account-handle}/{project-handle}") // e.g. tuist/tuist ``` > [!IMPORTANT] TUIST PROJECT-ONLY FEATURES > Note that there are some features like binary caching that require you having a Tuist project. If you are using raw Xcode projects, you won't be able to use those features. Your project's URL is constructed by using the full handle. For example, Tuist's dashboard, which is public, is accessible at [cloud.tuist.io/tuist/tuist](https://cloud.tuist.io/tuist/tuist), where `tuist/tuist` is the project's full handle. --- URL: "/ja/references/project-description/[identifier]" LLMS_URL: "/ja/references/project-description/[identifier].md" editLink: false titleTemplate: ":title · Project Description · References · Tuist" --- --- URL: "/ja/references/migrations/from-v3-to-v4" LLMS_URL: "/ja/references/migrations/from-v3-to-v4.md" title: "From v3 to v4" titleTemplate: ":title · Migrations · References · Tuist" description: "This page documents how to migrate the Tuist CLI from the version 3 to version 4." --- # From Tuist v3 to v4 {#from-tuist-v3-to-v4} With the release of [Tuist 4](https://github.com/tuist/tuist/releases/tag/4.0.0), we took the opportunity to introduce some breaking changes to the project, which we believed would make the project easier to use and maintain in the long run. This document outlines the changes you will need to make to your project to upgrade from Tuist 3 to Tuist 4. ### Dropped version management through `tuistenv` {#dropped-version-management-through-tuistenv} Prior to Tuist 4, the installation script installed a tool, `tuistenv`, that would get renamed to `tuist` at installation time. The tool would take care of installing and activating versions of Tuist ensuring determinism across environments. With the aim of reducing the feature surface of Tuist, we decided to drop `tuistenv` in favor of [Mise](https://mise.jdx.dev/), a tool that does the same job but is more flexible and can be used across different tools. If you were using `tuistenv`, you'll have to uninstall the current version of Tuist by running `curl -Ls https://uninstall.tuist.io | bash` and then install it using the installation method of your choice. We strongly recommend the usage of Mise because it's able to install and activate versions deterministically across environments. ::: code-group ```bash [Uninstall tuistenv] curl -Ls https://uninstall.tuist.io | bash ``` ::: > [!IMPORTANT] MISE IN CI ENVIRONMENTS AND XCODE PROJECTS > If you decide to embrace the determinism that Mise brings across the board, we recommend checking out the documentation for how to use Mise in [CI environments](https://mise.jdx.dev/continuous-integration.html) and [Xcode projects](https://mise.jdx.dev/ide-integration.html#xcode). > [!NOTE] HOMEBREW IS SUPPORTED > Note that you can still install Tuist using Homebrew, which is a popular package manager for macOS. You can find the instructions on how to install Tuist using Homebrew in the installation guide. ### Dropped `init` constructors from `ProjectDescription` models {#dropped-init-constructors-from-projectdescription-models} With the aim of improving the readability and expressiveness of the APIs, we decided to remove the `init` constructors from all the `ProjectDescription` models. Every model now provides a static constructor that you can use to create instances of the models. If you were using the `init` constructors, you'll have to update your project to use the static constructors instead. > [!TIP] NAMING CONVENTION > The naming convention that we follow is to use the name of the model as the name of the static constructor. For example, the static constructor for the `Target` model is `Target.target`. ### Renamed `--no-cache` to `--no-binary-cache` {#renamed-nocache-to-nobinarycache} Because the `--no-cache` flag was ambiguous, we decided to rename it to `--no-binary-cache` to make it clear that it refers to the binary cache. If you were using the `--no-cache` flag, you'll have to update your project to use the `--no-binary-cache` flag instead. ### Renamed `tuist fetch` to `tuist install` {#renamed-tuist-fetch-to-tuist-install} We renamed the `tuist fetch` command to `tuist install` to align with the industry convention. If you were using the `tuist fetch` command, you'll have to update your project to use the `tuist install` command instead. ### [Adopt `Package.swift` as the DSL for dependencies](https://github.com/tuist/tuist/pull/5862) {#adopt-packageswift-as-the-dsl-for-dependencieshttpsgithubcomtuisttuistpull5862} Before Tuist 4, you could define dependencies in a `Dependencies.swift` file. This proprietary format broke the support in tools like [Dependabot](https://github.com/dependabot) or [Renovatebot](https://github.com/renovatebot/renovate) to automatically update dependencies. Moreover, it introduced unnecessary indirections for users. Therefore, we decided to embrace `Package.swift` as the only way to define dependencies in Tuist. If you were using the `Dependencies.swift` file, you'll have to move the content from your `Tuist/Dependencies.swift` to a `Package.swift` at the root, and use the `#if TUIST` directive to configure the integration. You can read more about how to integrate Swift Package dependencies here ### Renamed `tuist cache warm` to `tuist cache` {#renamed-tuist-cache-warm-to-tuist-cache} For brevity, we decided to rename the `tuist cache warm` command to `tuist cache`. If you were using the `tuist cache warm` command, you'll have to update your project to use the `tuist cache` command instead. ### Renamed `tuist cache print-hashes` to `tuist cache --print-hashes` {#renamed-tuist-cache-printhashes-to-tuist-cache-printhashes} We decided to rename the `tuist cache print-hashes` command to `tuist cache --print-hashes` to make it clear that it's a flag of the `tuist cache` command. If you were using the `tuist cache print-hashes` command, you'll have to update your project to use the `tuist cache --print-hashes` flag instead. ### Removed caching profiles {#removed-caching-profiles} Before Tuist 4, you could define caching profiles in `Tuist/Config.swift` which contained a configuration for the cache. We decided to remove this feature because it could lead to confusion when using it in the generation process with a profile other than the one that was used to generate the project. Moreover, it could lead to users using a debug profile to build a release version of the app, which could lead to unexpected results. In its place, we introduced the `--configuration` option, which you can use to specify the configuration you want to use when generating the project. If you were using caching profiles, you'll have to update your project to use the `--configuration` option instead. ### Removed `--skip-cache` in favor of arguments {#removed-skipcache-in-favor-of-arguments} We removed the flag `--skip-cache` from the `generate` command in favor of controlling for which targets the binary cache should be skipped by using the arguments. If you were using the `--skip-cache` flag, you'll have to update your project to use the arguments instead. ::: code-group ```bash [Before] tuist generate --skip-cache Foo ``` ```bash [After] tuist generate Foo ``` ::: ### [Dropped signing capabilities](https://github.com/tuist/tuist/pull/5716) {#dropped-signing-capabilitieshttpsgithubcomtuisttuistpull5716} Signing is already solved by community tooling like [Fastlane](https://fastlane.tools/) and Xcode itself, which do a much better job at that. We felt that signing was an stretch goal for Tuist, and that it was better to focus on the core features of the project. If you were using Tuist signing capabilities, which consisted of encrypting the certificates and profiles in the repository and installing them in the right places at generation time, you might want to replicate that logic in your own scripts that run before project generation. In particular: - A script that decrypts the certificates and profiles using a key either stored in the file-system or in an environment variable, and installs certificates in the keychain, and the provisioning profiles in the directory `~/Library/MobileDevice/Provisioning\ Profiles`. - A script that can take an existing profiles and certificates and encrypt them. > [!TIP] SIGNING REQUIREMENTS > Signing requires the right certificates to be present in the keychain and the provisioning profiles to be present in the directory `~/Library/MobileDevice/Provisioning\ Profiles`. You can use the `security` command-line tool to install certificates in the keychain and the `cp` command to copy the provisioning profiles to the right directory. ### Dropped Carthage integration via `Dependencies.swift` {#dropped-carthage-integration-via-dependenciesswift} Before Tuist 4, Carthage dependencies could be defined in a `Dependencies.swift` file, which users could then fetch by running `tuist fetch`. We also felt that this was a stretch goal for Tuist, specially considering a future where Swift Package Manager would be the preferred way to manage dependencies. If you were using Carthage dependencies, you'll have to use `Carthage` directly to pull the pre-compiled frameworks and XCFrameworks into Carthage's standard directory, and then reference those binaries from your tagets using the `TargetDependency.xcframework` and `TargetDependency.framework` cases. > [!NOTE] CARTHAGE IS STILL SUPPORTED > Some users understood that we dropped Carthage support. We didn't. The contract between Tuist and Carthage's output is to system-stored frameworks and XCFrameworks. The only thing that changed is who is responsible for fetching the dependencies. It used to be Tuist through Carthage, now it's Carthage. ### Dropped the `TargetDependency.packagePlugin` API {#dropped-the-targetdependencypackageplugin-api} Before Tuist 4, you could define a package plugin dependency using the `TargetDependency.packagePlugin` case. After seeing the Swift Package Manager introducing new package types, we decided to iterate on the API towards something that would be more flexible and future-proof. If you were using `TargetDependency.packagePlugin`, you'll have to use `TargetDependency.package` instead, and pass the type of package you want to use as an argument. ### [Dropped deprecated APIs](https://github.com/tuist/tuist/pull/5560) {#dropped-deprecated-apishttpsgithubcomtuisttuistpull5560} We removed the APIs that were marked as deprecated in Tuist 3. If you were using any of the deprecated APIs, you'll have to update your project to use the new APIs. --- URL: "/ja/references/examples/[example]" LLMS_URL: "/ja/references/examples/[example].md" editLink: false titleTemplate: ":title · Examples · References · Tuist" --- Check out example --- URL: "/ja" LLMS_URL: "/ja.md" title: "Tuistとは?" description: "Apple 標準の開発ツールを強化し、より大規模で優れたアプリを構築する。" --- # アイデアからストア公開まで <0/> ## インストール方法 早速Tuist をインストールして `tuist init` を実行してみましょう: ::: code-group ```bash [Homebrew] brew tap tuist/tuist brew install --formula tuist tuist init ``` ```bash [Mise] mise x tuist@latest -- tuist init ``` ::: 詳細については、 導入の手順 をご覧ください。 ## もっと詳しく Tuist を少し試してみて、Tuist を最大限に活用する方法を学びましょう。 ## 最新の登壇一覧 Tuistのこれまでの発表内容をご覧ください。 最新情報をキャッチし、専門知識を身につけましょう。 ## コミュニティに参加する ソースコードを確認し、他の開発者とつながりましょう。 --- URL: "/ja/guides/tuist/about" LLMS_URL: "/ja/guides/tuist/about.md" title: "Tuist について" titleTemplate: ":title · Guides · Tuist" description: "Apple 標準の開発ツールを強化し、より大規模で優れたアプリを構築する。" --- # About Tuist {#about-tuist} アプリ開発の世界、特に Apple のようなプラットフォームでは、組織はしばしば **生産性の問題** に直面します。これには、遅いコンパイル時間、不確実なテスト、リソースを消耗する複雑な自動化ワークフローが含まれます。 従来、企業は専任のプラットフォームチームを結成してこれらの問題に対処しています。 これらの専門家はコードベースの健全性と整合性を維持し、他の開発者が機能の開発に集中できるようにします。 しかし、このアプローチは高コストでリスクが伴う可能性があり、重要な役割を担うチームメンバーの退職が生産性に深刻な影響を及ぼすことがあります。 ## What {#what} **Tuist は、アプリ開発を加速し、強化するために設計されたツールチェーンです。** 私たちは公式ツールやシステムとシームレスに統合し、開発者が馴染みのある環境で作業できるようサポートします。 ツールやシステムの統合の負担を軽減することで、チームが機能開発と全体的な開発者体験の向上にエネルギーを注げるようにします。 要するに、Tuistはあなたのプロジェクトを支えるチームのような役割を果たします。 アプリアイディアの閃きからユーザーへのリリースまで、私たちはあなたと共に歩み、発生する課題に取り組みます。 Tuist is comprised of a [CLI](https://github.com/tuist/tuist), which is the main entry point for developers, and a server that the CLI integrates with to persist state and integrate with other publicly available services. ## Why {#why} なぜTuistを選択するのか? その理由は以下の通りです。 ### Simplify 🌱 {#simplify} As projects grow and span multiple platforms, modularization becomes crucial. Tuistはこの複雑さを簡素化し、プロジェクトの構造を最適化し、よりよく理解するためのツールを提供します。 **Further reading:** Projects ### Optimize workflows 🚀 {#optimize-workflows} Leveraging project information, Tuist enhances efficiency through selective test execution and deterministic binary reuse across builds. **Further reading:** Cache, Selective testing, Registry, and Previews ### Foster healthy project evolution 📈 {#foster-healthy-project-evolution} We provide insights into your project's dynamics and expert guidance for informed decision-making. このアプローチにより、開発者の離職やビジネスゴールの達成に失敗することに繋がる健全でないプロジェクトによるフラストレーションや生産性の低下を防ぎます。 **Further reading:** Server ### Break down silos 💜 {#break-down-silos} Unlike platform-specific ecosystems (e.g., Xcode's contained environment), Tuist offers web-centric experiences and integrates seamlessly with popular tools like Slack, Prometheus, and GitHub, enhancing cross-tool collaboration. **Further reading:** Projects --- Tuist やプロジェクト、会社情報について詳しく知りたい場合は、私たちの[ハンドブック](https://handbook.tuist.io/)をご覧ください。そこには、私たちのビジョンや価値、Tuist を支えるチームに関する詳細な情報が含まれています。 --- URL: "/ja/guides/share/previews" LLMS_URL: "/ja/guides/share/previews.md" title: "Previews" titleTemplate: ":title · Share · Guides · Tuist" description: "Learn how to generate and share previews of your apps with anyone." --- # Previews {#previews} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project When building an app, you may want to share it with others to get feedback. Traditionally, this is something that teams do by building, signing, and pushing their apps to platforms like Apple's [TestFlight](https://developer.apple.com/testflight/). However, this process can be cumbersome and slow, especially when you're just looking for quick feedback from a colleague or a friend. To make this process more streamlined, Tuist provides a way to generate and share previews of your apps with anyone. > [!IMPORTANT] DEVICE BUILDS NEED TO BE SIGNED > When building for device, it is currently your responsibility to ensure the app is signed correctly. We plan to streamline this in the future. :::code-group ```bash [Tuist Project] tuist build App # Build the app for the simulator tuist build App -- -destination 'generic/platform=iOS' # Build the app for the device tuist share App ``` ```bash [Xcode Project] xcodebuild -scheme App -project App.xcodeproj -configuration Debug # Build the app for the simulator xcodebuild -scheme App -project App.xcodeproj -configuration Debug -destination 'generic/platform=iOS' # Build the app for the device tuist share App --configuration Debug --platforms iOS tuist share App.ipa # Share an existing .ipa file ``` ::: The command will generate a link that you can share with anyone to run the app – either on a simulator or an actual device. All they'll need to do is to run the command below: ```bash tuist run {url} tuist run --device "My iPhone" {url} # Run the app on a specific device ``` When sharing an `.ipa` file, you can download the app directly from the mobile device using the Preview link. The links to `.ipa` previews are by default _public_. In the future, you will have an option to make them private, so that the recipient of the link would need to authenticate with their Tuist account to download the app. `tuist run` also enables you to run a latest preview based on a specifier such as `latest`, branch name, or a specific commit hash: ```bash tuist run App@latest # Runs latest App preview associated with the project's default branch tuist run App@my-feature-branch # Runs latest App preview associated with a given branch tuist run App@00dde7f56b1b8795a26b8085a781fb3715e834be # Runs latest App preview associated with a given git commit sha ``` > [!IMPORTANT] PREVIEWS' VISIBILITY > Only people with access to the organization the project belongs to can access the previews. We plan to add support for expiring links. ## Tuist macOS app {#tuist-macos-app}

Tuist

Download
To make running Tuist Previews even easier, we developed a Tuist macOS menu bar app. Instead of running Previews via the Tuist CLI, you can [download](https://tuist.dev/download) the macOS app. You can also install the app by running `brew install --cask tuist/tuist/tuist`. When you now click on "Run" in the Preview page, the macOS app will automatically launch it on your currently selected device. > [!IMPORTANT] REQUIREMENTS > To download Previews, you need to first authenticate with the `tuist auth login` command. > In the future, you will be able to authenticate directly in the app. > > Additionally, you need to have Xcode locally installed. ## Pull/merge request comments {#pullmerge-request-comments} > [!IMPORTANT] INTEGRATION WITH GIT PLATFORM REQUIRED > To get automatic pull/merge request comments, integrate your remote project with a Git platform. Testing new functionality should be a part of any code review. But having to build an app locally adds unnecessary friction, often leading to developers skipping testing functionality on their device at all. But _what if each pull request contained a link to the build that would automatically run the app on a device you selected in the Tuist macOS app?_ Once your Tuist project is connected with your Git platform such as [GitHub](https://github.com), add a `tuist share MyApp` to your CI workflow. Tuist will then post a Preview link directly in your pull requests: ![GitHub app comment with a Tuist Preview link](/images/guides/share/github-app-with-preview.png) ## README badge {#readme-badge} To make Tuist Previews more visible in your repository, you can add a badge to your `README` file that points to the latest Tuist Preview: [![Tuist Preview](https://tuist.dev/Dimillian/IcySky/previews/latest/badge.svg)](https://tuist.dev/Dimillian/IcySky/previews/latest) To add the badge to your `README`, use the following markdown and replace the account and project handles with your own: ``` [![Tuist Preview](https://tuist.dev/{account-handle}/{project-handle}/previews/latest/badge.svg)](https://tuist.dev/{account-handle}/{project-handle}/previews/latest) ``` ## Automations {#automations} You can use the `--json` flag to get a JSON output from the `tuist share` command: ``` tuist share --json ``` The JSON output is useful to create custom automations, such as posting a Slack message using your CI provider. The JSON contains a `url` key with the full preview link and a `qrCodeURL` key with the URL to the QR code image to make it easier to download previews from a real device. An example of a JSON output is below: ```json { "id": 1234567890, "url": "https://cloud.tuist.io/preview/1234567890", "qrCodeURL": "https://cloud.tuist.io/preview/1234567890/qr-code.svg" } ``` --- URL: "/ja/guides/quick-start/install-tuist" LLMS_URL: "/ja/guides/quick-start/install-tuist.md" title: "Tuistのインストール" titleTemplate: ":title · クイックスタート · ガイド · Tuist" description: "開発環境にTuistをインストールする方法を学びます" --- # Tuistのインストール {#install-tuist} Tuist CLIは、実行可能ファイル、動的フレームワーク、およびリソースのセット (たとえば、テンプレート) で構成されています。 [ソース](https://github.com/tuist/tuist)からTuistを手動でビルドすることもできますが、**有効なインストールを確保するために、以下のインストール方法のいずれかを使用することをお勧めします。** ### Mise {#recommended-mise} :::info Miseは、異なる環境でツールの決定的なバージョンを確保する必要があるチームや組織にとって、推奨される[Homebrew](https://brew.sh)の代替手段です。 ::: Tuist は以下のコマンドのいずれかを使用してインストールできます。 ```bash mise install tuist # .tool-versions/.mise.tomlに指定された現在のバージョンをインストール mise install tuist@x.y.z # 特定のバージョン番号をインストール mise install tuist@3 # あいまいなバージョン番号をインストール ``` Homebrewのようなツールがグローバルに単一のバージョンをインストールしてアクティブにするのに対し、**miseではバージョンをグローバルまたはプロジェクト単位で有効にする必要があります。** これは `mise use` を実行することで行います。 ```bash mise use tuist@x.y.z # 現在のプロジェクトでtuist-x.y.zを使用 mise use tuist@latest # 現在のディレクトリで最新のtuistを使用 mise use -g tuist@x.y.z # tuist-x.y.zをグローバルデフォルトとして使用 mise use -g tuist@system # システムのtuistをグローバルデフォルトとして使用 ``` ### Homebrew {#recommended-homebrew} Tuist は [Homebrew](https://brew.sh) と私達の [formulas](https://github.com/tuist/homebrew-tuist) を使用してインストールできます: ```bash brew tap tuist/tuist brew install --formula tuist brew install --formula tuist@x.y.z ``` :::tip VERIFYING THE AUTHENTICITY OF THE BINARIES ```bash curl -fsSL "https://docs.tuist.dev/verify.sh" | bash ``` ::: --- URL: "/ja/guides/quick-start/get-started" LLMS_URL: "/ja/guides/quick-start/get-started.md" title: "はじめに" titleTemplate: ":title · 導入の手順 · Tuist" description: "開発環境にTuistをインストールする方法を学びます。" --- # はじめに {#get-started} 任意のディレクトリ、または Xcode プロジェクトおよびワークスペースのディレクトリで、以下のコマンドを実行するのが Tuist を始める最も簡単な方法です: ::: code-group ```bash [Mise] mise x tuist@latest -- tuist init ``` ```bash [Global Tuist (Homebrew)] tuist init ``` ::: The command will walk you through the steps to create a generated project or integrate an existing Xcode project or workspace. It helps you connect your setup to the remote server, giving you access to features like selective testing, previews, and the registry. > [!NOTE] 既存プロジェクトの移行 > 既存のプロジェクトを生成プロジェクトに移行して、開発体験を向上させたり、キャッシュなどの機能を活用したい場合は、移行ガイドをご覧ください。 --- URL: "/ja/guides/quick-start/gather-insights" LLMS_URL: "/ja/guides/quick-start/gather-insights.md" title: "インサイトを収集する" titleTemplate: ":title · クイックスタート · ガイド · Tuist" description: "プロジェクトに関するインサイトを収集する方法を学びます。" --- # インサイトを収集する {#gather-insights} Tuistはサーバーと統合してその機能を拡張できます。 その機能の一つは、プロジェクトやビルドに関するインサイトを収集することです。 サーバー上にプロジェクトのアカウントを持っているだけで済みます。 まず最初に、次のコマンドを実行して認証を行う必要があります: ```bash tuist auth login ``` ## プロジェクトの作成 {#create-a-project} 次に、次のコマンドを実行してプロジェクトを作成できます: ```bash tuist project create my-handle/MyApp # Tuist project my-handle/MyApp was successfully created 🎉 {#tuist-project-myhandlemyapp-was-successfully-created-} ``` `my-handle/MyApp` をコピーします。これはプロジェクトの完全なハンドルを表します。 ## プロジェクトを接続する {#connect-projects} サーバー上にプロジェクトを作成した後、ローカルプロジェクトに接続する必要があります。 サーバー上にプロジェクトを作成した後、ローカルプロジェクトに接続する必要があります。 サーバー上にプロジェクトを作成した後、ローカルプロジェクトに接続する必要があります。 サーバー上にプロジェクトを作成した後、ローカルプロジェクトに接続する必要があります。 サーバー上にプロジェクトを作成した後、ローカルプロジェクトに接続する必要があります。 サーバー上にプロジェクトを作成した後、ローカルプロジェクトに接続する必要があります。 Run `tuist edit` and edit the `Tuist.swift` file to include the full handle of the project: ```swift import ProjectDescription let tuist = Tuist(fullHandle: "my-handle/MyApp") ``` ほら! これで、プロジェクトやビルドに関するインサイトを収集する準備が整いましたよ。 `tuist test` を実行してテストを実行し、結果をサーバーに報告します。 > [!NOTE] > Tuistは結果をローカルにキューイングし、コマンドをブロックすることなく送信しようとします。 したがって、コマンドが終了した直後に結果が送信されない場合があります。 CIでは結果が即座に送信されます。 ![An image that shows a list of runs in the server](/images/guides/quick-start/runs.png) プロジェクトやビルドからデータを取得することは、情報に基づいた意思決定を行う上で重要です。 Tuistはその機能を拡張し続け、プロジェクトの設定を変更することなくその恩恵を受けることができます。 魔法のようですね? 🪄 --- URL: "/ja/guides/quick-start/add-dependencies" LLMS_URL: "/ja/guides/quick-start/add-dependencies.md" title: "依存関係の追加" titleTemplate: ":title · クイックスタート · ガイド · Tuist" description: "最初のSwiftプロジェクトに依存関係を追加する方法を学ぶ" --- # 依存関係の追加 {#add-dependencies} プロジェクトが追加機能を提供するためにサードパーティのライブラリに依存することは一般的です。 そのために、プロジェクトを編集するための最適な体験を得るために、次のコマンドを実行します。 そのために、プロジェクトを編集するための最適な体験を得るために、次のコマンドを実行します。 ```bash tuist edit ``` Xcodeプロジェクトが開き、プロジェクトファイルが表示されます。 Package.swiftを編集し、以下を追加します。 ```swift // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies .package(url: "https://github.com/onevcat/Kingfisher", .upToNextMajor(from: "7.12.0")) // [!code ++] ] ) ``` 次に、プロジェクト内のアプリケーションターゲットを編集して、`Kingfisher` を依存関係として宣言します。 ```swift import ProjectDescription let project = Project( name: "MyApp", targets: [ .target( name: "MyApp", destinations: .iOS, product: .app, bundleId: "io.tuist.MyApp", infoPlist: .extendingDefault( with: [ "UILaunchStoryboardName": "LaunchScreen.storyboard", ] ), sources: ["MyApp/Sources/**"], resources: ["MyApp/Resources/**"], dependencies: [ .external(name: "Kingfisher") // [!code ++] ] ), .target( name: "MyAppTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyAppTests", infoPlist: .default, sources: ["MyApp/Tests/**"], resources: [], dependencies: [.target(name: "MyApp")] ), ] ) ``` 次に、`tuist install` を実行して、[Swift Package Manager](https://www.swift.org/documentation/package-manager/) を使用して依存関係を解決し、取得します。 > [!NOTE] 依存関係解決ツールとしてのSPM > Tuistの推奨する依存関係のアプローチは、依存関係を解決するためにSwift Package Manager (SPM) を使用することです。 TuistはそれらをXcodeプロジェクトとターゲットに変換し、最大限の構成可能性と制御を提供します。 TuistはそれらをXcodeプロジェクトとターゲットに変換し、最大限の構成可能性と制御を提供します。 TuistはそれらをXcodeプロジェクトとターゲットに変換し、最大限の構成可能性と制御を提供します。 TuistはそれらをXcodeプロジェクトとターゲットに変換し、最大限の構成可能性と制御を提供します。 TuistはそれらをXcodeプロジェクトとターゲットに変換し、最大限の構成可能性と制御を提供します。 ## プロジェクトの可視化 {#visualize-the-project} 次のコマンドを実行してプロジェクト構造を可視化できます。 ```bash tuist graph ``` このコマンドは、プロジェクトのディレクトリ内に `graph.png` ファイルを出力して開きます: ![Project graph](/images/guides/quick-start/graph.png) ## 依存関係の使用 {#use-the-dependency} `tuist generate` を実行してプロジェクトをXcodeで開き、`ContentView.swift` ファイルに次の変更を加えます。 ```swift import SwiftUI import Kingfisher // [!code ++] public struct ContentView: View { public init() {} public var body: some View { Text("Hello, World!") // [!code --] .padding() // [!code --] KFImage(URL(string: "https://cloud.tuist.io/images/tuist_logo_32x32@2x.png")!) // [!code ++] } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } ``` Xcodeからアプリを実行すると、URLから読み込まれた画像が表示されるはずです。 --- URL: "/ja/guides/develop/test" LLMS_URL: "/ja/guides/develop/test.md" title: "tuist test" titleTemplate: ":title · Develop · Guides · Tuist" description: "Learn how to run tests efficiently with Tuist." --- # Test {#test} Tuist provides a command, `tuist test` to generate the project if needed, and then run the tests with the the platform-specific build tool (e.g. `xcodebuild` for Apple platforms). You might wonder what's the value of using `tuist test` over generating the project with `tuist generate` and running the tests with the platform-specific build tool. - **Single command:** `tuist test` ensures the project is generated if needed before compiling the project. - **Beautified output:** Tuist enriches the output using tools like [xcbeautify](https://github.com/cpisciotta/xcbeautify) that make the output more user-friendly. - Cache: It optimizes the build by deterministically reusing the build artifacts from a remote cache. - Smart runner: It runs only the tests that need to be run, saving time and resources. - Flakiness: Prevent, detect, and fix flaky tests. ## Usage {#usage} To run the tests of a project, you can use the `tuist test` command. This command will generate the project if needed, and then run the tests using the platform-specific build tool. We support the use of the `--` terminator to forward all subsequent arguments directly to the build tool. ::: code-group ```bash [Running scheme tests] tuist test MyScheme ``` ```bash [Running all tests without binary cache] tuist test --no-binary-cache ``` ```bash [Running all tests without selective testing] tuist test --no-selective-testing ``` ::: ## Pull/merge request comments {#pullmerge-request-comments} > [!IMPORTANT] REQUIREMENTS > To get automatic pull/merge request comments, integrate your remote project with a Git platform. When running tests in your CI environments we can correlate the test results with the pull/merge request that triggered the CI build. This allows us to post a comment on the pull/merge request with the test results. ![GitHub App example](/images/contributors/scheme-arguments.png) --- URL: "/ja/guides/develop/selective-testing/xcodebuild" LLMS_URL: "/ja/guides/develop/selective-testing/xcodebuild.md" title: "xcodebuild" titleTemplate: ":title · Selective testing · Develop · Guides · Tuist" description: "Learn how to leverage selective testing with `xcodebuild`." --- # xcodebuild {#xcodebuild} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project To run tests selectively using `xcodebuild`, you can prepend your `xcodebuild` command with `tuist` – for example, `tuist xcodebuild test -scheme App`. The command hashes your project and on success, it persists the hashes to determine what has changed in future runs. In future runs `tuist xcodebuild test` transparently uses the hashes to filter down the tests to run only the ones that have changed since the last successful test run. For example, assuming the following dependency graph: - `FeatureA` has tests `FeatureATests`, and depends on `Core` - `FeatureB` has tests `FeatureBTests`, and depends on `Core` - `Core` has tests `CoreTests` `tuist xcodebuild test` will behave as such: | Action | Description | Internal state | | ---------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | `tuist xcodebuild test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The hashes of `FeatureATests`, `FeatureBTests` and `CoreTests` are persisted | | `FeatureA` is updated | The developer modifies the code of a target | Same as before | | `tuist xcodebuild test` invocation | Runs the tests in `FeatureATests` because it hash has changed | The new hash of `FeatureATests` is persisted | | `Core` is updated | The developer modifies the code of a target | Same as before | | `tuist xcodebuild test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The new hash of `FeatureATests` `FeatureBTests`, and `CoreTests` are persisted | To use `tuist xcodebuild test` on your CI, follow the instructions in the Continuous integration guide. --- URL: "/ja/guides/develop/selective-testing/generated-project" LLMS_URL: "/ja/guides/develop/selective-testing/generated-project.md" title: "生成されたプロジェクト" titleTemplate: ":title · Selective testing · Develop · Guides · Tuist" description: "Learn how to leverage selective testing with a generated project." --- # Generated project {#generated-project} > [!IMPORTANT] REQUIREMENTS > > - A generated project > - A Tuist account and project To run tests selectively with your generated project, use the `tuist test` command. The command hashes your Xcode project the same way it does for warming the cache, and on success, it persists the hashes on to determine what has changed in future runs. In future runs `tuist test` transparently uses the hashes to filter down the tests to run only the ones that have changed since the last successful test run. For example, assuming the following dependency graph: - `FeatureA` has tests `FeatureATests`, and depends on `Core` - `FeatureB` has tests `FeatureBTests`, and depends on `Core` - `Core` has tests `CoreTests` `tuist test` will behave as such: | Action | Description | Internal state | | ----------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | `tuist test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The hashes of `FeatureATests`, `FeatureBTests` and `CoreTests` are persisted | | `FeatureA` is updated | The developer modifies the code of a target | Same as before | | `tuist test` invocation | Runs the tests in `FeatureATests` because it hash has changed | The new hash of `FeatureATests` is persisted | | `Core` is updated | The developer modifies the code of a target | Same as before | | `tuist test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The new hash of `FeatureATests` `FeatureBTests`, and `CoreTests` are persisted | `tuist test` integrates directly with binary caching to use as many binaries from your local or remote storage to improve the build time when running your test suite. The combination of selective testing with binary caching can dramatically reduce the time it takes to run tests on your CI. ## UI Tests {#ui-tests} Tuist supports selective testing of UI tests. However, Tuist needs to know the destination in advance. Only if you specify the `destination` parameter, Tuist will run the UI tests selectively, such as: ```sh tuist test --device 'iPhone 14 Pro' # or tuist test -- -destination 'name=iPhone 14 Pro' # or tuist test -- -destination 'id=SIMULATOR_ID' ``` --- URL: "/ja/guides/develop/selective-testing" LLMS_URL: "/ja/guides/develop/selective-testing.md" title: "選択的テスト" titleTemplate: ":title · Develop · Guides · Tuist" description: "Use selective testing to run only the tests that have changed since the last successful test run." --- # Selective testing {#selective-testing} As your project grows, so does the amount of your tests. For a long time, running all tests on every PR or push to `main` takes tens of seconds. But this solution does not scale to thousands of tests your team might have. On every test run on the CI, you most likely re-run all the tests, regardless of the changes. Tuist's selective testing helps you to drastically speed up running the tests themselves by running only the tests that have changed since the last successful test run based on our hashing algorithm. Selective testing works with `xcodebuild`, which supports any Xcode project, or if you generate your projects with Tuist, you can use the `tuist test` command instead that provides some extra convenience such as integration with the binary cache. To get started with selective testing, follow the instructions based on your project setup: - xcodebuild - Generated project > [!WARNING] MODULE VS FILE-LEVEL GRANULARITY > Due to the impossibility of detecting the in-code dependencies between tests and sources, the maximum granularity of selective testing is at the target level. Therefore, we recommend keeping your targets small and focused to maximize the benefits of selective testing. ## Pull/merge request comments {#pullmerge-request-comments} > [!IMPORTANT] INTEGRATION WITH GIT PLATFORM REQUIRED > To get automatic pull/merge request comments, integrate your Tuist project with a Git platform. Once your Tuist project is connected with your Git platform such as [GitHub](https://github.com), and you start using `tuist xcodebuild test` or `tuist test` as part of your CI wortkflow, Tuist will post a comment directly in your pull/merge requests, including which tests were run and which skipped: ![GitHub app comment with a Tuist Preview link](/images/guides/develop/github-app-comment.png) --- URL: "/ja/guides/develop/registry/xcodeproj-integration" LLMS_URL: "/ja/guides/develop/registry/xcodeproj-integration.md" title: "Generated project with the XcodeProj-based package integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in a generated Xcode project with the XcodeProj-based package integration." --- # Generated project with the XcodeProj-based package integration {#generated-project-with-xcodeproj-based-integration} When using the XcodeProj-based integration, you can use the `--replace-scm-with-registry` flag to resolve dependencies from the registry if they are available. Add it to the `installOptions` in your `Tuist.swift` file: ```swift import ProjectDescription let tuist = Tuist( fullHandle: "{account-handle}/{project-handle}", project: .tuist( installOptions: .options(passthroughSwiftPackageManagerArguments: ["--replace-scm-with-registry"]) ) ) ``` If you want to ensure that the registry is used every time you resolve dependencies, you will need to update `dependencies` in your `Tuist/Package.swift` file to use the registry identifier instead of a URL. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `swift-composable-architecture` package, do the following: ```diff dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") + .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ] ``` --- URL: "/ja/guides/develop/registry/xcode-project" LLMS_URL: "/ja/guides/develop/registry/xcode-project.md" title: "Xcode project" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in an Xcode project." --- # Xcode project {#xcode-project} To add packages using the registry in your Xcode project, use the default Xcode UI. You can search for packages in the registry by clicking on the `+` button in the `Package Dependencies` tab in Xcode. If the package is available in the registry, you will see the `tuist.dev` registry in the top right: ![Adding package dependencies](/images/guides/develop/build/registry/registry-add-package.png) > [!NOTE] > Xcode currently doesn't support automatically replacing source control packages with their registry equivalents. You will need to manually remove the source control package and add the registry package to speed up the resolution. --- URL: "/ja/guides/develop/registry/swift-package" LLMS_URL: "/ja/guides/develop/registry/swift-package.md" title: "Swift package" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in a Swift package." --- # Swift package {#swift-package} If you are working on a Swift package, you can use the `--replace-scm-with-registry` flag to resolve dependencies from the registry if they are available: ```bash swift package --replace-scm-with-registry resolve ``` If you want to ensure that the registry is used every time you resolve dependencies, you will need to update `dependencies` in your `Package.swift` file to use the registry identifier instead of a URL. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `swift-composable-architecture` package, do the following: ```diff dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") + .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ] ``` --- URL: "/ja/guides/develop/registry/generated-project" LLMS_URL: "/ja/guides/develop/registry/generated-project.md" title: "Generated project with the Xcode package integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in a generated Xcode project with the Xcode package integration." --- # Generated project with the Xcode package integration {#generated-project-with-xcode-based-integration} If you are using the Xcode's default integration of packages with Tuist Projects, you need to use the registry identifier instead of a URL when adding a package: ```swift import ProjectDescription let project = Project( name: "MyProject", packages: [ // Source control resolution // .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") // Registry resolution .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ], .target( name: "App", product: .app, bundleId: "io.tuist.App", dependencies: [ .package(product: "ComposableArchitecture"), ] ) ) ``` --- URL: "/ja/guides/develop/registry/continuous-integration" LLMS_URL: "/ja/guides/develop/registry/continuous-integration.md" title: "継続的インテグレーション" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in continuous integration." --- # Continuous Integration (CI) {#continuous-integration-ci} To use the registry on your CI, you need to ensure that you have logged in to the registry by running `tuist registry login` as part of your workflow. > [!NOTE] ONLY XCODE INTEGRATION > Creating a new pre-unlocked keychain is required only if you are using the Xcode integration of packages. Since the registry credentials are stored in a keychain, you need to ensure the keychain can be accessed in the CI environment. Note some CI providers or automation tools like [Fastlane](https://fastlane.tools/) already create a temporary keychain or provide a built-in way how to create one. However, you can also create one by creating a custom step with the following code: ```bash TMP_DIRECTORY=$(mktemp -d) KEYCHAIN_PATH=$TMP_DIRECTORY/keychain.keychain KEYCHAIN_PASSWORD=$(uuidgen) security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security default-keychain -s $KEYCHAIN_PATH security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH ``` `tuist registry login` will then store the credentials in the default keychain. Ensure that your default keychain is created and unlocked _before_ `tuist registry login` is run. Additionally, you need to ensure the `TUIST_CONFIG_TOKEN` environment variable is set. You can create one by following the documentation here. An example workflow for GitHub Actions could then look like this: ```yaml name: Build jobs: build: steps: - # Your set up steps... - name: Create keychain run: | TMP_DIRECTORY=$(mktemp -d) KEYCHAIN_PATH=$TMP_DIRECTORY/keychain.keychain KEYCHAIN_PASSWORD=$(uuidgen) security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security default-keychain -s $KEYCHAIN_PATH security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH - name: Log in to the Tuist Registry env: TUIST_CONFIG_TOKEN: ${{ secrets.TUIST_CONFIG_TOKEN }} run: tuist registry login - # Your build steps ``` ### Incremental resolution across environments {#incremental-resolution-across-environments} Clean/cold resolutions are slightly faster with our registry, and you can experience even greater improvements if you persist the resolved dependencies across CI builds. Note that thanks to the registry, the size of the directory that you need to store and restore is much smaller than without the registry, taking significantly less time. To cache dependencies when using the default Xcode package integration, the best way is to specify a custom `-clonedSourcePackagesDirPath` when resolving dependencies via `xcodebuild`, such as: ```sh xcodebuild -resolvePackageDependencies -clonedSourcePackagesDirPath .build ``` Additionally, you will need to find a path of the `Package.resolved`. You can grab the path by running `ls **/Package.resolved`. The path should look something like `App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved`. For Swift packages and the XcodeProj-based integration, we can use the default `.build` directory located either in the root of the project or in the `Tuist` directory. Make sure the path is correct when setting up your pipeline. Here's an example workflow for GitHub Actions for resolving and caching dependencies when using the default Xcode package integration: ```yaml - name: Restore cache id: cache-restore uses: actions/cache/restore@v4 with: path: .build key: ${{ runner.os }}-${{ hashFiles('App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} restore-keys: .build - name: Resolve dependencies if: steps.cache-restore.outputs.cache-hit != 'true' run: xcodebuild -resolvePackageDependencies -clonedSourcePackagesDirPath .build - name: Save cache id: cache-save uses: actions/cache/save@v4 with: path: .build key: ${{ steps.cache-restore.outputs.cache-primary-key }} ``` --- URL: "/ja/guides/develop/registry" LLMS_URL: "/ja/guides/develop/registry.md" title: "Registry" titleTemplate: ":title · Develop · Guides · Tuist" description: "Optimize your Swift package resolution times by leveraging the Tuist Registry." --- # Registry {#registry} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project As the number of dependencies grows, so does the time to resolve them. While other package managers like [CocoaPods](https://cocoapods.org/) or [npm](https://www.npmjs.com/) are centralized, Swift Package Manager is not. Because of that, SwiftPM needs to resolve dependencies by doing a deep clone of each repository, which can be time-consuming. To address this, Tuist provides an implementation of the [Package Registry](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/PackageRegistryUsage.md), so you can download only the commit you _actually need_. The packages in the registry are based on the [Swift Package Index](https://swiftpackageindex.com/) – if you can find a package there, the package is also available in the Tuist Registry. Additionally, the packages are distributed across the globe using an edge storage for minimum latency when resolving them. ## Usage {#usage} To set up and log in to the registry, run the following command in your project's directory: ```bash tuist registry setup ``` This command generates a registry configuration files and logs you in to the registry. To ensure the rest of your team can access the registry, ensure the generated files is committed and that your team members run the following command to log in: ```bash tuist registry login ``` Now you can access the registry! To resolve dependencies from the registry instead of from source control, continue reading based on your project setup: - Xcode project - Generated project with the Xcode package integration - Generated project with the XcodeProj-based package integration - Swift package To set up the registry on the CI, follow this guide: Continuous integration. ### Package registry identifiers {#package-registry-identifiers} If you want to ensure that the registry is used every time you resolve dependencies, you will need to update `dependencies` in your `Package.swift` file to use the registry identifier instead of a URL. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `swift-composable-architecture` package, do the following: > [!NOTE] > The identifier can't contain more than one dot. If the repository name contains a dot, it's replaced with an underscore. > For example, the `https://github.com/groue/GRDB.swift` package would have the registry identifier `groue.GRDB_swift`. --- URL: "/ja/guides/develop/projects/tma-architecture" LLMS_URL: "/ja/guides/develop/projects/tma-architecture.md" title: "The Modular Architecture (TMA)" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about The Modular Architecture (TMA) and how to structure your projects using it." --- # The Modular Architecture (TMA) {#the-modular-architecture-tma} TMA is an architectural approach to structure Apple OS applications to enable scalability, optimize build and test cycles, and ensure good practices in your team. Its core idea is to build your apps by building independent features that are interconnected using clear and concise APIs. These guidelines introduce the principles of the architecture, helping you identify and organize your application features in different layers. It also introduces tips, tools, and advice if you decide to use this architecture. > [!INFO] µFEATURES > This architecture was previously known as µFeatures. We've renamed it to The Modular Architecture (TMA) to better reflect its purpose and the principles behind it. ## Core principle {#core-principle} Developers should be able to **build, test, and try** their features fast, independently of the main app, and while ensuring Xcode features like UI previews, code completion, and debugging work reliably. ## What is a module {#what-is-a-module} A module represents an application feature and is a combination of the following five targets (where target referts to an Xcode target): - **Source:** Contains the feature source code (Swift, Objective-C, C++, JavaScript...) and its resources (images, fonts, storyboards, xibs). - **Interface:** It's a companion target that contains the public interface and models of the feature. - **Tests:** Contains the feature unit and integration tests. - **Testing:** Provides testing data that can be used in tests and the example app. It also provides mocks for module classes and protocols that can be used by other features as we'll see later. - **Example:** Contains an example app that developers can use to try out the feature under certain conditions (different languages, screen sizes, settings). We recommend following a naming convention for targets, something that you can enforce in your project thanks to Tuist's DSL. | Target | Dependencies | Content | | ------------------ | --------------------------- | --------------------------- | | `Feature` | `FeatureInterface` | Source code and resources | | `FeatureInterface` | - | Public interface and models | | `FeatureTests` | `Feature`, `FeatureTesting` | Unit and integration tests | | `FeatureTesting` | `FeatureInterface` | Testing data and mocks | | `FeatureExample` | `FeatureTesting`, `Feature` | Example app | > [!TIP] UI Previews > `Feature` can use `FeatureTesting` as a Development Asset to allow for UI previews > [!IMPORTANT] COMPILER DIRECTIVES INSTEAD OF TESTING TARGETS > Alternatively, you can use compiler directives to include test data and mocks in the `Feature` or `FeatureInterface` targets when compiling for `Debug`. You simplify the graph, but you'll end up compiling code that you won't need for running the app. ## Why a module {#why-a-module} ### Clear and concise APIs {#clear-and-concise-apis} When all the app source code lives in the same target it is very easy to build implicit dependencies in code and end up with the so well-known spaghetti code. Everything is strongly coupled, the state is sometimes unpredictable, and introducing new changes become a nightmare. When we define features in independent targets we need to design public APIs as part of our feature implementation. We need to decide what should be public, how our feature should be consumed, what should remain private. We have more control over how we want our feature clients to use the feature and we can enforce good practices by designing safe APIs. ### Small modules {#small-modules} [Divide and conquer](https://en.wikipedia.org/wiki/Divide_and_conquer). Working in small modules allows you to have more focus and test and try the feature in isolation. Moreover, development cycles are much faster since we have a more selective compilation, compiling only the components that are necessary to get our feature working. The compilation of the whole app is only necessary at the very end of our work, when we need to integrate the feature into the app. ### Reusability {#reusability} Reusing code across apps and other products like extensions is encouraged using frameworks or libraries. By building modules reusing them is pretty straightforward. We can build an iMessage extension, a Today Extension, or a watchOS application by just combining existing modules and adding _(when necessary)_ platform-specific UI layers. ## Dependencies {#dependencies} When a module depends on another module, it declares a dependency against its interface target. The benefit of this is two-fold. It prevents the implementation of a module to be coupled to the implementation of another module, and it speeds up clean builds because they only have to compile the implementation of our feature, and the interfaces of direct and transitive dependencies. This approach is inspired by SwiftRock's idea of [Reducing iOS Build Times by using Interface Modules](https://swiftrocks.com/reducing-ios-build-times-by-using-interface-targets). Depending on interfaces requires apps to build the graph of implementations at runtime, and dependency-inject it into the modules that need it. Although TMA is non-opinionated about how to do this, we recommend using dependency-injection solutions or patterns or solutions that don't add built-time indirections or use platform APIs that were not designed for this purpose. ## Product types {#product-types} When building a module, you can choose between **libraries and frameworks**, and **static and dynamic linking** for the targets. Without Tuist, making this decision is a bit more complex because you need to configure the dependency graph manually. However, thanks to Tuist Projects, this is no longer a problem. We recommend using dynamic libraries or frameworks during development using bundle accessors to decouple the bundle-accessing logic from the library or framework nature of the target. This is key for fast compilation times and to ensure [SwiftUI Previews](https://developer.apple.com/documentation/swiftui/previews-in-xcode) work reliably. And static libraries or frameworks for the release builds to ensure the app boots fast. You can leverage dynamic configuration to change the product type at generation-time: ```bash # You'll have to read the value of the variable from the manifest {#youll-have-to-read-the-value-of-the-variable-from-the-manifest} # and use it to change the linking type {#and-use-it-to-change-the-linking-type} TUIST_PRODUCT_TYPE=static-library tuist generate ``` ```swift // You can place this in your manifest files or helpers // and use the returned value when instantiating targets. func productType() -> Product { if case let .string(productType) = Environment.productType { return productType == "static-library" ? .staticLibrary : .framework } else { return .framework } } ``` > [!IMPORTANT] MERGEABLE LIBRARIES > Apple attempted to alleviate the cumbersomeness of switching between static and dynamic libraries by introducing [mergeable libraries](https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries). However, that introduces build-time non-determinism that makes your build non-reproducible and harder to optimize so we don't recommend using it. ## Code {#code} TMA is non-opinionated about the code architecture and patterns for your modules. However, we'd like to share some tips based on our experience: - **Leveraging the compiler is great.** Over-leveraging the compiler might end up being non-productive and cause some Xcode features like previews to work unreliably. We recommend using the compiler to enforce good practices and catch errors early, but not to the point that it makes the code harder to read and maintain. - **Use Swift Macros sparingly.** They can be very powerful but can also make the code harder to read and maintain. - **Embrace the platform and the language, don't abstract them.** Trying to come up with ellaborated abstraction layers might end up being counterproductive. The platform and the language are powerful enough to build great apps without the need for additional abstraction layers. Use good programming and design patterns as a reference to build your features. ## Resources {#resources} - [Building µFeatures](https://speakerdeck.com/pepibumur/building-ufeatures) - [Framework Oriented Programming](https://speakerdeck.com/pepibumur/framework-oriented-programming-mobilization-dot-pl) - [A Journey into frameworks and Swift](https://speakerdeck.com/pepibumur/a-journey-into-frameworks-and-swift) - [Leveraging frameworks to speed up our development on iOS - Part 1](https://developers.soundcloud.com/blog/leveraging-frameworks-to-speed-up-our-development-on-ios-part-1) - [Library Oriented Programming](https://academy.realm.io/posts/justin-spahr-summers-library-oriented-programming/) - [Building Modern Frameworks](https://developer.apple.com/videos/play/wwdc2014/416/) - [The Unofficial Guide to xcconfig files](https://pewpewthespells.com/blog/xcconfig_guide.html) - [Static and Dynamic Libraries](https://pewpewthespells.com/blog/static_and_dynamic_libraries.html) --- URL: "/ja/guides/develop/projects/templates" LLMS_URL: "/ja/guides/develop/projects/templates.md" title: "Templates" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to create and use templates in Tuist to generate code in your projects." --- # Templates {#templates} In projects with an established architecture, developers might want to bootstrap new components or features that are consistent with the project. With `tuist scaffold` you can generate files from a template. You can define your own templates or use the ones that are vendored with Tuist. These are some scenarios where scaffolding might be useful: - Create a new feature that follows a given architecture: `tuist scaffold viper --name MyFeature`. - Create new projects: `tuist scaffold feature-project --name Home` > [!NOTE] NON-OPINIONATED > Tuist is not opinionated about the content of your templates, and what you use them for. They are only required to be in a specific directory. ## Defining a template {#defining-a-template} To define templates, you can run `tuist edit` and then create a directory called `name_of_template` under `Tuist/Templates` that represents your template. So if you are creating a template called `framework`, you should create a new directory `framework` at `Tuist/Templates` with a manifest file called `framework.swift` that could look like this: To define templates, you can run `tuist edit` and then create a directory called `name_of_template` under `Tuist/Templates` that represents your template. To define templates, you can run `tuist edit` and then create a directory called `name_of_template` under `Tuist/Templates` that represents your template. ```swift import ProjectDescription let nameAttribute: Template.Attribute = .required("name") let template = Template( description: "Custom template", attributes: [ nameAttribute, .optional("platform", default: "ios"), ], items: [ .string( path: "Project.swift", contents: "My template contents of name \(nameAttribute)" ), .file( path: "generated/Up.swift", templatePath: "generate.stencil" ), .directory( path: "destinationFolder", sourcePath: "sourceFolder" ), ] ) ``` ## Using a template {#using-a-template} After defining the template, we can use it from the `scaffold` command: ```bash tuist scaffold name_of_template --name Name --platform macos ``` > [!NOTE] > Since platform is an optional argument, we can also call the command without the `--platform macos` argument. If `.string` and `.files` don't provide enough flexibility, you can leverage the [Stencil](https://stencil.fuller.li/en/latest/) templating language via the `.file` case. Besides that, you can also use additional filters defined here. Using string interpolation, `\(nameAttribute)` above would resolve to `{{ name }}`. If you'd like to use Stencil filters in the template definition, you can use that interpolation manually and add any filters you like. For example, you might use `{ { name | lowercase } }` instead of `\(nameAttribute)` to get the lowercased value of the name attribute. You can also use `.directory` which gives the possibility to copy entire folders to a given path. > [!TIP] PROJECT DESCRIPTION HELPERS > Templates support the use of project description helpers to reuse code across templates. --- URL: "/ja/guides/develop/projects/synthesized-files" LLMS_URL: "/ja/guides/develop/projects/synthesized-files.md" title: "Synthesized files" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about synthesized files in Tuist projects." --- # Synthesized files {#synthesized-files} Tuist can generate files and code at generation-time to bring some convenience to managing and working with Xcode projects. In this page you'll learn about this functionality, and how you can use it in your projects. ## Target resources {#target-resources} Xcode projects support adding resources to targets. However, they present teams with a few challenges, specially when working with a modular project where sources and resources are often moved around: - **Inconsistent runtime access**: Where the resources end up in the final product and how you access them depends on the target product. For example, if your target represents an application, the resources are copied to the application bundle. This leads to code accessing the resources that makes assumptions on the bundle structure, which is not ideal because it makes the code harder to reason about and the resources to move around. - **Products that don't support resources**: There are certain products like static libraries that are not bundles and therefore don't support resources. Because of that, you either have to resort to a different product type, for example frameworks, that might add some overhead on your project or app. For example, static frameworks will be linked statically to the final product, and a build phase is required to only copy the resources to the final product. Or dynamic frameworks, where Xcode will copy both the binary and the resources into the final product, but it'll increase the startup time of your app because the framework needs to be loaded dynamically. - **Prone to runtime errors**: Resources are identified by their name and extension (strings). Therefore, a typo in any of those will lead to a runtime error when trying to access the resource. This is not ideal because it's not caught at compile time and might lead to crashes in release. Tuist solves the problems above by **synthesizing a unified interface to access bundles and resources** that abstracts away the implementation details. > [!IMPORTANT] RECOMMENDED > Even though accessing resources through the Tuist-synthesized interface is not mandatory, we recommend it because it makes the code easier to reason about and the resources to move around. ## Resources {#resources} Tuist provides interfaces to declare the content of files such as `Info.plist` or entitlements in Swift. This is useful to ensure consistency across targets and projects, and leverage the compiler to catch issues at compile time. You can also come up with your own abstractions to model the content and share it across targets and projects. When your project is generated, Tuist will synthesize the content of those files and write them into the `Derived` directory relative to the directory containing the project that defines them. > [!TIP] GITIGNORE THE DERIVED DIRECTORY > We recommend adding the `Derived` directory to the `.gitignore` file of your project. ## Bundle accessors {#bundle-accessors} Tuist synthesizes an interface to access the bundle that contains the target resources. ### Swift {#swift} The target will contain an extension of the `Bundle` type that exposes the bundle: ```swift let bundle = Bundle.module ``` ### Objective-C {#objectivec} In Objective-C, you'll get an interface `{Target}Resources` to access the bundle: ```objc NSBundle *bundle = [MyFeatureResources bundle]; ``` > [!TIP] SUPPORTING RESOURCES IN LIBRARIES THROUGH BUNDLES > If a target product, for example a library, doesn't support resources, Tuist will include the resources in a target of product type `bundle` ensuring that it ends up in the final product and that the interface points to the right bundle. ## Resource accessors {#resource-accessors} Resources are identified by their name and extension using strings. This is not ideal because it's not caught at compile time and might lead to crashes in release. To prevent that, Tuist integrates [SwiftGen](https://github.com/SwiftGen/SwiftGen) into the project generation process to synthesize an interface to access the resources. Thanks to that, you can confidently access the resources leveraging the compiler to catch any issues. Tuist includes [templates](https://github.com/tuist/tuist/tree/main/Sources/TuistGenerator/Templates) to synthesize accessors for the following resource types by default: | Resource type | Synthesized file | | ----------------- | ------------------------ | | Images and colors | `Assets+{Target}.swift` | | Strings | `Strings+{Target}.swift` | | Plists | `{NameOfPlist}.swift` | | Fonts | `Fonts+{Target}.swift` | | Files | `Files+{Target}.swift` | > Note: You can disable the synthesizing of resource accessors on a per-project basis by passing the `disableSynthesizedResourceAccessors` option to the project options. #### Custom templates {#custom-templates} If you want to provide your own templates to synthesize accessors to other resource types, which must be supported by [SwiftGen](https://github.com/SwiftGen/SwiftGen), you can create them at `Tuist/ResourceSynthesizers/{name}.stencil`, where the name is the camel-case version of the resource. | Resource | Template name | | ---------------- | -------------------------- | | strings | `Strings.stencil` | | assets | `Assets.stencil` | | plists | `Plists.stencil` | | fonts | `Fonts.stencil` | | coreData | `CoreData.stencil` | | interfaceBuilder | `InterfaceBuilder.stencil` | | json | `JSON.stencil` | | yaml | `YAML.stencil` | | files | `Files.stencil` | If you want to configure the list of resource types to synthesize accessors for, you can use the `Project.resourceSynthesizers` property passing the list of resource synthesizers you want to use: ```swift let project = Project(resourceSynthesizers: [.string(), .fonts()]) ``` > [!NOTE] REFERENCE > You can check out [this fixture](https://github.com/tuist/tuist/tree/main/fixtures/ios_app_with_templates) to see an example of how to use custom templates to synthesize accessors to resources. --- URL: "/ja/guides/develop/projects/plugins" LLMS_URL: "/ja/guides/develop/projects/plugins.md" title: "Plugins" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to create and use plugins in Tuist to extend its functionality." --- # Plugins {#plugins} Plugins are a tool to share and reuse Tuist artifacts across multiple projects. The following artifacts are supported: - Project description helpers across multiple projects. - Templates across multiple projects. - Tasks across multiple projects. - Resource accessor template across multiple projects Note that plugins are designed to be a simple way to extend Tuist's functionality. Therefore there are **some limitations to consider**: - A plugin cannot depend on another plugin. - A plugin cannot depend on third-party Swift packages - A plugin cannot use project description helpers from the project that uses the plugin. If you need more flexibility, consider suggesting a feature for the tool or building your own solution upon Tuist's generation framework, [`TuistGenerator`](https://github.com/tuist/tuist/tree/main/Sources/TuistGenerator). ## Plugin types {#plugin-types} ### Project description helper plugin {#project-description-helper-plugin} A project description helper plugin is represented by a directory containing a `Plugin.swift` manifest file that declares the plugin's name and a `ProjectDescriptionHelpers` directory containing the helper Swift files. ::: code-group ```bash [Plugin.swift] import ProjectDescription let plugin = Plugin(name: "MyPlugin") ``` ```bash [Directory structure] . ├── ... ├── Plugin.swift ├── ProjectDescriptionHelpers └── ... ``` ::: ### Resource accessor templates plugin {#resource-accessor-templates-plugin} If you need to share synthesized resource accessors you can use this type of plugin. The plugin is represented by a directory containing a `Plugin.swift` manifest file that declares the plugin's name and a `ResourceSynthesizers` directory containing the resource accessor template files. ::: code-group ```bash [Plugin.swift] import ProjectDescription let plugin = Plugin(name: "MyPlugin") ``` ```bash [Directory structure] . ├── ... ├── Plugin.swift ├── ResourceSynthesizers ├───── Strings.stencil ├───── Plists.stencil ├───── CustomTemplate.stencil └── ... ``` ::: The name of the template is the [camel case](https://en.wikipedia.org/wiki/Camel_case) version of the resource type: | Resource type | Template file name | | ----------------- | ---------------------------------------- | | Strings | Strings.stencil | | Assets | Assets.stencil | | Property Lists | Plists.stencil | | Fonts | Fonts.stencil | | Core Data | CoreData.stencil | | Interface Builder | InterfaceBuilder.stencil | | JSON | JSON.stencil | | YAML | YAML.stencil | When defining the resource synthesizers in the project, you can specify the plugin name to use the templates from the plugin: ```swift let project = Project(resourceSynthesizers: [.strings(plugin: "MyPlugin")]) ``` ### Task plugin {#task-plugin-badge-typewarning-textdeprecated-} > [!WARNING] DEPRECATED > Task plugins are deprecated. Check out [this blog post](https://tuist.dev/blog/2025/04/15/automation-in-swift-projects) if you are looking for an automation solution for your project. Tasks are `$PATH`-exposed executables that are invocable through the `tuist` command if they follow the naming convention `tuist-`. In earlier versions, Tuist provided some weak conventions and tools under `tuist plugin` to `build`, `run`, `test` and `archive` tasks represented by executables in Swift Packages, but we have deprecated this feature since it increases the maintenance burden and complexity of the tool. If you were using Tuist for distributing tasks, we recommend building your - You can continue using the `ProjectAutomation.xcframework` distributed with every Tuist release to have access to the project graph from your logic with `let graph = try Tuist.graph()`. The command uses sytem process to run the `tuist` command, and return the in-memory representation of the project graph. - To distribute tasks, we recommend including the a fat binary that supports the `arm64` and `x86_64` in GitHub releases, and using [Mise](https://mise.jdx.dev) as an installation tool. To instruct Mise on how to install your tool, you'll need a plugin repository. You can use [Tuist's](https://github.com/asdf-community/asdf-tuist) as a reference. - If you name your tool `tuist-{xxx}` and users can install it by running `mise install`, they can run it either invoking it directly, or through `tuist xxx`. > [!NOTE] THE FUTURE OF PROJECTAUTOMATION > We plan to consolidate the models of `ProjectAutomation` and `XcodeGraph` into a single backward-compatible framework that exposes the entirity of the project graph to the user. Moreover, we'll extract the generation logic into a new layer, `XcodeGraph` that you can also use from your own CLI. Think of it as building your own Tuist. ## Using plugins {#using-plugins} To use a plugin, you'll have to add it to your project's `Tuist.swift` manifest file: ```swift import ProjectDescription let tuist = Tuist( project: .tuist(plugins: [ .local(path: "/Plugins/MyPlugin") ]) ) ``` If you want to reuse a plugin across projects that live in different repositories, you can push your plugin to a Git repository and reference it in the `Tuist.swift` file: ```swift import ProjectDescription let tuist = Tuist( project: .tuist(plugins: [ .git(url: "https://url/to/plugin.git", tag: "1.0.0"), .git(url: "https://url/to/plugin.git", sha: "e34c5ba") ]) ) ``` After adding the plugins, `tuist install` will fetch the plugins in a global cache directory. > [!NOTE] NO VERSION RESOLUTION > As you might have noted, we don't provide version resolution for plugins. We recommend using Git tags or SHAs to ensure reproducibility. > [!TIP] PROJECT DESCRIPTION HELPERS PLUGINS > When using a project description helpers plugin, the name of the module that contains the helpers is the name of the plugin > > ```swift > import ProjectDescription > import MyTuistPlugin > let project = Project.app(name: "MyCoolApp", platform: .iOS) > ``` --- URL: "/ja/guides/develop/projects/manifests" LLMS_URL: "/ja/guides/develop/projects/manifests.md" title: "Manifests" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the manifest files that Tuist uses to define projects and workspaces and configure the generation process." --- # Manifests {#manifests} Tuist defaults to Swift files as the primary way to define projects and workspaces and configure the generation process. These files are referred to as **manifest files** throughout the documentation. The decision of using Swift was inspired by the [Swift Package Manager](https://www.swift.org/documentation/package-manager/), which also uses Swift files to define packages. Thanks to the usage of Swift, we can leverage the compiler to validate the correctness of the content and reuse code across different manifest files, and Xcode to provide a first-class editing experience thanks to the syntax highlighting, auto-completion, and validation. > [!NOTE] CACHING > Since manifest files are Swift files that need to be compiled, Tuist caches the compilation results to speed up the parsing process. Therefore, you'll notice that the first time you run Tuist, it might take a bit longer to generate the project. Subsequent runs will be faster. ## Project.swift {#projectswift} The `Project.swift` manifest declares an Xcode project. The project gets generated in the same directory where the manifest file is located with the name indicated in the `name` property. ```swift // Project.swift let project = Project( name: "App", targets: [ // .... ] ) ``` > [!WARNING] ROOT VARIABLES > The only variable that should be at the root of the manifest is `let project = Project(...)`. If you need to reuse code across various parts of the manifest, you can use Swift functions. ## Workspace.swift {#workspaceswift} By default, Tuist generates an [Xcode Workspace](https://developer.apple.com/documentation/xcode/projects-and-workspaces) containing the project being generated and the projects of its dependencies. If for any reason you'd like to customize the workspace to add additional projects or include files and groups, you can do so by defining a `Workspace.swift` manifest. ```swift // Workspace.swift import ProjectDescription let workspace = Workspace( name: "App-Workspace", projects: [ "./App", // Path to directory containing the Project.swift file ] ) ``` > [!NOTE] > Tuist will resolve the dependency graph and include the projects of the dependencies in the workspace. You don't need to include them manually. This is necessary for the build system to resolve the dependencies correctly. ### Multi or mono-project {#multi-or-monoproject} A question that often comes up is whether to use a single project or multiple projects in a workspace. In a world without Tuist where a mono-project setup would lead to frequent Git conflicts the usage of workspaces is encouraged. However, since we don't recommend including the Tuist-generated Xcode projects in the Git repository, Git conflicts are not an issue. Therefore, the decision of using a single project or multiple projects in a workspace is up to you. In the Tuist project we lean on mono-projects because the cold generation time is faster (fewer manifest files to compile) and we leverage project description helpers as a unit of encapsulation. However, you might want to use Xcode projects as a unit of encapsulation to represent different domains of your application, which aligns more closely with the Xcode's recommended project structure. ## Tuist.swift {#tuistswift} Tuist provides sensible defaults to simplify project configuration. However, you can customize the configuration by defining a `Tuist.swift` at the root of the project, which is used by Tuist to determine the root of the project. ```swift import ProjectDescription let tuist = Tuist( project: .tuist(generationOptions: .options(enforceExplicitDependencies: true)) ) ``` --- URL: "/ja/guides/develop/projects/inspect/implicit-dependencies" LLMS_URL: "/ja/guides/develop/projects/inspect/implicit-dependencies.md" title: "Implicit imports" titleTemplate: ":title · Inspect · Projects · Develop · Guides · Tuist" description: "Learn how to use Tuist to find implicit dependencies." --- # Implicit imports {#implicit-imports} To alleviate the complexity of maintaining an Xcode project graph with raw Xcode project, Apple designed the build system in a way that allows dependencies to be implicitly defined. This means that a product, for example an app, can depend on a framework, even without declaring the dependency explicitly. At a small scale, this is is fine, but as the project graph grows in complexity, the implicitness might manifest as unreliable incremental builds or editor-based features such as previews or code completion. The problem is that you can't prevent implicit dependencies from happening. Any developer can add an `import` statement to their Swift code, and the implicit dependency will be created. This is where Tuist comes in. Tuist provides a command to inspect the implicit dependencies by statically analyzing the code in your project. The following command will output the implicit dependencies of your project: ```bash tuist inspect implicit-imports ``` If the command detects any implicit imports, it exits with an exit code other than zero. > [!TIP] VALIDATE IN CI > We strongly recommend to run this command as part of your continuous integration command every time new code is pushed upstream. > [!IMPORTANT] NOT ALL IMPLICIT CASES ARE DETECTED > Since Tuist relies on static code analysis to detect implicit dependencies, it might not catch all cases. For example, Tuist is unable to understand conditional imports through compiler directives in code. --- URL: "/ja/guides/develop/projects/hashing" LLMS_URL: "/ja/guides/develop/projects/hashing.md" title: "Hashing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about Tuist's hashing logic upon which features like binary caching and selective testing are built." --- # Hashing {#hashing} Features like caching or smart test execution require a way to determine whether a target has changed. Tuist calculates a hash for each target in the dependency graph to determine if a target has changed. The hash is calculated based on the following attributes: - The target's attributes (e.g., name, platform, product, etc.) - The target's files - The hash of the target's dependencies ### Cache attributes {#cache-attributes} Additionally, when calculating the hash for caching, we also hash the following attributes. #### Swift version {#swift-version} We hash the Swift version obtained from running the command `/usr/bin/xcrun swift --version` to prevent compilation errors due to Swift version mismatches between the targets and the binaries. > [!NOTE] MODULE STABILITY > Previous versions of binary caching relied on the `BUILD_LIBRARY_FOR_DISTRIBUTION` build setting to enable [module stability](https://www.swift.org/blog/library-evolution#enabling-library-evolution-support) and enable using binaries with any compiler version. However, it caused compilation issues in projects with targets that don't support module stability. Generated binaries are bound to the Swift version used to compile them, and the Swift version must match the one used to compile the project. #### Configuration {#configuration} The idea behind the flag `-configuration` was to ensure debug binaries were not used in release builds and viceversa. However, we are still missing a mechanism to remove the other configurations from the projects to prevent them from being used. ## Debugging {#debugging} If you notice non-deterministic behaviors when using the caching across environments or invocations, it might be related to differences across the environments or a bug in the hashing logic. We recommend following these steps to debug the issue: 1. Ensure the same [configuration](#configuration) and [Swift version](#swift-version) is used across environments. 2. Check if there are differences between the Xcode projects generated by two consecutive invocations of `tuist generate` or across environments. You can use the `diff` command to compare the projects. The generated projects might include **absolute paths** causing the hashing logic to be non-deterministic. > [!NOTE] BETTER DEBUGGING EXPERIENCE PLANNED > Improving our debugging experience is in our roadmap. The print-hashes command, which lacks the context to understand the differences, will be replaced by a more user-friendly command that uses a tree-like structure to show the differences between the hashes. --- URL: "/ja/guides/develop/projects/editing" LLMS_URL: "/ja/guides/develop/projects/editing.md" title: "Editing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to use Tuist's edit workflow to declare your project leveraging Xcode's build system and editor capabilities." --- # Editing {#editing} Unlike traditional Xcode projects or Swift Packages, where changes are done through Xcode's UI, Tuist-managed projects are defined in Swift code contained in **manifest files**. If you're familiar with Swift Packages and the `Package.swift` file, the approach is very similar. You could edit these files using any text editor, but we recommend to use Tuist-provided workflow for that, `tuist edit`. The workflow creates an Xcode project that contains all manifest files and allows you to edit and compile them. Thanks to using Xcode, you get all the benefits of **code completion, syntax highlighting, and error checking**. ## プロジェクトの編集 {#edit-the-project} To edit your project, you can run the following command in a Tuist project directory or a sub-directory: ```bash tuist edit ``` The command creates an Xcode project in a global directory and opens it in Xcode. The project includes a `Manifests` directory that you can build to ensure all your manifests are valid. > [!INFO] GLOB-RESOLVED MANIFESTS > `tuist edit` resolves the manifests to be included by using the glob `**/{Manifest}.swift` from the project's root directory (the one containing the `Tuist.swift` file). Make sure there's a valid `Tuist.swift` at the root of the project. ## Edit and generate workflow {#edit-and-generate-workflow} As you might have noticed, the editing can't be done from the generated Xcode project. That's by design to prevent the generated project from having a dependency on Tuist, ensuring you can move from Tuist in the future with little effort. When iterating on a project, we recommend running `tuist edit` from a terminal session to get an Xcode project to edit the project, and use another terminal session to run `tuist generate`. --- URL: "/ja/guides/develop/projects/dynamic-configuration" LLMS_URL: "/ja/guides/develop/projects/dynamic-configuration.md" title: "Dynamic configuration" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how how to use environment variables to dynamically configure your project." --- # Dynamic configuration {#dynamic-configuration} There are certain scenarios where you might need to dynamically configure your project at generation time. For example, you might want to change the name of the app, the bundle identifier, or the deployment target based on the environment where the project is being generated. Tuist supports that via environment variables, which can be accessed from the manifest files. ## Configuration through environment variables {#configuration-through-environment-variables} Tuist allows passing configuration through environment variables that can be accessed from the manifest files. For example: ```bash TUIST_APP_NAME=MyApp tuist generate ``` If you want to pass multiple environment variables just separate them with a space. For example: ```bash TUIST_APP_NAME=MyApp TUIST_APP_LOCALE=pl tuist generate ``` ## Reading the environment variables from manifests {#reading-the-environment-variables-from-manifests} Variables can be accessed using the `Environment` type. Any variables following the convention `TUIST_XXX` defined in the environment or passed to Tuist when running commands will be accessible using the `Environment` type. The following example shows how we access the `TUIST_APP_NAME` variable: ```swift func appName() -> String { if case let .string(environmentAppName) = Environment.appName { return environmentAppName } else { return "MyApp" } } ``` Accessing variables returns an instance of type `Environment.Value?` which can take any of the following values: | Case | Description | | ----------------- | ----------------------------------------------------------- | | `.string(String)` | Used when the variable represents a string. | You can also retrieve the string or boolean `Environment` variable using either of the helper methods defined below, these methods require a default value to be passed to ensure the user gets consistent results each time. This avoids the need to define the function appName() defined above. ::: code-group ```swift [String] Environment.appName.getString(default: "TuistServer") ``` ```swift [Boolean] Environment.isCI.getBoolean(default: false) ``` ::: --- URL: "/ja/guides/develop/projects/directory-structure" LLMS_URL: "/ja/guides/develop/projects/directory-structure.md" title: "Directory structure" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the structure of Tuist projects and how to organize them." --- # Directory structure {#directory-structure} Although Tuist projects are commonly used to supersede Xcode projects, they are not limited to this use case. Tuist projects are also used to generate other types of projects, such as SPM packages, templates, plugins, and tasks. This document describes the structure of Tuist projects and how to organize them. In later sections, we'll cover how to define templates, plugins, and tasks. ## Standard Tuist projects {#standard-tuist-projects} Tuist projects are **the most common type of project generated by Tuist.** They are used to build apps, frameworks, and libraries among others. Unlike Xcode projects, Tuist projects are defined in Swift, which makes them more flexible and easier to maintain. Tuist projects are also more declarative, which makes them easier to understand and reason about. The following structure shows a typical Tuist project that generates an Xcode project: ```bash Tuist.swift Tuist/ Package.swift ProjectDescriptionHelpers/ Projects/ App/ Project.swift Feature/ Project.swift Workspace.swift ``` - **Tuist directory:** This directory has two purposes. First, it signals **where the root of the project is**. Second, it's the container for the following files: This allows constructing paths relative to the root of the project, and also running Tuist commands from any directory within the project. Second, it's the container for the following files: This allows constructing paths relative to the root of the project, and also running Tuist commands from any directory within the project. - **ProjectDescriptionHelpers:** This directory contains Swift code that's shared across all the manifest files. Manifest files can `import ProjectDescriptionHelpers` to use the code defined in this directory. Sharing code is useful to avoid duplications and ensure consistency across the projects. - **Package.swift:** This file contains Swift Package dependencies for Tuist to integrate them using Xcode projects and targets (like [CocoaPods](https://cococapods)) that are configurable and optimizable. Learn more here. - **Root directory**: The root directory of your project that also contains the `Tuist` directory. - Tuist.swift: This file contains configuration for Tuist that's shared across all the projects, workspaces, and environments. For example, it can be used to disable automatic generation of schemes, or to define the deployment target of the projects. - Workspace.swift: This manifest represents an Xcode workspace. It's used to group other projects and can also add additional files and schemes. - Project.swift: This manifest represents an Xcode project. It's used to define the targets that are part of the project, and their dependencies. When interacting with the above project, commands expect to find either a `Workspace.swift` or a `Project.swift` file in the working directory or the directory indicated via the `--path` flag. The manifest should be in a directory or subdirectory of a directory containing a `Tuist` directory, which represents the root of the project. > [!TIP] > Xcode workspaces allowed splitting projects into multiple Xcode projects to reduce the likelihood of merge conflicts. If that's what you were using workspaces for, you don't need them in Tuist. Tuist auto-generates a workspace containing a project and its dependencies' projects. ## Swift Package {#swift-package-badge-typewarning-textbeta-} Tuist also supports SPM package projects. If you are working on an SPM package, you shouldn't need to update anything. Tuist automatically picks up on your root `Package.swift` and all the features of Tuist work as if it was a `Project.swift` manifest. To get started, run `tuist install` and `tuist generate` in your SPM package. Your project should now have all the same schemes and files that you would see in the vanilla Xcode SPM integration. However, now you can also run `tuist cache` and have majority of your SPM dependencies and modules precompiled, making subsequent builds extremely fast. --- URL: "/ja/guides/develop/projects/dependencies" LLMS_URL: "/ja/guides/develop/projects/dependencies.md" title: "Dependencies" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to declare dependencies in your Tuist project." --- # Dependencies {#dependencies} When a project grows, it's common to split it into multiple targets to share code, define boundaries, and improve build times. Multiple targets means defining dependencies between them forming a **dependency graph**, which might include external dependencies as well. ## XcodeProj-codified graphs {#xcodeprojcodified-graphs} Due to Xcode and XcodeProj's design, the maintenance of a dependency graph can be a tedious and error-prone task. Here are some examples of the problems that you might encounter: - Because Xcode's build system outputs all the project's products into the same directory in derived data, targets might be able to import products that they shouldn't. Compilations might fail on CI, where clean builds are more common, or later on when a different configuration is used. - The transitive dynamic dependencies of a target need to be copied into any of the directories that are part of the `LD_RUNPATH_SEARCH_PATHS` build setting. If they aren't, the target won't be able to find them at runtime. This is easy to think about and set up when the graph is small, but it becomes a problem as the graph grows. - When a target links a static [XCFramework](https://developer.apple.com/documentation/xcode/creating-a-multi-platform-binary-framework-bundle), the target needs an additional build phase for Xcode to process the bundle and extract the right binary for the current platform and architecture. This build phase is not added automatically, and it's easy to forget to add it. The above are just a few examples, but there are many more that we've encountered over the years. Imagine if you required a team of engineers to maintain a dependency graph and ensure its validity. Or even worse, that the intricacies were resolved at build-time by a closed-source build system that you can't control or customize. Sounds familiar? This is the approach that Apple took with Xcode and XcodeProj and that the Swift Package Manager has inherited. We strongly believe that the dependency graph should be **explicit** and **static** because only then can it be **validated** and **optimized**. With Tuist, you focus on describing what depends on what, and we take care of the rest. The intricacies and implementation details are abstracted away from you. In the following sections you'll learn how to declare dependencies in your project. > [!TIP] GRAPH VALIDATION > Tuist validates the graph when generating the project to ensure that there are no cycles and that all the dependencies are valid. Thanks to this, any team can take part in evolving the dependency graph without worrying about breaking it. ## Local dependencies {#local-dependencies} Targets can depend on other targets in the same and different projects, and on binaries. When instantiating a `Target`, you can pass the `dependencies` argument with any of the following options: - `Target`: Declares a dependency with a target within the same project. - `Project`: Declares a dependency with a target in a different project. - `Framework`: Declares a dependency with a binary framework. - `Library`: Declares a dependency with a binary library. - `XCFramework`: Declares a dependency with a binary XCFramework. - `SDK`: Declares a dependency with a system SDK. - `XCTest`: Declares a dependency with XCTest. > [!NOTE] DEPENDENCY CONDITIONS > Every dependency type accepts a `condition` option to conditionally link the dependency based on the platform. By default, it links the dependency for all platforms the target supports. ## External dependencies {#external-dependencies} Tuist also allows you to declare external dependencies in your project. ### Swift Packages {#swift-packages} Swift Packages are our recommended way of declaring dependencies in your project. You can integrate them using Xcode's default integration mechanism or using Tuist's XcodeProj-based integration. #### Tuist's XcodeProj-based integration {#tuists-xcodeprojbased-integration} Xcode's default integration while being the most convenient one, lacks flexibility and control that's required for medium and large projects. To overcome this, Tuist offers an XcodeProj-based integration that allows you to integrate Swift Packages in your project using XcodeProj's targets. Thanks to that, we can not only give you more control over the integration but also make it compatible with workflows like caching and smart test runs. XcodeProj's integration is more likely to take more time to support new Swift Package features or handle more package configurations. However, the mapping logic between Swift Packages and XcodeProj targets is open-source and can be contributed to by the community. This is contrary to Xcode's default integration, which is closed-source and maintained by Apple. To add external dependencies, you'll have to create a `Package.swift` either under `Tuist/` or at the root of the project. ::: code-group ```swift [Tuist/Package.swift] // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription import ProjectDescriptionHelpers let packageSettings = PackageSettings( productTypes: [ "Alamofire": .framework, // default is .staticFramework ] ) #endif let package = Package( name: "PackageName", dependencies: [ .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), ], targets: [ .binaryTarget( name: "Sentry", url: "https://github.com/getsentry/sentry-cocoa/releases/download/8.40.1/Sentry.xcframework.zip", checksum: "db928e6fdc30de1aa97200576d86d467880df710cf5eeb76af23997968d7b2c7" ), ] ) ``` ::: > [!TIP] PACKAGE SETTINGS > The `PackageSettings` instance wrapped in a compiler directive allows you to configure how packages are integrated. For example, in the example above it's used to override the default product type used for packages. By default, you shouldn't need it. The `Package.swift` file is just an interface to declare external dependencies, nothing else. That's why you don't define any targets or products in the package. Once you have the dependencies defined, you can run the following command to resolve and pull the dependencies into the `Tuist/Dependencies` directory: ```bash tuist install # Resolving and fetching dependencies. {#resolving-and-fetching-dependencies} # Installing Swift Package Manager dependencies. {#installing-swift-package-manager-dependencies} ``` As you might have noticed, we take an approach similar to [CocoaPods](https://cocoapods.org)', where the resolution of dependencies is its own command. This gives control to the users over when they'd like dependencies to be resolved and updated, and allows opening the Xcode in project and have it ready to compile. This is an area where we believe the developer experience provided by Apple's integration with the Swift Package Manager degrates over time as the project grows. From your project targets you can then reference those dependencies using the `TargetDependency.external` dependency type: ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "App", organizationName: "tuist.io", targets: [ .target( name: "App", destinations: [.iPhone], product: .app, bundleId: "io.tuist.app", deploymentTargets: .iOS("13.0"), infoPlist: .default, sources: ["Targets/App/Sources/**"], dependencies: [ .external(name: "Alamofire"), // [!code ++] ] ), ] ) ``` ::: > [!NOTE] NO SCHEMES GENERATED FOR EXTERNAL PACKAGES > The **schemes** are not automatically created for Swift Package projects to keep the schemes list clean. You can create them via Xcode's UI. #### Xcode's default integration {#xcodes-default-integration} If you want to use Xcode's default integration mechanism, you can pass the list `packages` when instantiating a project: ```swift let project = Project(name: "MyProject", packages: [ .remote(url: "https://github.com/krzyzanowskim/CryptoSwift", requirement: .exact("1.8.0")) ]) ``` And then reference them from your targets: ```swift let target = .target(name: "MyTarget", dependencies: [ .package(product: "CryptoSwift", type: .runtime) ]) ``` For Swift Macros and Build Tool Plugins, you'll need to use the types `.macro` and `.plugin` respectively. > [!WARNING] SPM Build Tool Plugins > SPM build tool plugins must be declared using [Xcode's default integration](#xcode-s-default-integration) mechanism, even when using Tuist's [XcodeProj-based integration](#tuist-s-xcodeproj-based-integration) for your project dependencies. A practical application of an SPM build tool plugin is performing code linting during Xcode's "Run Build Tool Plug-ins" build phase. In a package manifest this is defined as follows: ```swift // swift-tools-version: 5.9 import PackageDescription let package = Package( name: "Framework", products: [ .library(name: "Framework", targets: ["Framework"]), ], dependencies: [ .package(url: "https://github.com/SimplyDanny/SwiftLintPlugins", .upToNextMajor(from: "0.56.1")), ], targets: [ .target( name: "Framework", plugins: [ .plugin(name: "SwiftLint", package: "SwiftLintPlugin"), ] ), ] ) ``` To generate an Xcode project with the build tool plugin intact, you must declare the package in the project manifest's `packages` array, and then include a package with type `.plugin` in a target's dependencies. ```swift import ProjectDescription let project = Project( name: "Framework", packages: [ .remote(url: "https://github.com/SimplyDanny/SwiftLintPlugins", requirement: .upToNextMajor(from: "0.56.1")), ], targets: [ .target( name: "Framework", dependencies: [ .package(product: "SwiftLintBuildToolPlugin", type: .plugin), ] ), ] ) ``` ### Carthage {#carthage} Since [Carthage](https://github.com/carthage/carthage) outputs `frameworks` or `xcframeworks`, you can run `carthage update` to output the dependencies in the `Carthage/Build` directory and then use the `.framework` or `.xcframework` target dependency type to declare the dependency in your target. You can wrap this in a script that you can run before generating the project. ```bash #!/usr/bin/env bash carthage update tuist generate ``` > [!WARNING] BUILD AND TEST > If you build and test your project through `tuist build` and `tuist test`, you will similarly need to ensure that the Carthage-resolved dependencies are present by running the `carthage update` command before `tuist build` or `tuist test` are run. ### CocoaPods {#cocoapods} [CocoaPods](https://cocoapods.org) expects an Xcode project to integrate the dependencies. You can use Tuist to generate the project, and then run `pod install` to integrate the dependencies by creating a workspace that contains your project and the Pods dependencies. You can wrap this in a script that you can run before generating the project. ```bash #!/usr/bin/env bash tuist generate pod install ``` > [!WARNING] > CocoaPods dependencies are not compatible with workflows like `build` or `test` that run `xcodebuild` right after generating the project. They are also incompatible with binary caching and selective testing since the fingerprinting logic doesn't account for the Pods dependencies. ## Static or dynamic {#static-or-dynamic} Frameworks and libraries can be linked either statically or dynamically, **a choice that has significant implications for aspects like app size and boot time**. Despite its importance, this decision is often made without much consideration. The **general rule of thumb** is that you want as many things as possible to be statically linked in release builds to achieve fast boot times, and as many things as possible to be dynamically linked in debug builds to achieve fast iteration times. The challenge with changing between static and dynamic linking in a project graph is that is not trivial in Xcode because a change has cascading effect on the entire graph (e.g. libraries can't contain resources, static frameworks don't need to be embedded). Apple tried to solve the problem with compile time solutions like Swift Package Manager's automatic decision between static and dynamic linking, or [Mergeable Libraries](https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries). However, this adds new dynamic variables to the compilation graph, adding new sources of non-determinism, and potentially causing some features like Swift Previews that rely on the compilation graph to become unreliable. Luckily, Tuist conceptually compresses the complexity associated with changing between static and dynamic and synthesizes bundle accessors that are standard across linking types. In combination with dynamic configurations via environment variables, you can pass the linking type at invocation time, and use the value in your manifests to set the product type of your targets. ```swift // Use the value returned by this function to set the product type of your targets. func productType() -> Product { if case let .string(linking) = Environment.linking { return linking == "static" ? .staticFramework : .framework } else { return .framework } } ``` Note that Tuist does not default to convenience through implicit configuration due to its costs. What this means is that we rely on you setting the linking type and any additional build settings that are sometimes required, like the [`-ObjC` linker flag](https://github.com/pointfreeco/swift-composable-architecture/discussions/1657#discussioncomment-4119184), to ensure the resulting binaries are correct. Therefore, the stance that we take is providing you with the resources, usually in the shape of documentation, to make the right decisions. > [!TIP] EXAMPLE: COMPOSABLE ARCHITECTURE > A Swift Package that many projects integrate is [Composable Architecture](https://github.com/pointfreeco/swift-composable-architecture). As described [here](https://github.com/pointfreeco/swift-composable-architecture/discussions/1657#discussioncomment-4119184) and the [troubleshooting section](#troubleshooting), you'll need to set the `OTHER_LDFLAGS` build setting to `$(inherited) -ObjC` when linking the packages statically, which is Tuist's default linking type. Alternatively, you can override the product type for the package to be dynamic. ### Scenarios {#scenarios} There are some scenarios where setting the linking entirely to static or dynamic is not feasible or a good idea. The following is a non-exhaustive list of scenarios where you might need to mix static and dynamic linking: - **Apps with extensions:** Since apps and their extensions need to share code, you might need to make those targets dynamic. Otherwise, you'll end up with the same code duplicated in both the app and the extension, causing the binary size to increase. - **Pre-compiled external dependencies:** Sometimes you are provided with pre-compiled binaries that are either static or dynamic. Static binaries can be wrapped in dynamic frameworks or libraries to be linked dynamically. When making changes to the graph, Tuist will analyze it and display a warning if it detects a "static side effect". This warning is meant to help you identify issues that might arise from linking a target statically that depends transitively on a static target through dynamic targets. These side effects often manifest as increased binary size or, in the worst cases, runtime crashes. ## Troubleshooting {#troubleshooting} ### Objective-C Dependencies {#objectivec-dependencies} When integrating Objective-C dependencies, the inclusion of certain flags on the consuming target may be necessary to avoid runtime crashes as detailed in [Apple Technical Q&A QA1490](https://developer.apple.com/library/archive/qa/qa1490/_index.html). Since the build system and Tuist have no way of inferring whether the flag is necessary or not, and since the flag comes with potentially undesirable side effects, Tuist will not automatically apply any of these flags, and because Swift Package Manager considers `-ObjC` to be included via an `.unsafeFlag` most packages cannot include it as part of their default linking settings when required. Consumers of Objective-C dependencies (or internal Objective-C targets) should apply `-ObjC` or `-force_load` flags when required by setting `OTHER_LDFLAGS` on consuming targets. ### Firebase & Other Google Libraries {#firebase-other-google-libraries} Google's open source libraries — while powerful — can be difficult to integrate within Tuist as they often use non-standard architecture and techniques in how they are built. Here are a few tips that may be necessary to follow to integrate Firebase and Google's other Apple-platform libraries: #### Ensure `-ObjC` is added to `OTHER_LDFLAGS` {#ensure-objc-is-added-to-other_ldflags} Many of Google's libraries are written in Objective-C. Because of this, any consuming target will need to include the `-ObjC` tag in its `OTHER_LDFLAGS` build setting. This can either be set in an `.xcconfig` file or manually specified in the target's settings within your Tuist manifests. An example: ```swift Target.target( ... settings: .settings( base: ["OTHER_LDFLAGS": "$(inherited) -ObjC"] ) ... ) ``` Refer to the [Objective-C Dependencies](#objective-c-dependencies) section above for more details. #### Set the product type for `FBLPromises` to dynamic framework {#set-the-product-type-for-fblpromises-to-dynamic-framework} Certain Google libraries depend on `FBLPromises`, another of Google's libraries. You may encounter a crash that mentions `FBLPromises`, looking something like this: ``` NSInvalidArgumentException. Reason: -[FBLPromise HTTPBody]: unrecognized selector sent to instance 0x600000cb2640. ``` Explicitly setting the product type of `FBLPromises` to `.framework` in your `Package.swift` file should fix the issue: ```swift [Tuist/Package.swift] // swift-tools-version: 5.10 import PackageDescription #if TUIST import ProjectDescription import ProjectDescriptionHelpers let packageSettings = PackageSettings( productTypes: [ "FPLPromises": .framework, ] ) #endif let package = Package( ... ``` ### Transitive static dependencies leaking through `.swiftmodule` {#transitive-static-dependencies-leaking-through-swiftmodule} When a dynamic framework or library depends on static ones through `import StaticSwiftModule`, the symbols are included in the `.swiftmodule` of the dynamic framework or library, potentially [causing the compilation to fail](https://forums.swift.org/t/compiling-a-dynamic-framework-with-a-statically-linked-library-creates-dependencies-in-swiftmodule-file/22708/1). To prevent that, you'll have to import the static dependency using [`@_implementationOnly`](https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_implementationonly): ```swift @_implementationOnly import StaticModule ``` --- URL: "/ja/guides/develop/projects/cost-of-convenience" LLMS_URL: "/ja/guides/develop/projects/cost-of-convenience.md" title: "The cost of convenience" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the cost of convenience in Xcode and how Tuist helps you prevent the issues that come with it." --- # The cost of convenience {#the-cost-of-convenience} Designing a code editor that the spectrum **from small to large-scale projects can use** is a challenging task. Many tools approach the problem by layering their solution and providing extensibility. The bottom-most layer is very low-level and close to the underlying build system, and the top-most layer is a high-level abstraction that's convenient to use but less flexible. By doing so, they make the simple things easy, and everything else possible. However, **[Apple](https://www.apple.com) decided to take a different approach with Xcode**. The reason is unknown, but it's likely that optimizing for the challenges of large-scale projects has never been their goal. They overinvested in convenience for small projects, provided little flexibility, and strongly coupled the tools with the underlying build system. To achieve the convenience, they provide sensible defaults, which you can easily replace, and added a lot of implicit build-time-resolved behaviors that are the culprit of many issues at scale. ## Explicitness and scale {#explicitness-and-scale} When working at scale, **explicitness is key**. It allows the build system to analyze and understand the project structure and dependencies ahead of time, and perform optimizations that would be impossible otherwise. The same explicitness is also key in ensuring that editor features such as [SwiftUI previews](https://developer.apple.com/documentation/swiftui/previews-in-xcode) or [Swift Macros](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros/) work reliably and predictably. Because Xcode and Xcode projects embraced implicitness as a valid design choice to achieve convenience, a principle that the Swift Package Manager has inherited, the difficulties of using Xcode are also present in the Swift Package Manager. > [!INFO] THE ROLE OF TUIST > We could summarize Tuist's role as a tool that prevents implicitly-defined projects and leverages explicitness to provide a better developer experience (e.g. validations, optimizations). Tools like [Bazel](https://bazel.build) take it further by bringing it down to the build system level. This is an issue that's barely discussed in the community, but it's a significant one. While working on Tuist, we've noticed many organizations and developers thinking that the current challenges they face will be addressed by the [Swift Package Manager](https://www.swift.org/documentation/package-manager/), but what they don't realize is that because it's building on the same principles, even though it mitigates the so well-known Git conflicts, they degrade the developer experience in other areas and continue to make the projects non-optimizable. In the following sections, we'll discuss some real examples of how implicitness affects the developer experience and the project's health. The list is not exhaustive, but it should give you a good idea of the challenges that you might face when working with Xcode projects or Swift Packages. ## Convenience getting in your way {#convenience-getting-in-your-way} ### Shared built products directory {#shared-built-products-directory} Xcode uses a directory inside the derived data directory for each product. Inside it, it stores the build artifacts, such as the compiled binaries, the dSYM files, and the logs. Because all the products of a project go into the same directory, which is visible by default from other targets to link against, **you might end up with targets that implicitly depend on each other.** While this might not be a problem when having just a few targets, it might manifest as failing builds that are hard to debug when the project grows. The consequence of this design decision is that many projects acidentally compile with a graph that is not well-defined. > [!TIP] TUIST DETECTION OF IMPLICIT DEPENDENCIES > Tuist provides a command to detect implicit dependencies. You can use the command to validate in CI that all your dependencies are explicit. ### Find implicit dependencies in schemes {#find-implicit-dependencies-in-schemes} Defining and maintaining a dependency graph in Xcode gets harder as the project grows. It's hard because they are codified in the `.pbxproj` files as build phases and build settings, there are no tools to visualize and work with the graph, and the changes in the graph (e.g. adding a new dynamic precompiled framework), might require configuration changes upstream (e.g. adding a new build phase to copy the framework into the bundle). Apple decided at some point that instead of evolving the graph model into something more manageable, it'd make more sense to add an option to resolve implicit dependencies at build time. This is once again a questionable design choice because you might end up with slower build times or unpredictable builds. For example, a build might pass locally due to some state in derive data, which acts as a [singleton](https://en.wikipedia.org/wiki/Singleton_pattern), but then fail to compile on CI because the state is different. > [!TIP] > We recommend disabling this in your project schemes, and use like Tuist that eases the management of the dependency graph. ### SwiftUI Previews and static libraries/frameworks {#swiftui-previews-and-static-librariesframeworks} Some editor features like SwiftUI Previews or Swift Macros require the compilation of the dependency graph from the file that's being edited. This integration between the editor requires that the build system resolves any implicitness and output the right artifacts that are necessary for those features to work. As you can imagine, **the more implicit the graph is, the more challenging the task is for the build system**, and therefore it's not surprising that many of these features don't work reliably. We often hear from developers that they stopped using SwiftUI previews long time ago because they were too unreliable. Instead, they are using either example apps, or avoiding certaing things, like the usage of static libraries or script build phases, because they cause the feature to break. ### Mergeable libraries {#mergeable-libraries} Dynamic frameworks, while more flexible and easier to work with, have a negative impact in the launch time of apps. On the other side, static libraries are faster to launch, but impact the compilation time and are a bit harder to work with, specially in complex graph scenarios. _Wouldn't it be great if you could change between one or the other depending on the configuration?_ That's what Apple must have thought when they decided to work on mergeable libraries. But once again, they moved more build-time inference to the build-time. If reasoning about a dependency graph, imagine having to do so when the static or dynamic nature of the target will be resolved at build-time based on some build settings in some targets. Good luck making that work reliably while ensuring features like SwiftUI previews don't break. **Many users come to Tuist wanting to use mergeable libraries and our answer is always the same. You don't need to.** You can control the static or dynamic nature of your targets at generation-time leading to a project whose graph is known ahead of compilation. No variables need to be resolved at build-time. ```bash # The value of TUIST_DYNAMIC can be read from the project {#the-value-of-tuist_dynamic-can-be-read-from-the-project} # to set the product as static or dynamic based on the value. {#to-set-the-product-as-static-or-dynamic-based-on-the-value} TUIST_DYNAMIC=1 tuist generate ``` ## Explicit, explicit, and explicit {#explicit-explicit-and-explicit} If there's an important non-written principle that we recommend every developer or organization that wants their development with Xcode to scale, is that they should embrace explicitness. And if explicitness is hard to manage with raw Xcode projects, they should consider something else, either [Tuist](https://tuist.io) or [Bazel](https://bazel.build). **Only then reliability, predicability, and optimizations will be possible.** ## Future {#future} Whether Apple will do something to prevent all the above issues is unknown. Their continuous decisions embedded into Xcode and the Swift Package Manager don't suggest that they will. Once you allow implicit configuration as a valid state, **it's hard to move from there without introducing breaking changes.** Going back to first principles and rethinking the design of the tools might lead to breaking many Xcode projects that accidentally compiled for years. Imagine the community uproar if that happened. Apple finds itself in a bit of a chicken-and-egg problem. Convenience is what helps developers get started quickly and build more apps for their ecosystem. But their decisions to make the experience convenience at that scale, is making it hard for them to ensure some of the Xcode features work reliably. Because the future is unknown, we try to **be as close as possible to the industry standards and Xcode projects**. We prevent the above issues, and leverage the knowledge that we have to provide a better developer experience. Ideally we wouldn't have to resort to project generation for that, but the lack of extensibility of Xcode and the Swift Package Manager make it the only viable option. And it's also a safe option because they'll have to break the Xcode projects to break Tuist projects. Ideally, **the build system was more extensible**, but wouldn't it be a bad idea to have plugins/extensions that contract with a world of implicitness? It doesn't seem like a good idea. So it seems like we'll need external tools like Tuist or [Bazel](https://bazel.build) to provide a better developer experience. Or maybe Apple will surprise us all and make Xcode more extensible and explicit... Until that happens, you have to choose whether you want to embrace the convencience of Xcode and take on the debt that comes with it, or trust us on this journey to provide a better developer experience. We won't disappoint you. --- URL: "/ja/guides/develop/projects/code-sharing" LLMS_URL: "/ja/guides/develop/projects/code-sharing.md" title: "Code sharing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to share code across manifest files to reduce duplications and ensure consistency" --- # Code sharing {#code-sharing} One of the inconveniences of Xcode when we use it with large projects is that it doesn't allow reusing elements of the projects other than the build settings through `.xcconfig` files. Being able to reuse project definitions is useful for the following reasons: - It eases the **maintenance** because changes can be applied in one place and all the projects get the changes automatically. - It makes it possible to define **conventions** that new projects can conform to. - Projects are more **consistent** and therefore the likelihood of broken builds due inconsistencies is significantly less. - Adding a new projects becomes an easy task because we can reuse the existing logic. Reusing code across manifest files is possible in Tuist thanks to the concept of **project description helpers**. > [!TIP] A TUIST UNIQUE ASSET > Many organizations like Tuist because they see in project description helpers a platform for platform teams to codify their own conventions and come up with their own language for describing their projects. For example, YAML-based project generators have to come up with their own YAML-based propietary templating solution, or force organizations onto building their tools upon. ## Project description helpers {#project-description-helpers} Project description helpers are Swift files that get compiled into a module, `ProjectDescriptionHelpers`, that manifest files can import. The module is compiled by gathering all the files in the `Tuist/ProjectDescriptionHelpers` directory. You can import them into your manifest file by adding an import statement at the top of the file: ```swift // Project.swift import ProjectDescription import ProjectDescriptionHelpers ``` `ProjectDescriptionHelpers` are available in the following manifests: - `Project.swift` - `Package.swift` (only behind the `#TUIST` compiler flag) - `Workspace.swift` ## Example {#example} The snippets below contain an example of how we extend the `Project` model to add static constructors and how we use them from a `Project.swift` file: ::: code-group ```swift [Tuist/Project+Templates.swift] import ProjectDescription extension Project { public static func featureFramework(name: String, dependencies: [TargetDependency] = []) -> Project { return Project( name: name, targets: [ .target( name: name, destinations: .iOS, product: .framework, bundleId: "io.tuist.\(name)", infoPlist: "\(name).plist", sources: ["Sources/\(name)/**"], resources: ["Resources/\(name)/**",], dependencies: dependencies ), .target( name: "\(name)Tests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.\(name)Tests", infoPlist: "\(name)Tests.plist", sources: ["Sources/\(name)Tests/**"], resources: ["Resources/\(name)Tests/**",], dependencies: [.target(name: name)] ) ] ) } } ``` ```swift {2} [Project.swift] import ProjectDescription import ProjectDescriptionHelpers let project = Project.featureFramework(name: "MyFeature") ``` ::: > [!TIP] A TOOL TO ESTABLISH CONVENTIONS > Note how through the function we are defining conventions about the name of the targets, the bundle identifier, and the folders structure. --- URL: "/ja/guides/develop/projects/best-practices" LLMS_URL: "/ja/guides/develop/projects/best-practices.md" title: "ベストプラクティス" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Tuist プロジェクトと Xcode プロジェクトのベストプラクティスについて学ぶ" --- # ベストプラクティス {#best-practices} Over the years working with different teams and projects, we've identified a set of best practices that we recommend following when working with Tuist and Xcode projects. These practices are not mandatory, but they can help you structure your projects in a way that makes them easier to maintain and scale. ## Xcode {#xcode} ### 避けるべきパターン {#discouraged-patterns} #### リモート環境をモデル化するための設定 {#configurations-to-model-remote-environments} 多くの組織は、異なるリモート環境(例: Debug-Production や Release-Canary)をモデル化するためにビルド設定を使用しますが、このアプローチにはいくつかの欠点があります: - **Inconsistencies:** If there are configuration inconsistencies throughout the graph, the build system might end up using the wrong configuration for some targets. - **Complexity:** Projects can end up with a long list of local configurations and remote environments that are hard to reason about and maintain. Build configurations were designed to embody different build settings, and projects rarely need more than just `Debug` and `Release`. The need to model different environments can be achieved by using schemes: - **In Debug builds:** You can include all the configurations that should be accessible in development in the app (e.g. endpoints), and switch them at runtime. The switch can happen either using scheme launch environment variables, or with a UI within the app. - **In Release builds:** In case of release, you can only include the configuration that the release build is bound to, and not include the runtime logic for switching configurations by using compiler directives. --- URL: "/ja/guides/develop/projects/adoption/swift-package" LLMS_URL: "/ja/guides/develop/projects/adoption/swift-package.md" title: "TuistをSwiftパッケージと使用する" titleTemplate: ":title · Adoption · Projects · Develop · Guides · Tuist" description: "TuistをSwiftパッケージと使用する方法を学びます。" --- # TuistをSwiftパッケージと使用する {#using-tuist-with-a-swift-package-badge-typewarning-textbeta-} Tuist は、`Package.swift` をプロジェクトの DSL として使用することをサポートしており、パッケージのターゲットをネイティブの Xcode プロジェクトおよびターゲットに変換します。 > [!WARNING] > この機能の目的は、開発者がSwiftパッケージにTuistを導入する影響を評価するための簡単な方法を提供することです。 そのため、Swiftパッケージマネージャーの全機能をサポートする予定はなく、Project Description HelperのようなTuist特有の機能をパッケージの世界に持ち込むことも計画していません。 > [!NOTE] ROOT DIRECTORY > Tuist コマンドは、`Tuist` ディレクトリまたは `.git` ディレクトリによってルートが識別される特定の ディレクトリ構造を 期待する。 ## TuistをSwiftパッケージと使用する {#using-tuist-with-a-swift-package} Swiftパッケージを含む[TootSDK Package](https://github.com/TootSDK/TootSDK)リポジトリでTuistを使用します。 まず、リポジトリをクローンする必要があります。 ```bash git clone https://github.com/TootSDK/TootSDK cd TootSDK ``` リポジトリのディレクトリに一度入ったら、Swift Package Manager の依存関係をインストールする必要があります。 ```bash tuist install ``` `tuist install` は、Swift Package Managerを使用してパッケージの依存関係を解決して pull します。 依存関係の解決が完了したら、プロジェクトを生成することができます。 ```bash tuist generate ``` ほら! ネイティブの Xcode プロジェクトを開いて作業を開始できます。 --- URL: "/ja/guides/develop/projects/adoption/new-project" LLMS_URL: "/ja/guides/develop/projects/adoption/new-project.md" title: "新規プロジェクトの作成" titleTemplate: ":title · Adoption · Projects · Develop · Guides · Tuist" description: "Tuist で新規プロジェクトを作成する方法を学びます。" --- # 新規プロジェクトの作成 {#create-a-new-project} Tuist を使って新しいプロジェクトを開始する最も簡単な方法は、`tuist init` コマンドを使用することです。 This command launches an interactive CLI that guides you through setting up your project. When prompted, make sure to select the option to create a "generated project". コマンドは現在のディレクトリ内のプロジェクトを初期化します。 プロジェクトを編集するには、`tuist edit` を実行します。そうすると、Xcode がプロジェクトを開き、<0>編集できるようになります。 生成されるファイルの1つは `Project.swift` で、プロジェクトの定義が含まれています。 Swift Package Manager に馴染みがある方は、Xcode プロジェクト向けの `Package.swift` のようなものだと考えてください。 生成されるファイルの1つは `Project.swift` で、プロジェクトの定義が含まれています。 Swift Package Manager に馴染みがある方は、Xcode プロジェクト向けの `Package.swift` のようなものだと考えてください。 生成されるファイルの1つは `Project.swift` で、プロジェクトの定義が含まれています。 Swift Package Manager に馴染みがある方は、Xcode プロジェクト向けの `Package.swift` のようなものだと考えてください。 ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "MyApp", targets: [ .target( name: "MyApp", destinations: .iOS, product: .app, bundleId: "io.tuist.MyApp", infoPlist: .extendingDefault( with: [ "UILaunchScreen": [ "UIColorName": "", "UIImageName": "", ], ] ), sources: ["MyApp/Sources/**"], resources: ["MyApp/Resources/**"], dependencies: [] ), .target( name: "MyAppTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyAppTests", infoPlist: .default, sources: ["MyApp/Tests/**"], resources: [], dependencies: [.target(name: "MyApp")] ), ] ) ``` ::: > [!NOTE] > We intentionally keep the list of available templates short to minimize maintenance overhead. If you want to create a project that doesn't represent an application, for example a framework, you can use `tuist init` as a starting point and then modify the generated project to suit your needs. ## Manually creating a project {#manually-creating-a-project} Alternatively, you can create the project manually. We recommend doing this only if you're already familiar with Tuist and its concepts. The first thing that you'll need to do is to create additional directories for the project structure: ```bash mkdir MyFramework cd MyFramework ``` 次に、Tuist の設定を行い、プロジェクトのルートディレクトリを判定するために Tuist が使用する `Tuist.swift` ファイルと、プロジェクトの内容を宣言する `Project.swift` ファイルを作成します。 ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "MyFramework", targets: [ .target( name: "MyFramework", destinations: .macOS, product: .framework, bundleId: "io.tuist.MyFramework", sources: ["MyFramework/Sources/**"], dependencies: [] ) ] ) ``` ```swift [Tuist.swift] import ProjectDescription let tuist = Tuist() ``` ::: > [!IMPORTANT] > Tuist uses the `Tuist/` directory to determine the root of your project, and from there it looks for other manifest files globbing the directories. We recommend creating those files with your editor of choice, and from that point on, you can use `tuist edit` to edit the project with Xcode. --- URL: "/ja/guides/develop/projects/adoption/migrate/xcodegen-project" LLMS_URL: "/ja/guides/develop/projects/adoption/migrate/xcodegen-project.md" title: "Migrate an XcodeGen project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate your projects from XcodeGen to Tuist." --- # Migrate an XcodeGen project {#migrate-an-xcodegen-project} [XcodeGen](https://github.com/yonaskolb/XcodeGen) is a project-generation tool that uses YAML as [a configuration format](https://github.com/yonaskolb/XcodeGen/blob/master/Docs/ProjectSpec.md) to define Xcode projects. Many organizations **adopted it trying to escape from the frequent Git conflicts that arise when working with Xcode projects.** However, frequent Git conflicts is just one of the many problems that organizations experience. Xcode exposes developers with a lot of intricacies and implicit configurations that make it hard to maintain and optimize projects at scale. XcodeGen falls short there by design because it's a tool that generates Xcode projects, not a project manager. If you need a tool that helps you beyond generating Xcode projects, you might want to consider Tuist. > [!TIP] SWIFT OVER YAML > Many organizations prefer Tuist as a project generation tool too because it uses Swift as a configuration format. Swift is a programming language that developers are familiar with, and that provides them with the convenience of using Xcode's autocompletion, type-checking, and validation features. What follows are some considerations and guidelines to help you migrate your projects from XcodeGen to Tuist. ## Project generation {#project-generation} Both Tuist and XcodeGen provide a `generate` command that turns your project declaration into Xcode projects and workspaces. ::: code-group ```bash [XcodeGen] xcodegen generate ``` ```bash [Tuist] tuist generate ``` ::: The difference lays in the editing experience. With Tuist, you can run the `tuist edit` command, which generates an Xcode project on the fly that you can open and start working on. This is particularly useful when you want to make quick changes to your project. ## `project.yaml` {#projectyaml} XcodeGen's `project.yaml` description file becomes `Project.swift`. Moreover, you can have `Workspace.swift` as a way to customize how projects are grouped in workspaces. You can also have a project `Project.swift` with targets that reference targets from other projects. In those cases, Tuist will generate an Xcode Workspace including all the projects. ::: code-group ```bash [XcodeGen directory structure] / project.yaml ``` ```bash [Tuist directory structure] / Tuist.swift Project.swift Workspace.swift ``` ::: > [!TIP] XCODE'S LANGUAGE > Both XcodeGen and Tuist embrace Xcode's language and concepts. However, Tuist's Swift-based configuration provides you with the convenience of using Xcode's autocompletion, type-checking, and validation features. ## Spec templates {#spec-templates} One of the disadvantages of YAML as a language for project configuration is that it doesn't support reusability across YAML files out of the box. This is a common need when describing projects, which XcodeGen had to solve with their own propietary solution named _"templates"_. With Tuist's re-usability is built into the language itself, Swift, and through a Swift module named project description helpers, which allow reusing code across all your manifest files. ::: code-group ```swift [Tuist/ProjectDescriptionHelpers/Target+Features.swift] import ProjectDescription extension Target { /** This function is a factory of targets that together represent a feature. */ static func featureTargets(name: String) -> [Target] { // ... } } ``` ```swift [Project.swift] import ProjectDescription import ProjectDescriptionHelpers // [!code highlight] let project = Project(name: "MyProject", targets: Target.featureTargets(name: "MyFeature")) // [!code highlight] ``` --- URL: "/ja/guides/develop/projects/adoption/migrate/xcode-project" LLMS_URL: "/ja/guides/develop/projects/adoption/migrate/xcode-project.md" title: "Migrate an Xcode project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Xcode プロジェクトを Tuist プロジェクトに移行する方法を学びます" --- # Xcode プロジェクトの移行 {#migrate-an-xcode-project} Xcode プロジェクトの移行 {#migrate-an-xcode-project} このプロセスがどれほど退屈かは、プロジェクトの複雑さによって異なります。 As you probably know, Xcode projects can become messy and complex over time: groups that don't match the directory structure, files that are shared across targets, or file references that point to nonexisting files (to mention some). All that accumulated complexity makes it hard for us to provide a command that reliably migrates project. Moreover, manual migration is an excellent exercise to clean up and simplify your projects. Not only the developers in your project will be thankful for that, but Xcode, who will be faster processing and indexing them. Once you have fully adopted Tuist, it will make sure that projects are consistently defined and that they remain simple. In the aim of easing that work, we are giving you some guidelines based on the feedback that we have received from the users. ## Create project scaffold {#create-project-scaffold} First of all, create a scaffold for your project with the following Tuist files: ::: code-group ```js [Tuist.swift] import ProjectDescription let tuist = Tuist() ``` ```js [Project.swift] import ProjectDescription let project = Project( name: "MyApp-Tuist", targets: [ /** Targets will go here **/ ] ) ``` ```js [Tuist/Package.swift] // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies ] ) ``` ::: `Project.swift` is the manifest file where you'll define your project, and `Package.swift` is the manifest file where you'll define your dependencies. The `Tuist.swift` file is where you can define project-scoped Tuist settings for your project. > [!TIP] PROJECT NAME WITH -TUIST SUFFIX > To prevent conflicts with the existing Xcode project, we recommend adding the `-Tuist` suffix to the project name. You can drop it once you've fully migrated your project to Tuist. ## Build and test the Tuist project in CI {#build-and-test-the-tuist-project-in-ci} To ensure the migration of each change is valid, we recommend extending your continuous integration to build and test the project generated by Tuist from your manifest file: ```bash tuist install tuist generate tuist build -- ...{xcodebuild flags} # or tuist test ``` ## Extract the project build settings into `.xcconfig` files {#extract-the-project-build-settings-into-xcconfig-files} Extract the build settings from the project into an `.xcconfig` file to make the project leaner and easier to migrate. You can use the following command to extract the build settings from the project into an `.xcconfig` file: ```bash mkdir -p xcconfigs/ tuist migration settings-to-xcconfig -p MyApp.xcodeproj -x xcconfigs/MyApp-Project.xcconfig ``` Then update your `Project.swift` file to point to the `.xcconfig` file you've just created: ```swift import ProjectDescription let project = Project( name: "MyApp", settings: .settings(configurations: [ .debug(name: "Debug", xcconfig: "./xcconfigs/MyApp-Project.xcconfig"), // [!code ++] .release(name: "Release", xcconfig: "./xcconfigs/MyApp-Project.xcconfig"), // [!code ++] ]), targets: [ /** Targets will go here **/ ] ) ``` Then extend your continuous integration pipeline to run the following command to ensure that changes to build settings are made directly to the `.xcconfig` files: ```bash tuist migration check-empty-settings -p Project.xcodeproj ``` ## Extract package dependencies {#extract-package-dependencies} Extract all your project's dependencies into the `Tuist/Package.swift` file: ```swift // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies .package(url: "https://github.com/onevcat/Kingfisher", .upToNextMajor(from: "7.12.0")) // [!code ++] ] ) ``` > [!TIP] PRODUCT TYPES > You can override the product type for a specific package by adding it to the `productTypes` dictionary in the `PackageSettings` struct. By default, Tuist assumes that all packages are static frameworks. ## Determine the migration order {#determine-the-migration-order} We recommend migrating the targets from the one that is the most dependent upon to the least. You can use the following command to list the targets of a project, sorted by the number of dependencies: ```bash tuist migration list-targets -p Project.xcodeproj ``` Start migrating the targets from the top of the list, as they are the ones that are the most depended upon. ## Migrate targets {#migrate-targets} Migrate the targets one by one. We recommend doing a pull request for each target to ensure that the changes are reviewed and tested before merging them. ### Extract the target build settings into `.xcconfig` files {#extract-the-target-build-settings-into-xcconfig-files} Like you did with the project build settings, extract the target build settings into an `.xcconfig` file to make the target leaner and easier to migrate. You can use the following command to extract the build settings from the target into an `.xcconfig` file: ```bash tuist migration settings-to-xcconfig -p MyApp.xcodeproj -t TargetX -x xcconfigs/TargetX.xcconfig ``` ### Define the target in the `Project.swift` file {#define-the-target-in-the-projectswift-file} Define the target in `Project.targets`: ```swift import ProjectDescription let project = Project( name: "MyApp", settings: .settings(configurations: [ .debug(name: "Debug", xcconfig: "./xcconfigs/Project.xcconfig"), .release(name: "Release", xcconfig: "./xcconfigs/Project.xcconfig"), ]), targets: [ .target( // [!code ++] name: "TargetX", // [!code ++] destinations: .iOS, // [!code ++] product: .framework, // [!code ++] // or .staticFramework, .staticLibrary... bundleId: "io.tuist.targetX", // [!code ++] sources: ["Sources/TargetX/**"], // [!code ++] dependencies: [ // [!code ++] /** Dependencies go here **/ // [!code ++] /** .external(name: "Kingfisher") **/ // [!code ++] /** .target(name: "OtherProjectTarget") **/ // [!code ++] ], // [!code ++] settings: .settings(configurations: [ // [!code ++] .debug(name: "Debug", xcconfig: "./xcconfigs/TargetX.xcconfig"), // [!code ++] .debug(name: "Release", xcconfig: "./xcconfigs/TargetX.xcconfig"), // [!code ++] ]) // [!code ++] ), // [!code ++] ] ) ``` > [!NOTE] TEST TARGETS > If the target has an associated test target, you should define it in the `Project.swift` file as well repeating the same steps. ### Validate the target migration {#validate-the-target-migration} Run `tuist build` and `tuist test` to ensure the project builds and tests pass. Additionally, you can use [xcdiff](https://github.com/bloomberg/xcdiff) to compare the generated Xcode project with the existing one to ensure that the changes are correct. ### Repeat {#repeat} Repeat until all the targets are fully migrated. Once you are done, we recommend updating your CI and CD pipelines to build and test the project using `tuist build` and `tuist test` commands to benefit from the speed and reliability that Tuist provides. ## Troubleshooting {#troubleshooting} ### Compilation errors due to missing files. {#compilation-errors-due-to-missing-files} If the files associated to your Xcode project targets were not all contained in a file-system directory representing the target, you might end up with a project that doesn't compile. Make sure the list of files after generating the project with Tuist matches the list of files in the Xcode project, and take the opportunity to align the file structure with the target structure. --- URL: "/ja/guides/develop/projects/adoption/migrate/swift-package" LLMS_URL: "/ja/guides/develop/projects/adoption/migrate/swift-package.md" title: "Migrate a Swift Package" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate from Swift Package Manager as a solution for managing your projects to Tuist projects." --- # Migrate a Swift Package {#migrate-a-swift-package} Swift Package Manager emerged as a dependency manager for Swift code that uninentionally found itself solving the problem of managing projects and supporting other programming languages like Objective-C. Because the tool was designed with a different purpose in mind, it can be challenging to use it to manage projects at scale because it lacks flexibility, performance, and power that Tuist provides. This is well captured in the [Scaling iOS at Bumble](https://medium.com/bumble-tech/scaling-ios-at-bumble-239e0fa009f2) article, which includes the following table comparing the performance of Swift Package Manager and native Xcode projects: A table that compares the regression in performance when using SPM over native Xcode projects We often come across developers and organizations that challenge the need for Tuist considering that Swift Package Manager can take a similar project management role. Some venture into a migration to later on realize that their developer experience has degraded signicantly. For instance, the rename of a file might take up to 15 seconds to re-index. 15 seconds! **Whether Apple will make Swift Package Manager a built-for-scale project manager is uncertain.** However, we are not seeing any signs that it's happening. In fact, we are seeing quite the opposite. They are making Xcode-inspired decisions, like achieving convenience through implicit configurations, which as you might know, is the source of complications at scale. We believe it'd take Apple to go to first principles and revisit some decisions that made sense as a dependency manager but not as a project manager, for example the usage of a compiled language as an interface to define projects. > [!TIP] SPM AS JUST A DEPENDENCY MANAGER > Tuist treats Swift Package Manager as a dependency manager, and it's a great one. We use it to resolve dependencies and to build them. We don't use it to define projects because it's not designed for that. ## Migrating from Swift Package Manager to Tuist {#migrating-from-swift-package-manager-to-tuist} The similarities between Swift Package Manager and Tuist make the migration process straightforward. The main difference is that you'll be defining your projects using Tuist's DSL instead of `Package.swift`. First, create a `Project.swift` file next to your `Package.swift` file. The `Project.swift` file will contain the definition of your project. Here's an example of a `Project.swift` file that defines a project with a single target: ```swift import ProjectDescription let project = Project( name: "App", targets: [ .target( name: "App", destinations: .iOS, product: .app, bundleId: "io.tuist.App", sources: ["Sources/**/*.swift"]* ), ] ) ``` Some things to note: - **ProjectDescription**: Instead of using `PackageDescription`, you'll be using `ProjectDescription`. - **Project:** Instead of exporting a `package` instance, you'll be exporting a `project` instance. - **Xcode language:** The primitives that you use to define your project mimic Xcode's language, so you'll find schemes, targets, and build phases among others. Then create a `Tuist.swift` file with the following content: ```swift import ProjectDescription let tuist = Tuist() ``` The `Tuist.swift` contains the configuration for your project and its path serves as a reference to determine the root of your project. You can check out the directory structure document to learn more about the structure of Tuist projects. ## Editing the project {#editing-the-project} You can use `tuist edit` to edit the project in Xcode. The command will generate an Xcode project that you can open and start working on. ```bash tuist edit ``` Depending on the size of the project, you might consider using it in one shot or incrementally. We recommend starting with a small project to get familiar with the DSL and the workflow. Our advise is always to start from the most depended upon target and work all the way up to the top-level target. --- URL: "/ja/guides/develop/projects/adoption/migrate/bazel-project" LLMS_URL: "/ja/guides/develop/projects/adoption/migrate/bazel-project.md" title: "Migrate a Bazel project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate your projects from Bazel to Tuist." --- # Migrate a Bazel project {#migrate-a-bazel-project} [Bazel](https://bazel.build) is a build system that Google open-sourced in 2015. It's a powerful tool that allows you to build and test software of any size, quickly and reliably. Some large organizations like [Spotify](https://engineering.atspotify.com/2023/10/switching-build-systems-seamlessly/), [Tinder](https://medium.com/tinder/bazel-hermetic-toolchain-and-tooling-migration-c244dc0d3ae), or [Lyft](https://semaphoreci.com/blog/keith-smiley-bazel) use it, however, it requires an upfront (i.e., learning the technology) and ongoing investment (i.e., keeping up with Xcode updates) to introduce and maintain. While this works for some organizations that treat it as a cross-cutting concern, it might not be the best fit for others that want to focus on their product development. For instance, we've seen organizations whose iOS platform team introduced Bazel and had to drop it after the engineers that led the effort left the company. Apple's stance on the strong coupling between Xcode and the build system is another factor that makes it hard to maintain Bazel projects over time. > [!TIP] TUIST UNIQUENESS LIES IN ITS FINESSE > Instead of fighting Xcode and Xcode projects, Tuist embraces it. It's the same concepts (e.g., targets, schemes, build settings), a familiar language (i.e., Swift), and a simple and enjoyable experience that makes maintaining and scaling projects everyone's job and not just the iOS platform team's. ## Rules {#rules} Bazel uses rules to define how to build and test software. The rules are written in [Starlark](https://github.com/bazelbuild/starlark), a Python-like language. Tuist uses Swift as a configuration language, which provides developers with the convenience of using Xcode's autocompletion, type-checking, and validation features. For example, the following rule describes how to build a Swift library in Bazel: ::: code-group ```txt [BUILD (Bazel)] swift_library( name = "MyLibrary.library", srcs = glob(["**/*.swift"]), module_name = "MyLibrary" ) ``` ```swift [Project.swift (Tuist)] let project = Project( // ... targets: [ .target(name: "MyLibrary", product: .staticLibrary, sources: ["**/*.swift"]) ] ) ``` ::: Here's another example but compating how to define unit tests in Bazel and Tuist: :::code-group ```txt [BUILD (Bazel)] ios_unit_test( name = "MyLibraryTests", bundle_id = "io.tuist.MyLibraryTests", minimum_os_version = "16.0", test_host = "//MyApp:MyLibrary", deps = [":MyLibraryTests.library"], ) ``` ```swift [Project.swift (Tuist)] let project = Project( // ... targets: [ .target( name: "MyLibraryTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyLibraryTests", sources: "Tests/MyLibraryTests/**", dependencies: [ .target(name: "MyLibrary"), ] ) ] ) ``` ::: ## Swift Package Manager dependencies {#swift-package-manager-dependencies} In Bazel, you can use the [`rules_swift_package_manager`](https://github.com/cgrindel/rules_swift_package_manager) [Gazelle](https://github.com/bazelbuild/bazel-gazelle/blob/master/extend.md) plugin to use Swift Packages as dependencies. The plugin requires a `Package.swift` as a source of truth for the dependencies. Tuist's interface is similar to Bazel's in that sense. You can use the `tuist install` command to resolve and pull the dependencies of the package. After the resolution completes, you can then generate the project with the `tuist generate` command. ```bash tuist install # Fetch dependencies defined in Tuist/Package.swift tuist generate # Generate an Xcode project ``` ## Project generation {#project-generation} The community provides a set of rules, [rules_xcodeproj](https://github.com/MobileNativeFoundation/rules_xcodeproj), to generate Xcode projects off Bazel-declared projects. Unlike Bazel, where you need to add some configuration to your `BUILD` file, Tuist doesn't require any configuration at all. You can run `tuist generate` in the root directory of your project, and Tuist will generate an Xcode project for you. --- URL: "/ja/guides/develop/projects" LLMS_URL: "/ja/guides/develop/projects.md" title: "Projects" titleTemplate: ":title · Develop · Guides · Tuist" description: "Learn about Tuist's DSL for defining Xcode projects." --- # Projects {#projects} Generated is a viable alternative that helps to overcome these challenges while keeping complexity and costs at an acceptable level. It considers Xcode projects as a fundamental element, ensuring resilience against future Xcode updates, and leverages Xcode project generation to provide teams with a modularization-focused declarative API. Tuist uses the project declaration to simplify the complexities of modularization\*\*, optimize workflows like build or test across various environments, and facilitate and democratize the evolution of Xcode projects. ## How does it work? {#how-does-it-work} To get started with generated projects, all you need is to define your project using **Tuist's Domain Specific Language (DSL)**. This entails using manifest files such as `Workspace.swift` or `Project.swift`. If you've worked with the Swift Package Manager before, the approach is very similar. Once you've defined your project, Tuist offers various workflows to manage and interact with it: - **Generate:** This is a foundational workflow. Use it to create an Xcode project that's compatible with Xcode. - **Build:** This workflow not only generates the Xcode project but also employs `xcodebuild` to compile it. - **Test:** Operating much like the build workflow, this not only generates the Xcode project but utilizes `xcodebuild` to test it. ## Challenges with Xcode projects {#challenges-with-xcode-projects} As Xcode projects grow, **organizations may face a decline in productivity** due to several factors, including unreliable incremental builds, frequent clearing of Xcode's global cache by developers encountering issues, and fragile project configurations. To maintain rapid feature development, organizations typically explore various strategies. Some organizations choose to bypass the compiler by abstracting the platform using JavaScript-based dynamic runtimes, such as [React Native](https://reactnative.dev/). While this approach may be effective, it [complicates access to the platform's native features](https://shopify.engineering/building-app-clip-react-native). Other organizations opt for **modularizing the codebase**, which helps establish clear boundaries, making the codebase easier to work with and improving the reliability of build times. However, the Xcode project format is not designed for modularity and results in implicit configurations that few understand and frequent conflicts. This leads to a bad bus factor, and although incremental builds may improve, developers might still frequently clear Xcode's build cache (i.e., derived data) when builds fail. To address this, some organizations choose to **abandon Xcode's build system** and adopt alternatives like [Buck](https://buck.build/) or [Bazel](https://bazel.build/). However, this comes with a [high complexity and maintenance burden](https://bazel.build/migrate/xcode). ## Alternatives {#alternatives} ### Swift Package Manager {#swift-package-manager} While the Swift Package Manager (SPM) primarily focuses on dependencies, Tuist offers a different approach. With Tuist, you don't just define packages for SPM integration; you shape your projects using familiar concepts like projects, workspaces, targets, and schemes. ### XcodeGen {#xcodegen} [XcodeGen](https://github.com/yonaskolb/XcodeGen) is a dedicated project generator designed to reduce conflicts in collaborative Xcode projects and simplify some complexities of Xcode's internal workings. However, projects are defined using serializable formats like [YAML](https://yaml.org/). Unlike Swift, this doesn't allow developers to build upon abstractions or checks without incorporating additional tools. While XcodeGen does offer a way to map dependencies to an internal representation for validation and optimization, it still exposes developers to the nuances of Xcode. This might make XcodeGen a suitable foundation for [building tools](https://github.com/MobileNativeFoundation/rules_xcodeproj), as seen in the Bazel community, but it's not optimal for inclusive project evolution that aims to maintain a healthy and productive environment. ### Bazel {#bazel} [Bazel](https://bazel.build) is an advanced build system renowned for its remote caching features, gaining popularity within the Swift community primarily for this capability. However, given the limited extensibility of Xcode and its build system, substituting it with Bazel's system demands significant effort and maintenance. Only a few companies with abundant resources can bear this overhead, as evident from the select list of firms investing heavily to integrate Bazel with Xcode. Interestingly, the community created a [tool](https://github.com/MobileNativeFoundation/rules_xcodeproj) that employs Bazel's XcodeGen to generate an Xcode project. This results in a convoluted chain of conversions: from Bazel files to XcodeGen YAML and finally to Xcode Projects. Such layered indirection often complicates troubleshooting, making issues more challenging to diagnose and resolve. --- URL: "/ja/guides/develop/insights" LLMS_URL: "/ja/guides/develop/insights.md" title: "Insights" titleTemplate: ":title · Develop · Guides · Tuist" description: "Get insights into your projects to maintain a product developer environment." --- # Insights {#insights} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project Working on large projects shouldn't feel like a chore. In fact, it should be as enjoyable as working on a project you started just two weeks ago. One of the reasons it is not is because as the project grows, the developer experience suffers. The build times increase and tests become slow and flaky. It's often easy to overlook these issues until it gets to a point where they become unbearable – however, at that point, it's difficult to address them. Tuist Insights provides you with the tools to monitor the health of your project and maintain a productive developer environment as your project scales. In other words, Tuist Insights helps you to anwer questions such as: - Has the build time significantly increased in the last week? - Have my tests become slower? Which ones? > [!NOTE] > Tuist Insights are in early development. ## Builds {#builds} While you probably have some metrics for the performance of CI workflows, you might not have the same visibility into the local development environment. However, local build times are one of the most important factors that contribute to the developer experience. To start tracking local build times, you can leverage the `tuist inspect build` command by adding it to your scheme's post-action: ![Post-action for inspecting builds](/images/guides/develop/insights/inspect-build-scheme-post-action.png) In case you're using [Mise](https://mise.jdx.dev/), your script will need to activate `tuist` in the post-action environment: ```sh # -C ensures that Mise loads the configuration from the Mise configuration # file in the project's root directory. eval "$($HOME/.local/bin/mise activate -C $SRCROOT bash --shims)" tuist inspect build ``` Your local builds are now tracked as long as you are logged in to your Tuist account. You can now access your build times in the Tuist dashboard and see how they evolve over time: > [!TIP] > To quickly access the dashboard, run `tuist project show --web` from the CLI. ![Dashboard with build insights](/images/guides/develop/insights/builds-dashboard.png) ## Projects {#projects} > [!NOTE] > Auto-generated schemes automatically include the `tuist inspect build` post-action. > > If you are not interested in tracking build insights in your auto-generated schemes, disable them using the buildInsightsDisabled generation option. If you are using generated projects, you can set up a custom build post-action using a custom scheme, such as: ```swift let project = Project( name: "MyProject", targets: [ // Your targets ], schemes: [ .scheme( name: "MyApp", shared: true, buildAction: .buildAction(targets: ["MyApp"]), testAction: .testAction(targets: ["MyAppTests"]), runAction: .runAction(configuration: "Debug"), postActions: [ .postAction( name: "Inspect Build", scriptText: """ eval \"$($HOME/.local/bin/mise activate -C $SRCROOT bash --shims)\" tuist inspect build """ ) ] ) ] ) ``` If you're not using Mise, your script can be simplified to just: ```swift .postAction( name: "Inspect Build", script: "tuist inspect build", execution: .always ) ``` ## Continuous integration {#continuous-integration} To track build times also on the CI, you will need to ensure that your CI is authenticated. Additionally, you will either need to: - Use the `tuist xcodebuild` command when invoking `xcodebuild` actions. - Add `-resultBundlePath` to your `xcodebuild` invocation. When `xcodebuild` builds your project without `-resultBundlePath`, the `.xcactivitylog` file is not generated. But the `tuist inspect build` post-action requires that file to be generated to analyze your build. --- URL: "/ja/guides/develop/cache" LLMS_URL: "/ja/guides/develop/cache.md" title: "Cache" titleTemplate: ":title · Develop · Guides · Tuist" description: "Optimize your build times by caching compiled binaries and sharing them across different environments." --- # Cache {#cache} > [!IMPORTANT] REQUIREMENTS > > - A generated project > - A Tuist account and project Xcode's build system provides [incremental builds](https://en.wikipedia.org/wiki/Incremental_build_model), enhancing efficiency under normal circumstances. However, this feature falls short in [Continuous Integration (CI) environments](https://en.wikipedia.org/wiki/Continuous_integration), where data essential for incremental builds is not shared across different builds. Additionally, **developers often reset this data locally to troubleshoot complex compilation problems**, leading to more frequent clean builds. This results in teams spending excessive time waiting for local builds to finish or for Continuous Integration pipelines to provide feedback on pull requests. Furthermore, the frequent context switching in such an environment compounds this unproductiveness. Tuist addresses these challenges effectively with its caching feature. This tool optimizes the build process by caching compiled binaries, significantly reducing build times both in local development and CI environments. This approach not only accelerates feedback loops but also minimizes the need for context switching, ultimately boosting productivity. ## Warming {#warming} Tuist efficiently utilizes hashes for each target in the dependency graph to detect changes. Utilizing this data, it builds and assigns unique identifiers to binaries derived from these targets. At the time of graph generation, Tuist then seamlessly substitutes the original targets with their corresponding binary versions. This operation, known as _"warming,"_ produces binaries for local use or for sharing with teammates and CI environments via Tuist. The process of warming the cache is straightforward and can be initiated with a simple command: ```bash tuist cache ``` The command re-uses binaries to speed up the process. ## Usage {#usage} By default, when Tuist commands necessitate project generation, they automatically substitute dependencies with their binary equivalents from the cache, if available. Additionally, if you specify a list of targets to focus on, Tuist will also replace any dependent targets with their cached binaries, provided they are available. For those who prefer a different approach, there is an option to opt out of this behavior entirely by using a specific flag: ::: code-group ```bash [Project generation] tuist generate # Only dependencies tuist generate Search # Dependencies + Search dependencies tuist generate Search Settings # Dependencies, and Search and Settings dependencies tuist generate --no-binary-cache # No cache at all ``` ```bash [Testing] tuist test ``` ::: > [!WARNING] > Binary caching is a feature designed for development workflows such as running the app on a simulator or device, or running tests. It is not intended for release builds. When archiving the app, generate a project with the sources by using the `--no-binary-cache` flag. ## Supported products {#supported-products} Only the following target products are cacheable by Tuist: - Frameworks (static and dynamic) that don't depend on [XCTest](https://developer.apple.com/documentation/xctest) - Bundles - Swift Macros We are working on supporting libraries and targets that depend on XCTest. > [!NOTE] UPSTREAM DEPENDENCIES > When a target is non-cacheable it makes the upstream targets non-cacheable too. For example, if you have the dependency graph `A > B`, where A depends on B, if B is non-cacheable, A will also be non-cacheable. ## Efficiency {#efficiency} The level of efficiency that can be achieved with binary caching depends strongly on the graph structure. To achieve the best results, we recommend the following: 1. Avoid very nested dependency graphs. The shallower the graph, the better. 2. Define dependencies with protocol/interface targets instead of implementation ones, and dependency-inject implementations from the top-most targets. 3. Split frequently-modified targets into smaller ones whose likelihood of change is lower. The above suggestions are part of the The Modular Architecture, which we propose as a way to structure your projects to maximize the benefits not only of binary caching but also of Xcode's capabilities. ## Recommended setup {#recommended-setup} We recommend having a CI job that **runs in every commit in the main branch** to warm the cache. This will ensure the cache always contains binaries for the changes in `main` so local and CI branch build incrementally upon them. > [!TIP] CACHE WARMING USES BINARIES > The `tuist cache` command also makes use of the binary cache to speed up the warming. The following are some examples of common workflows: ### A developer starts to work on a new feature {#a-developer-starts-to-work-on-a-new-feature} 1. They create a new branch from `main`. 2. They run `tuist generate`. 3. Tuist pulls the most recent binaries from `main` and generates the project with them. ### A developer pushes changes upstream {#a-developer-pushes-changes-upstream} 1. The CI pipeline will run `tuist build` or `tuist test` to build or test the project. 2. The workflow will pull the most recent binaries from `main` and generate the project with them. 3. It will then build or test the project incrementally. ## Troubleshooting {#troubleshooting} ### It doesn't use binaries for my targets {#it-doesnt-use-binaries-for-my-targets} Ensure that the hashes are deterministic across environments and runs. This might happen if the project has references to the environment, for example through absolute paths. You can use the `diff` command to compare the projects generated by two consecutive invocations of `tuist generate` or across environments or runs. Also make sure that the target doesn't depend either directly or indirectly on a non-cacheable target. --- URL: "/ja/guides/develop/build" LLMS_URL: "/ja/guides/develop/build.md" title: "Build" titleTemplate: ":title · Develop · Guides · Tuist" description: "Learn how to use Tuist to build your projects efficiently." --- # Build {#build} Projects are usually built through a build-system-provided CLI (e.g. `xcodebuild`). Tuist wraps them to improve the user experience and integrate the workflows with the platform to provide optimizations and analytics. You might wonder what's the value of using `tuist build` over generating the project with `tuist generate` (if needed) and building it with the platform-specific CLI. Here are some reasons: - **Single command:** `tuist build` ensures the project is generated if needed before compiling the project. - **Beautified output:** Tuist enriches the output using tools like [xcbeautify](https://github.com/cpisciotta/xcbeautify) that make the output more user-friendly. - Cache: It optimizes the build by deterministically reusing the build artifacts from a remote cache. - **Analytics:** It collects and reports metrics that are correlated with other data points to provide you with actionable information to make informed decisions. ## Usage {#usage} `tuist build` generates the project if needed, and then build it using the platform-specific build tool. We support the use of the `--` terminator to forward all subsequent arguments directly to the underlying build tool. This is useful when you need to pass arguments that are not supported by `tuist build` but are supported by the underlying build tool. ::: code-group ```bash [Build a scheme] tuist build MyScheme ``` ```bash [Build a specific configuration] tuist build MyScheme -- -configuration Debug ``` ```bash [Build all schemes without binary cache] tuist build --no-binary-cache ``` ::: --- URL: "/ja/guides/automate/continuous-integration" LLMS_URL: "/ja/guides/automate/continuous-integration.md" title: "継続的インテグレーション (CI)" titleTemplate: ":title · 自動化 · ガイド · Tuist" description: "CI ワークフローで Tuist を使用する方法を学びましょう。" --- # 継続的インテグレーション (CI) {#continuous-integration-ci} You can use Tuist in [continuous integration](https://en.wikipedia.org/wiki/Continuous_integration) environments. The following sections provide examples of how to do this on different CI platforms. ## Examples {#examples} To run Tuist commands in your CI workflows, you’ll need to install it in your CI environment. ### Xcode Cloud {#xcode-cloud} In [Xcode Cloud](https://developer.apple.com/xcode-cloud/), which uses Xcode projects as the source of truth, you'll need to add a [post-clone](https://developer.apple.com/documentation/xcode/writing-custom-build-scripts#Create-a-custom-build-script) script to install Tuist and run the commands you need, for example `tuist generate`: :::code-group ```bash [Mise] #!/bin/sh curl https://mise.jdx.dev/install.sh | sh mise install # Installs the version from .mise.toml # Runs the version of Tuist indicated in the .mise.toml file {#runs-the-version-of-tuist-indicated-in-the-misetoml-file} mise exec -- tuist generate ``` ```bash [Homebrew] #!/bin/sh brew install --formula tuist@x.y.z tuist generate ``` ::: ### Codemagic {#codemagic} In [Codemagic](https://codemagic.io), you can add an additional step to your workflow to install Tuist: ::: code-group ```yaml [Mise] workflows: lint: name: Build max_build_duration: 30 environment: xcode: 15.0.1 scripts: - name: Install Mise script: | curl https://mise.jdx.dev/install.sh | sh mise install # Installs the version from .mise.toml - name: Build script: mise exec -- tuist build ``` ```yaml [Homebrew] workflows: lint: name: Build max_build_duration: 30 environment: xcode: 15.0.1 scripts: - name: Install Tuist script: | brew install --formula tuist@x.y.z - name: Build script: tuist build ``` ::: ### GitHub Actions {#github-actions} On [GitHub Actions](https://docs.github.com/en/actions) you can add an additional step to install Tuist, and in the case of managing the installation of Mise, you can use the [mise-action](https://github.com/jdx/mise-action), which abstracts the installation of Mise and Tuist: ::: code-group ```yaml [Mise] name: Build Application on: pull_request: branches: - main push: branches: - main jobs: build: runs-on: macos-latest steps: - uses: actions/checkout@v3 - uses: jdx/mise-action@v2 - run: tuist build ``` ```yaml [Homebrew] name: test on: pull_request: branches: - main push: branches: - main jobs: lint: runs-on: macos-latest steps: - uses: actions/checkout@v3 - run: brew install --formula tuist@x.y.z - run: tuist build ``` ::: :::tip We recommend using `mise use --pin` in your Tuist projects to pin the version of Tuist across environments. The command will create a `.tool-versions` file containing the version of Tuist. ::: ## Authentication {#authentication} When using server-side features such as cache, you'll need a way to authenticate requests going from your CI workflows to the server. For that, you can generate a project-scoped token by running the following command: ```bash tuist project tokens create my-handle/MyApp ``` The command will generate a token for the project with full handle `my-account/my-project`. Set the value to the environment variable `TUIST_CONFIG_TOKEN` in your CI environment ensuring it's configured as a secret so it's not exposed. > [!IMPORTANT] CI ENVIRONMENT DETECTION > Tuist only uses the token when it detects it's running on a CI environment. If your CI environment is not detected, you can force the token usage by setting the environment variable `CI` to `1`. --- URL: "/ja/guides/ai/mcp" LLMS_URL: "/ja/guides/ai/mcp.md" title: "モデルコンテキストプロトコル(MCP)" titleTemplate: ":title · AI · Guides · Tuist" description: "Tuist MCPサーバーを使用して、アプリ開発環境に自然言語インターフェースを導入する方法を学びましょう。" --- # モデルコンテキストプロトコル (MCP) [MCP](https://www.claudemcp.com) は、LLM(大規模言語モデル)が開発環境と連携するための標準仕様として、[Claude](https://claude.ai) によって提案されたプロトコルです。 これは、LLM における USB-C のような存在と考えることができます。つまり、さまざまな開発環境とスムーズに接続できる、LLM 向けの共通インターフェースのようなものです。 貨物輸送を相互運用可能にしたコンテナや、アプリケーション層とトランスポート層を分離した通信プロトコルのように、MCPは[Claude](https://claude.ai/) のような LLMを活用したアプリケーションと、[Zed](https://zed.dev) や[Cursor](https://www.cursor.com) のようなエディタを、他のドメインと相互運用可能にします。 これは、LLM における USB-C のような存在と考えることができます。つまり、さまざまな開発環境とスムーズに接続できる、LLM 向けの共通インターフェースのようなものです。 貨物輸送を相互運用可能にしたコンテナや、アプリケーション層とトランスポート層を分離した通信プロトコルのように、MCPは[Claude](https://claude.ai/) のような LLMを活用したアプリケーションと、[Zed](https://zed.dev) や[Cursor](https://www.cursor.com) のようなエディタを、他のドメインと相互運用可能にします。 Tuist provides a local server through its CLI so that you can interact with your **app development environment**. クライアントアプリをこのサーバーに接続することで、自然言語を用いてプロジェクトと対話できるようになります。 このページではMCPの設定方法と、その機能について学ぶことができます。 > [!NOTE] > Tuist MCPサーバーは、操作対象となるプロジェクトの情報源として、Xcodeの最新プロジェクトを利用します。 ## 導入手順 ### [Claude](https://claude.ai) [Claude desktop](https://claude.ai/download) を使用している場合は、tuist mcp setup claude コマンドを実行することで、Claude 環境を構成できます。 または、 `~/Library/Application\ Support/Claude/claude_desktop_config.json` にあるファイルを手動で編集し、Tuist MCPサーバーを追加することもできます: :::code-group ```json [Global Tuist installation (e.g. Homebrew)] { "mcpServers": { "tuist": { "command": "tuist", "args": ["mcp", "start"] } } } ``` ```json [Mise installation] { "mcpServers": { "tuist": { "command": "mise", "args": ["x", "tuist@latest", "--", "tuist", "mcp", "start"] // Or tuist@x.y.z to fix the version } } } ``` ::: ## 主な機能 以下のセクションでは、Tuist MCPサーバーの機能について解説します。 ### 参考文献 #### 最近使用したプロジェクトとワークスペース Tuistは、最近操作したXcodeプロジェクトおよびワークスペースの記録を保持しており、アプリケーションがそれらの依存関係グラフにアクセスできるようにすることで、強力な分析や可視化を可能にします。 このデータに対してクエリを実行することで、例えば以下のようなプロジェクトの構造や依存関係などの詳細情報を把握できます: - 特定のターゲットに対する直接的および推移的な依存関係は何か? - 最も多くのソースファイルを含むターゲットはどれで、いくつのファイルが含まれているか? - グラフ内に含まれるすべての静的プロダクト(スタティックライブラリやフレームワークなど)は何か? - すべてのターゲットを名前とプロダクトの種類(アプリ、フレームワーク、ユニットテストなど)とともに、アルファベット順で並び替えは可能か? - 特定のフレームワークや外部依存関係に依存しているターゲットはどれか? - プロジェクト内のすべてのターゲットに含まれるソースファイルの合計数はいくつか? - ターゲット間に循環依存は存在するか?ある場合は、その発生箇所はどこか? - 特定のリソース(画像や plistファイルなど)を使用しているターゲットはどれか? - グラフ内で最も深い依存関係のチェーンは何か?また、それに関与しているターゲットはどれか? - すべてのテストターゲットと、それぞれが関連付けられているアプリまたはフレームワークのターゲットを表示することは可能か? - 最近の操作履歴に基づいて、ビルド時間が最も長いターゲットはどれか? - 2つの特定のターゲット間で依存関係にどのような違いがあるか? - プロジェクトに使用されていないソース ファイルやリソースはあるか? - どのターゲットが共通の依存関係を持っており、それらは何か? Tuist を使えば、これまでにない方法でXcodeプロジェクトを深く掘り下げて理解でき、複雑な構成であっても、理解・最適化・管理がより簡単になります! --- URL: "/ja/contributors/translate" LLMS_URL: "/ja/contributors/translate.md" title: "翻訳する" titleTemplate: ":title · コントリビューター · Tuist" description: "このドキュメントでは、Tuist の開発を導く原則について説明します。" --- # 翻訳 {#translate} 言語は理解を妨げる大きな壁になることがあります。 私たちは Tuist をできるだけ多くの人に使っていただきたいと考えています。 もし Tuist がサポートしていない言語を話す場合、Tuist のさまざまな部分を翻訳していただくことでご協力ください。 翻訳をメンテナンスするためには継続的な取り組みが必要となるため、私たちはメンテナンスに協力してくださるコントリビューターがいる言語を随時追加していきます。 現在サポートされている言語は、以下のとおりです: - 英語 - 韓国語 - 日本語 - ロシア語 > [!TIP] 新しい言語のリクエスト > Tuist に新しい言語サポートを追加することが有益であると考える場合は、コミュニティフォーラムの[トピック](https://community.tuist.io/c/general/4)を作成して、コミュニティと議論してみてください。 ## 翻訳方法 {#how-to-translate} 私たちは翻訳の管理に [Crowdin](https://crowdin.com/) を使用しています。 まず、貢献したいプロジェクトに移動します: - [Documentation](https://crowdin.com/project/tuist-documentation) - [Website](https://crowdin.com/project/tuist-documentation) 翻訳を始めるにはアカウントが必要です。 GitHub アカウントでサインインできます。 サインイン後にアクセス権を得ると、翻訳を始められます。 翻訳対象のリソース一覧が表示されます。 リソースをクリックするとエディタが開き、左側にソース言語のリソース、右側に翻訳する箇所が表示されます。 右側のテキストを翻訳し、変更を保存してください。 翻訳が更新されると Crowdin が自動的に該当リポジトリにプルリクエストを送信し、メンテナーがレビューとマージを行います。 > [!IMPORTANT] 対象言語のリソースに直接変更を加えないでください > Crowdin はファイルをセグメント化してソース言語とターゲット言語を関連付けています。 ソース言語を変更すると、この紐付けが壊れ、想定外の結果が生じる可能性があります。 ## ガイドライン {#guidelines} 以下は私たちが翻訳の際に従っているガイドラインです。 ### カスタムコンテナや GitHub Alerts について {#custom-containers-and-github-alerts} [カスタムコンテナ](https://vitepress.dev/guide/markdown#custom-containers) や [GitHub Alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts) を翻訳する際は、タイトルと内容のみ翻訳し、アラートの種類自体は翻訳しないようにしてください。 :::details GitHub Alert の例 ````markdown > [!WARNING] 루트 변수 > 매니페스트의 루트에 있어야 하는 변수는... // Instead of > [!주의] 루트 변수 > 매니페스트의 루트에 있어야 하는 변수는... ``` ::: ::: details Example with custom container ```markdown ::: warning 루트 변수\ 매니페스트의 루트에 있어야 하는 변수는... ::: # Instead of ::: 주의 루트 변수\ 매니페스트의 루트에 있어야 하는 변수는... ::: ```` ::: ### 見出しタイトル {#heading-titles} 見出しを翻訳する場合、見出しのタイトル部分のみ翻訳し、ID は変更しないでください。 たとえば、次の見出しがあるとします: ```markdown # Add dependencies {#add-dependencies} ``` これを翻訳する場合は、ID をそのままにして以下のように翻訳してください: ```markdown # 의존성 추가하기 {#add-dependencies} ``` --- URL: "/ja/contributors/principles" LLMS_URL: "/ja/contributors/principles.md" title: "原則" titleTemplate: ":title · コントリビューター · Tuist" description: "このドキュメントでは、Tuist の開発を導く原則について説明します。" --- # 原則 {#principles} このページでは、Tuistの設計と開発の柱となる原則について説明します。 これらの原則はプロジェクトとともに進化し、プロジェクトの基盤に適切に合致する形で持続可能な成長を保証するために存在します。 ## 慣習を基準にする {#default-to-conventions} Tuist が存在する理由のひとつは、Xcode が慣習に乏しく、それによって複雑で拡張性や保守性に難があるプロジェクトにつながっているためです。 そこで Tuist は、単純かつ綿密に設計された慣習に、まずは従うというアプローチを取ります。 **開発者は慣習から外れることもできますが、それはわざわざ行うことであり、自然に感じられない意識的な決定です。** たとえば、ターゲット間の依存関係を定義するとき、提供されたパブリック・インターフェイスを使用するという慣習があります。 これにより、Tuist はリンクが正しく動作するようプロジェクトを生成できます。 開発者はビルド設定を通じて依存関係を定義することも可能ですが、それを行うと暗黙的な定義になり、`tuist graph` や `tuist cache` のように特定の慣習に依存する Tuist の機能が正しく動作しなくなる恐れがあります。 慣習を既定とする理由は、開発者が行う決定をできる限り Tuist 側で肩代わりすることで、アプリの機能開発に専念できるようにするためです。 慣習がない状態では、大小さまざまな決定を下す必要があり、それらが一貫性を欠いた場合に複雑さが増していき、管理困難になってしまいます。 ## マニフェストを信頼できる唯一の情報源とする {#manifests-are-the-source-of-truth} 多層な設定やそれらの間をつなぐコンテキストを持つと、プロジェクトの設定を理解し保守することが困難になります。 一般的なプロジェクトで少し考えてみてください。 `.xcodeproj` ディレクトリにプロジェクト定義があり、CLI の設定はスクリプト(例: `Fastfiles`)の中にあり、CI のロジックはパイプラインにあります。 これらは三つの層が存在し、相互の間でコンテキストを維持する必要があります。 _プロジェクトを変更してから一週間後にリリーススクリプトが壊れていたと気づいた、というような経験はありませんか?_ これを簡単にする方法は、マニフェストファイルだけを信頼できる唯一の情報源とすることです。 これらのファイルは、Xcode プロジェクトを生成するために必要な情報を Tuist に提供します。 また、ローカルおよび CI 環境でプロジェクトをビルドするための統一されたコマンドを可能にします。 **Tuist は複雑な部分を引き受け、開発者ができるだけ明示的にプロジェクトを記述できるように、シンプルかつ安全で使いやすいインターフェイスを提供する必要があります。** ## 暗黙を明示化する {#make-the-implicit-explicit} Xcode は暗黙的な設定を数多くサポートしています。 依存関係を暗黙的に推測する仕様が好例でしょう。 小規模プロジェクトなど単純な設定であれば暗黙的な振る舞いでも問題にならない場合がありますが、プロジェクトが拡大するとビルドの遅延や予期せぬ挙動を引き起こしかねません。 Tuist は Xcode の暗黙的な挙動に対応する明示的な API を提供するべきです。 Xcode の暗黙的な設定をサポートする必要はありますが、最終的には開発者が明示的なアプローチを選択しやすい仕組みであることが望ましいです。 Xcode の暗黙や複雑さをサポートすることで、Tuist の導入障壁が下がり、チームは徐々に暗黙を排除していくことができます。 依存関係の定義は良い例です。 ビルド設定やビルドフェーズを通じて定義することもできますが、Tuist が提供する美しい API を使うことで、より自然に依存関係を明示できます。 **API を明示的に設計することで、Tuist がプロジェクトに対して行うチェックや最適化が可能になります。** さらに、依存関係グラフをエクスポートする `tuist graph` や、ターゲットをすべてバイナリとしてキャッシュする `tuist cache` のような機能も利用できるようになります。 > [!TIP] > Xcode から機能を移植しようという要望がある度に、その概念をシンプルかつ明示的な API に落とし込むチャンスと捉えるべきです。 ## シンプルに保つ {#keep-it-simple} Xcode プロジェクトが大規模化するときの主な課題のひとつは、**Xcode がユーザーに多くの複雑さを直接さらしている**ことです。その結果として、一部のメンバーしかプロジェクト全体やビルドシステムのエラーを理解できず、暗黙知がチーム内に偏在しがちです。 これはチームが少数の人に依存するリスクを抱えることになり、好ましい状況ではありません。 Xcodeは優れたツールですが、長年の改良や新プラットフォーム、プログラミング言語の追加により、シンプルなインターフェースの維持が難しくなっています。 Tuist は、この機会を活かして物事をシンプルに保つべきです。シンプルなものを扱うのは楽しいし、モチベーションも上がります。 経過時間が長いコンパイルプロセスの最後で発生する不可解なエラーの解析や、「なぜデバイスでアプリが動かないか」を延々と調べるような作業に時間を費やしたくはありません。 Xcode は内部のビルドシステムにタスクを委譲しますが、そのエラーがユーザーにとって分かりやすい形で返ってくるとは限りません。 _"framework X not found"_ というエラーを見ても、どう対応したらいいか分からないということはよくある話ですよね? もし根本原因の候補を一覧で提示してくれたらどれほど助かるでしょう。 ## 開発者体験を起点にする {#start-from-the-developers-experience} Xcode に関するイノベーションが乏しい(あるいは他の環境ほど盛んではない)理由のひとつは、**既存の解決策を起点に問題を分析することが多い**からです。その結果として、現在ある解決策の多くは同じようなアイデアやワークフローの範囲にとどまっています。 既存の解決策も検討に入れるのは良いことですが、それだけに縛られてしまうと創造性が制約されてしまいます。 私たちは [Tom Preston](https://tom.preston-werner.com/) が [このポッドキャスト](https://tom.preston-werner.com/) で語っている考え方を参考にしています。「プログラミングによるソリューションは、それが物理的に不可能でない限り、頭の中で想像するほとんどのことを実現できる」という趣旨です。つまり、**開発者体験がどうあってほしいかをまず想像**しさえすれば、実現までは時間の問題にすぎません。開発者体験を起点に問題を分析すれば、ユーザーに好まれるような独自の解決策にたどり着けるでしょう。 私たちは、皆と同じ行動を取りたくなる誘惑に駆られることがあります。たとえそれが、皆が絶えず不便だと感じている事柄に固執することを意味していたとしても。 そうしないでください。 アプリをアーカイブする理想の形はどうか? コード署名をもっと快適にするには? どんなプロセスなら Tuist で効率化できるか? たとえば [Fastlane](https://fastlane.tools/) 対応を検討する場合でも、まずは問題がどこにあるのか、なぜ必要とされているのかを掘り下げることが大切です。 「なぜ」という質問をすることで、問題の根本にたどり着くことができます。 モチベーションがどこから来るのかを絞り込めば、Tuistがどのように彼らを最善の方法で助けることができるかを考えることができる。 最終的な解決策として Fastlane との連携が正しい場合もあるかもしれませんが、それ以外の選択肢があるかもしれません。十分に検討し、トレードオフを理解したうえで最良の手段を選ぶことが重要です。 ## エラーは起こるもの {#errors-can-and-will-happen} 私たち開発者には、エラーは起こり得るという事実を軽視しがちな部分があります。 そのため、理想的なシナリオのみを想定してソフトウェアを設計・テストしてしまうことがよくあります。 Swift の型システムや適切なアーキテクチャ設計は多くのエラーを防ぐのに役立ちますが、それでもコントロール外の要因から発生するエラーは防げません。 ユーザーが常にインターネット接続できるとは限りませんし、システムコマンドが常に成功するとも限りません。 Tuist が動作する環境は私たちの制御下にあるサンドボックスではないので、どのように変動して Tuist に影響を与えるかを理解する努力が必要です。 エラーを適切に処理できないと、ユーザー体験は損なわれ、プロジェクトへの信頼も失われる可能性があります。 Tuist のあらゆる側面をユーザーが楽しめるようにしたいと考えています。 私たちはユーザーの立場に立って、エラーが伝えるべき内容を明確に想像するべきです。 プログラミング言語がエラー伝播のコミュニケーション手段であり、ユーザーがそのエラーの受け手であるなら、エラーはユーザーが理解する言語で記述されるべきです。 何が起こったのかを知るのに十分な情報を含める必要がありますし、関連性のない情報を隠す必要があります。 また、問題を解決するためにどのようなステップを実行できるかをユーザーに伝えることで、実用的であるべきです。 そして最後に、私達のテストケースは失敗するシナリオを熟考するべきです。 これによって、意図通りにエラーを処理できているかを保証し、将来的に他の開発者がそのロジックを壊すのを防ぐことができます。 --- URL: "/ja/contributors/issue-reporting" LLMS_URL: "/ja/contributors/issue-reporting.md" title: "Issue の報告" titleTemplate: ":title · コントリビューター · Tuist" description: "バグを報告してTuist に貢献する方法を学ぶ" --- # Issue の報告 {#issue-reporting} Tuist のユーザーとして、バグや予期しない動作に遭遇することがあるかもしれません。 その場合は、ぜひ報告してください。私たちが修正に取り組みます。 ## GitHub Issues は当プロジェクトのチケット管理プラットフォームです {#github-issues-is-our-ticketing-platform} 問題は GitHub の [Issue](https://github.com/tuist/tuist/issues) として報告し、Slack など他のプラットフォームでは報告しないでください。 GitHub は、問題の追跡や管理に適しており、コードベースに近い場所で問題の進捗を追うことができます。 加えて、問題の詳細な説明が推奨されるため、報告者は問題について考え、より多くの背景情報を提供することが求められます。 ## 背景情報が鍵 {#context-is-crucial} 背景情報が不十分な課題は不完全と見なされ、作成者は追加の情報を要求されます。 もし背景情報が提供されない場合、 Issue はクローズされます。 逆に言えば、コンテキストを多く提供するほど、私たちが問題を理解し、解決するのが容易になります。 そのため、Issue を解決してほしい場合は、できるだけ詳しい情報を提供してください。 次の質問に答える形で情報を記載してください。 - 試したことは何か? - プロジェクトの依存関係の状態はどうなっているのか? - 使用している Tuist のバージョンは? - その問題はあなたの作業をブロックしているのか? また、最小限の**再現可能なプロジェクト**の提供もお願いしています。 ## 再現可能なプロジェクト {#reproducible-project} ### 再現可能なプロジェクトとは? {#what-is-a-reproducible-project} 再現可能なプロジェクトは、問題を実証するための小さなTuistプロジェクトです - 多くの場合、この問題はTuistのバグによって引き起こされます。 再現可能なプロジェクトには、バグを明確に示すために必要な最小限の機能が含まれている必要があります。 ### なぜ再現可能なテストケースを作成する必要がありますか? {#why-should-you-create-a-reproducible-test-case} 再現性のあるプロジェクトでは、問題の原因を特定することができます。これは、問題を解決するための最初のステップです! バグレポートの最も重要な部分は、バグを再現するために必要な正確なステップを記述することです。 再現可能なプロジェクトは、バグを引き起こす特定の環境を共有するのに最適な方法です。 あなたの再現可能なプロジェクトは、あなたを助けたい人々を助けるための最善の方法です。 ### 再現可能なプロジェクトを作成する手順 {#steps-to-create-a-reproducible-project} - 新しい git リポジトリを作成します。 - リポジトリのディレクトリで `tuist init` を使用してプロジェクトを初期化します。 - あなたが見たエラーを再現するために必要なコードを追加してください。 - コードを公開(GitHubアカウントはこれを行うのに適した場所です)して、Issue を作成する際にリンクを掲載してください。 ### 再現可能なプロジェクトの利点 {#benefits-of-reproducible-projects} - **最小限の範囲:** エラー部分以外をすべて取り除くことで、どこにバグがあるのかを探るために余計な部分を深く調べる必要がなくなります。 - **秘密のコードを公開する必要はありません:** メインサイトを公開できない場合があります (多くの理由で)。 再現可能なテストケースとしてその小さな部分を再現することで、秘密のコードを公開せずに問題を公開することができます。 - **バグの証拠:** 時々バグはあなたのマシンの設定を組み合わせることによって引き起こされます。 再現可能なテストケースは、コントリビューターがあなたのビルドをダウンロードして彼らのマシンでテストすることを可能にします。 これは問題の原因を検証し、絞り込むのに役立ちます。 - **バグ修正のサポートを得る**: 他の人があなたの問題を再現できる場合、彼らが問題を解決する可能性は高いです。 バグを修正するためには、まずそれを再現できなければほとんど不可能です。 --- URL: "/ja/contributors/get-started" LLMS_URL: "/ja/contributors/get-started.md" title: "はじめに" titleTemplate: ":title · コントリビューター · Tuist" description: "このガイドに従って、Tuistへのコントリビューションを始めましょう。" --- # はじめに {#get-started} iOS などの Apple プラットフォーム向けのアプリ開発経験がある場合、Tuist にコードを追加することもそれほど違いはないでしょう。 アプリ開発と比べて、触れておくべき違いが2点あります。 - **CLIとのやり取りはターミナルを通じて行われます。** ユーザーはTuistを実行し、指定したタスクを実行した後、正常に終了するか、またはステータスコードを返します。 実行中は、標準出力や標準エラーに情報を出力することで、ユーザーに通知を行うことができます。 ジェスチャーやグラフィカルな操作はなく、あるのはユーザーの意図だけです。 - **プロセスを保持して入力待ちをするランループはありません。** これはiOSアプリがシステムやユーザーのイベントを受け取るときの挙動とは異なります。 CLIはそのプロセス内で実行され、タスクが完了すると終了します。 非同期処理は、[DispatchQueue](https://developer.apple.com/documentation/dispatch/dispatchqueue) や [構造化並行処理](https://developer.apple.com/tutorials/app-dev-training/managing-structured-concurrency) などのシステムAPIを使用して実行できますが、非同期処理が実行されている間はプロセスが稼働していることを確認する必要があります。 さもなければ、プロセスが終了し、非同期処理も中断されてしまいます。 Swiftに関する経験がない場合は、言語と Foundation API の主要な要素に慣れるために、[Appleの公式ブック](https://docs.swift.org/swift-book/)をお勧めします。 ## 最小要件 {#minimum-requirements} Tuist に貢献するには、最低限の要件があります。 - macOS 14.0 以上 - Xcode 16.3+ ## ローカルでプロジェクトをセットアップする {#set-up-the-project-locally} プロジェクトの作業を開始するには、以下の手順に従います。 - `git clone git@github.com:tuist/tuist.git` を実行してリポジトリをクローンします。 - 開発環境を整えるため、Mise を [インストール](https://mise.jdx.dev/getting-started.html) します。 - Tuist が必要とするシステム依存関係をインストールするため、 `mise install` を実行します。 - Tuist が必要とする外部依存関係をインストールするため、 `tuist install` を実行します。 - (任意) `tuist auth login` を実行して、 Tuist Cache へのアクセスを取得します - `tuist generate` を実行して、Tuist の Xcode プロジェクトを生成します。 **生成されたプロジェクトは自動的に開きます。** 再生成せずにもう一度開くには、`open Tuist.xcworkspace` を実行するか、Finderを使ってください。 > [!NOTE] XED . > `xed .` を使ってプロジェクトを開いた場合、Tuist が生成したプロジェクトではなく、パッケージが開きます。 Tuist で生成されたプロジェクトを使って、自分でツールを試すことを推奨します。 ## プロジェクトの編集 {#edit-the-project} 依存関係の追加やターゲットの調整など、プロジェクトを編集する必要がある場合は、`tuist edit` コマンドを使用できます。 あまり使われることはありませんが、知っておいて損はありません。 ## Tuist を実行する {#run-tuist} ### Xcode 経由 {#from-xcode} 生成されたXcodeプロジェクトから `tuist` を実行するには、`tuist` スキームを編集し、コマンドに渡す引数を設定します。 例えば、`tuist generate` コマンドを実行する際に、引数を `generate --no-open` に設定すると、生成後にプロジェクトが開かれるのを防げます。 ![Tuist で生成コマンドを実行するためのスキーム設定例](/images/contributors/scheme-arguments.png) また、生成されるプロジェクトのルートを作業ディレクトリに設定する必要があります。 `--path` 引数を使用して設定することも、以下のようにスキームで作業ディレクトリを設定することもできます。 ![Tuistを実行するための作業ディレクトリの設定例](/images/contributors/scheme-working-directory.png) > [!WARNING] PROJECTDESCRIPTION COMPILATION > `tuist` CLI は、ビルドされたプロダクトのディレクトリにある `ProjectDescription` フレームワークの存在に依存します。 `ProjectDescription` フレームワークが見つからずに `tuist` の実行が失敗した場合は、まず `Tuist-Workspace` スキームをビルドしてください。 ### ターミナル経由 {#from-the-terminal} Tuist 自体の `run` コマンドを使って `tuist` を実行できます。 ```bash tuist run tuist generate --path /path/to/project --no-open ``` または、Swift Package Manager で直接実行することもできます。 ```bash swift build --product ProjectDescription swift run tuist generate --path /path/to/project --no-open ``` --- URL: "/ja/contributors/code-reviews" LLMS_URL: "/ja/contributors/code-reviews.md" title: "コードレビュー" titleTemplate: ":title · コントリビューター · Tuist" description: "コードをレビューして、Tuist に貢献する方法を学ぶ" --- # コードレビュー {#code-reviews} プルリクエストのレビューはよくある貢献の形です。 継続的インテグレーション (CI) によってコードが期待通りに動作することが保証されていても、それだけでは十分ではありません。 設計、コードの構造・アーキテクチャ、テストの品質、タイポなど、自動化できない貢献要素が存在します。 以下の項では、コードレビューのプロセスに関するさまざまな観点を取り上げます。 ## 可読性 {#readability} そのコードは意図を明確に示していますか? **コードの意図を理解するのに時間がかかる場合、そのコードを改善する必要があるでしょう。** コードを理解しやすいよう、より小さく抽象的なコードに分割することを提案しましょう。 代替案として、そして最終手段として、レビュイーはコードの背後にある理由を説明するコメントを追加することができます。 プルリクエストの説明などの文脈がなくても、近い将来にそのコードを理解できるかどうか、自分自身に問いかけてみてください。 ## 小さなプルリクエスト {#small-pull-requests} 巨大なプルリクエストはレビューが難しく、詳細を見逃しやすくなります。 プルリクエストが大きくなりすぎて管理が難しくなった場合は、作成者に分割するよう提案してください。 > [!NOTE] 例外 > 変更が密接に結びついていて分割できない場合など、プルリクエストを分割できないケースがいくつかあります。 そのような場合、作成者は変更内容とその理由について明確に説明する必要があります。 ## 整合性 {#consistency} 変更がプロジェクト全体と整合性を保っていることが重要です。 整合性の欠如はメンテナンスを複雑にするため、避けるべきです。 ユーザーへのメッセージ出力やエラー報告の方法が既に決まっている場合は、それに従うべきです。 もし作成者がプロジェクトの標準に異議を唱えている場合は、議論を深めるために Issue を作成するよう提案してください。 ## テスト {#tests} テストは、安心してコードを変更できるようにしてくれます。 プルリクエストのコードはすべてテストされ、すべてのテストが通っている必要があります。 良いテストとは、一貫して同じ結果を生み出し、理解しやすく、保守しやすいテストのことです。 レビュワーは実装コードのレビューに多くの時間を費やしますが、テストもコードである以上同様に重要です。 ## 破壊的な変更 {#breaking-changes} 破壊的な変更はTuistのユーザーにとって悪いユーザー体験です。 どうしても避けられない場合を除き、破壊的な変更を含む貢献は避けてください。 破壊的な変更に頼らなくとも、Tuistのインターフェイスを進化させるために活用できる言語機能はたくさんあります。 破壊的な変更であるかが分かりづらい場合があるかもしれません。 fixturesディレクトリ内のfixtureプロジェクトに対してTuistを実行することでその変更が破壊的変更であるかを確認することができます。 ユーザーの立場に立ち、変更がユーザーにとってどのような影響を与えるかを想像しましょう。 --- URL: "/ja/contributors/cli/logging" LLMS_URL: "/ja/contributors/cli/logging.md" title: "ロギング" titleTemplate: ":title · CLI · Contributors · Tuist" description: "コードを確認してTuist に貢献する方法を学ぶ" --- # ロギング {#logging} CLI はロギングのために [swift-log](https://github.com/apple/swift-log) インターフェースを採用しています。 パッケージはロギングの実装の詳細を抽象化し、CLIがロギングバックエンドに依存しないようにします。 ロガーは [swift-service-context](https://github.com/apple/swift-service-context) を使用して依存性を注入されており、どこからでもアクセスできます: ```bash ServiceContext.current?.logger ``` > [!NOTE] > `swift-service-context` は、 `Dispatch` を使用して値を伝播しない [task locals](https://developer.apple.com/documentation/swift/tasklocal) を使用してインスタンスを渡します。 ですから、`Dispatch` を使用して非同期コードを実行する場合、コンテキストからインスタンスを取得し、非同期処理に渡すことになります。 ## 記録する内容 {#what-to-log} ログはCLIのUIではありません。 ログは発生した問題を診断するためのツールです。 したがって、あなたが提供する情報が多いほど、より良いです。 新しい機能を構築するときは、予期せぬ動作に遭遇する開発者の立場になって、 どんな情報が役に立つか考えください。 適切な[log level](https://www.swift.org/documentation/server/guides/libraries/log-levels.html)を使用することを徹底しましょう。 そうしないと、開発者は不要な情報をフィルターすることができなくなってしまいます。 --- URL: "/ja/cli/shell-completions" LLMS_URL: "/ja/cli/shell-completions.md" title: "Shell completions" titleTemplate: ":title · CLI · Tuist" description: "Learn how to configure your shell to auto-complete Tuist commands." --- # Shell completions Tuistを**グローバルにインストールしている**場合 (例えば、Homebrew経由で)、BashやZsh用のシェル補完をインストールして、コマンドやオプションを自動補完できます。 :::warning WHAT IS A GLOBAL INSTALLATION グローバルインストールは、シェルの `$PATH` 環境変数で利用可能なインストールです。 つまり、ターミナルの任意のディレクトリから `tuist` を実行できます。 This is the default installation method for Homebrew. ::: #### Zsh {#zsh} [oh-my-zsh](https://ohmyz.sh/) がインストールされている場合、自動的に読み込まれる補完スクリプトのディレクトリ `.oh-my-zsh/completions` があります。 新しい補完スクリプトをそのディレクトリに `_tuist` という名前の新しいファイルとしてコピーします。 ```bash tuist --generate-completion-script > ~/.oh-my-zsh/completions/_tuist ``` `oh-my-zsh` がない場合、補完スクリプトのパスを関数パスに追加し、補完スクリプトの自動読み込みを有効にする必要があります。 最初に、`~/.zshrc` に以下の行を追加します。 ```bash fpath=(~/.zsh/completion $fpath) autoload -U compinit compinit ``` 次に、`~/.zsh/completion` にディレクトリを作成し、補完スクリプトを新しいディレクトリに再度 `_tuist` というファイルにコピーします。 ```bash tuist --generate-completion-script > ~/.zsh/completion/_tuist ``` #### Bash {#bash} [bash-completion](https://github.com/scop/bash-completion) がインストールされている場合、新しい補完スクリプトをファイル `/usr/local/etc/bash_completion.d/_tuist` にコピーするだけで済みます。 ```bash tuist --generate-completion-script > /usr/local/etc/bash_completion.d/_tuist ``` bash-completion がない場合、補完スクリプトを直接 source で読み込む必要があります。 `~/.bash_completions/` のようなディレクトリにコピーし、次の行を `~/.bash_profile` または `~/.bashrc` に追加します。 ```bash source ~/.bash_completions/example.bash ``` #### Fish {#fish} If you use [fish shell](https://fishshell.com), you can copy your new completion script to `~/.config/fish/completions/tuist.fish`: ```bash mkdir -p ~/.config/fish/completions tuist --generate-completion-script > ~/.config/fish/completions/tuist.fish ``` --- URL: "/ja/cli/logging" LLMS_URL: "/ja/cli/logging.md" title: "ロギング" titleTemplate: ":title · CLI · Tuist" description: "Tuist でログを有効にして設定する方法を学ぶ。" --- # ロギング {#logging} CLI は問題を診断するのに役立つメッセージを内部的に記録します。 ## ログを使用して問題を診断する {#diagnose-issues-using-logs} コマンド呼び出しが意図した結果をもたらさない場合は、ログを調べることで問題を診断できます。 CLI はログを [OSLog](https://developer.apple.com/documentation/os/oslog) とファイルシステムに転送します。 実行ごとに、`$XDG_STATE_HOME/tuist/logs/{uuid}.log` にログファイルが作成されます。環境変数が設定されていない場合、 `$XDG_STATE_HOME` は `~/.local/state` の値をとります。 デフォルトでは、CLIは実行が予期せず終了した場合にログのパスを出力します。 出力されない場合は、上記のパス(つまり、最新のログファイル)にログを見つけることができます。 > [!IMPORTANT] > 機密情報はマスキングされていないので、ログを共有する際は注意してください。 ### 継続的インテグレーション {#diagnose-issues-using-logs-ci} CIでは、環境が使い捨てであるため、CIパイプラインを設定してTuistのログをエクスポートすることを検討する必要があるかもしれません。 成果物のエクスポートはCIサービスに共通する機能であり、設定は利用するサービスによって異なります。 たとえば、GitHub Actionsでは、`actions/upload-artifact` アクションを使用してログを成果物としてアップロードできます: ```yaml name: Node CI on: [push] env: XDG_STATE_HOME: /tmp jobs: build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 # ... other steps - run: tuist generate # ... do something with the project - name: Export Tuist logs uses: actions/upload-artifact@v4 with: name: tuist-logs path: /tmp/tuist/logs/*.log ``` --- URL: "/ja/cli/[command]" LLMS_URL: "/ja/cli/[command].md" editLink: false titleTemplate: ":title · CLI · Tuist" --- --- URL: "/generated/manifest/typealiases/SettingsDictionary" LLMS_URL: "/generated/manifest/typealiases/SettingsDictionary.md" --- **TYPEALIAS** # `SettingsDictionary` ```swift public typealias SettingsDictionary = [String: SettingValue] ``` --- URL: "/generated/manifest/typealiases/SettingValue.BooleanLiteralType" LLMS_URL: "/generated/manifest/typealiases/SettingValue.BooleanLiteralType.md" --- **TYPEALIAS** # `SettingValue.BooleanLiteralType` ```swift public typealias BooleanLiteralType = Bool ``` --- URL: "/generated/manifest/typealiases/RunActionOptions.SimulatedLocation" LLMS_URL: "/generated/manifest/typealiases/RunActionOptions.SimulatedLocation.md" --- **TYPEALIAS** # `RunActionOptions.SimulatedLocation` ```swift public typealias SimulatedLocation = ProjectDescription.SimulatedLocation ``` --- URL: "/generated/manifest/typealiases/PlatformFilters" LLMS_URL: "/generated/manifest/typealiases/PlatformFilters.md" --- **TYPEALIAS** # `PlatformFilters` ```swift public typealias PlatformFilters = Set ``` --- URL: "/generated/manifest/typealiases/Destinations" LLMS_URL: "/generated/manifest/typealiases/Destinations.md" --- **TYPEALIAS** # `Destinations` ```swift public typealias Destinations = Set ``` Set of deployment destinations --- URL: "/generated/manifest/typealiases/Config" LLMS_URL: "/generated/manifest/typealiases/Config.md" --- **TYPEALIAS** # `Config` ```swift public typealias Config = Tuist ``` The configuration of your environment. Tuist can be configured through a shared `Tuist.swift` manifest. When Tuist is executed, it traverses up the directories to find `Tuist.swift` file. Defining a configuration manifest is not required, but recommended to ensure a consistent behaviour across all the projects that are part of the repository. The example below shows a project that has a global `Tuist.swift` file that will be used when Tuist is run from any of the subdirectories: ```bash /Workspace.swift /Tuist.swift # Configuration manifest /Framework/Project.swift /App/Project.swift ``` That way, when executing Tuist in any of the subdirectories, it will use the shared configuration. The snippet below shows an example configuration manifest: ```swift import ProjectDescription let tuist = Config(project: .tuist(generationOptions: .options(resolveDependenciesWithSystemScm: false))) ``` --- URL: "/generated/manifest/typealiases/Array.UnicodeScalarLiteralType" LLMS_URL: "/generated/manifest/typealiases/Array.UnicodeScalarLiteralType.md" --- **TYPEALIAS** # `[FileElement].UnicodeScalarLiteralType` ```swift public typealias UnicodeScalarLiteralType = String ``` --- URL: "/generated/manifest/typealiases/Array.StringLiteralType" LLMS_URL: "/generated/manifest/typealiases/Array.StringLiteralType.md" --- **TYPEALIAS** # `[FileElement].StringLiteralType` ```swift public typealias StringLiteralType = String ``` --- URL: "/generated/manifest/typealiases/Array.ExtendedGraphemeClusterLiteralType" LLMS_URL: "/generated/manifest/typealiases/Array.ExtendedGraphemeClusterLiteralType.md" --- **TYPEALIAS** # `[FileElement].ExtendedGraphemeClusterLiteralType` ```swift public typealias ExtendedGraphemeClusterLiteralType = String ``` --- URL: "/generated/manifest/structs/Workspace" LLMS_URL: "/generated/manifest/structs/Workspace.md" --- **STRUCT** # `Workspace` **Contents** - [Properties](#properties) - `name` - `projects` - `schemes` - `fileHeaderTemplate` - `additionalFiles` - `generationOptions` - [Methods](#methods) - `init(name:projects:schemes:fileHeaderTemplate:additionalFiles:generationOptions:)` ```swift public struct Workspace: Codable, Equatable, Sendable ``` A workspace representation. By default, `tuist generate` generates an Xcode workspace that has the same name as the current project. It includes the project and all its dependencies. Tuist allows customizing this behaviour by defining a workspace manifest within a `Workspace.swift` file. Workspace manifests allow specifying a list of projects to generate and include in an Xcode workspace. Those projects don’t necessarily have to depend on one another. Additionally, files and folder references _(such as documentation files)_ can be included in a workspace manifest. The snippet below shows an example workspace manifest: ```swift import ProjectDescription let workspace = Workspace( name: "Workspace", projects: ["Projects/**"] ) ``` ## Properties ### `name` ```swift public let name: String ``` The name of the workspace. Also, the file name of the generated Xcode workspace. ### `projects` ```swift public let projects: [Path] ``` The paths (or glob patterns) to manifest projects. ### `schemes` ```swift public let schemes: [Scheme] ``` The custom schemes for the workspace. Default schemes for each target are generated by default. ### `fileHeaderTemplate` ```swift public let fileHeaderTemplate: FileHeaderTemplate? ``` The custom file header template for Xcode built-in file templates. ### `additionalFiles` ```swift public let additionalFiles: [FileElement] ``` The additional files for the workspace. For project's additional files, see ``Project/additionalFiles``. ### `generationOptions` ```swift public let generationOptions: GenerationOptions ``` The generation configuration of the workspace. ## Methods ### `init(name:projects:schemes:fileHeaderTemplate:additionalFiles:generationOptions:)` ```swift public init( name: String, projects: [Path], schemes: [Scheme] = [], fileHeaderTemplate: FileHeaderTemplate? = nil, additionalFiles: [FileElement] = [], generationOptions: GenerationOptions = .options() ) ``` --- URL: "/generated/manifest/structs/Workspace.GenerationOptions" LLMS_URL: "/generated/manifest/structs/Workspace.GenerationOptions.md" --- **STRUCT** # `Workspace.GenerationOptions` **Contents** - [Properties](#properties) - `enableAutomaticXcodeSchemes` - `autogeneratedWorkspaceSchemes` - `lastXcodeUpgradeCheck` - `renderMarkdownReadme` - [Methods](#methods) - `options(enableAutomaticXcodeSchemes:autogeneratedWorkspaceSchemes:lastXcodeUpgradeCheck:renderMarkdownReadme:)` ```swift public struct GenerationOptions: Codable, Equatable, Sendable ``` Generation options allow customizing the generation of the Xcode workspace. ## Properties ### `enableAutomaticXcodeSchemes` ```swift public var enableAutomaticXcodeSchemes: Bool? ``` Enable or disable automatic generation of schemes by Xcode. ### `autogeneratedWorkspaceSchemes` ```swift public var autogeneratedWorkspaceSchemes: AutogeneratedWorkspaceSchemes ``` Enable or disable automatic generation of `Workspace` schemes. If enabled, options to configure code coverage and test targets can be passed in via associated values. ### `lastXcodeUpgradeCheck` ```swift public var lastXcodeUpgradeCheck: Version? ``` Allows to suppress warnings in Xcode about updates to recommended settings added in or below the specified Xcode version. The warnings appear when Xcode version has been upgraded. It is recommended to set the version option to Xcode's version that is used for development of a project, for example `.lastXcodeUpgradeCheck(Version(13, 0, 0))` for Xcode 13.0.0. ### `renderMarkdownReadme` ```swift public var renderMarkdownReadme: Bool ``` Allows to render markdown files inside the workspace including an .xcodesamples.plist inside it. ## Methods ### `options(enableAutomaticXcodeSchemes:autogeneratedWorkspaceSchemes:lastXcodeUpgradeCheck:renderMarkdownReadme:)` ```swift public static func options( enableAutomaticXcodeSchemes: Bool? = false, autogeneratedWorkspaceSchemes: AutogeneratedWorkspaceSchemes = .enabled(), lastXcodeUpgradeCheck: Version? = nil, renderMarkdownReadme: Bool = false ) -> Self ``` --- URL: "/generated/manifest/structs/Version" LLMS_URL: "/generated/manifest/structs/Version.md" --- **STRUCT** # `Version` **Contents** - [Properties](#properties) - `major` - `minor` - `patch` - `prereleaseIdentifiers` - `buildMetadataIdentifiers` - [Methods](#methods) - `init(_:_:_:prereleaseIdentifiers:buildMetadataIdentifiers:)` ```swift public struct Version: Hashable, Codable, Sendable ``` A struct representing a semver version. This is taken from SPMUtility and copied here so we do not create a direct dependency for ProjectDescription. Used for specifying version number requirements inside of Project.swift ## Properties ### `major` ```swift public var major: Int ``` The major version. ### `minor` ```swift public var minor: Int ``` The minor version. ### `patch` ```swift public var patch: Int ``` The patch version. ### `prereleaseIdentifiers` ```swift public var prereleaseIdentifiers: [String] ``` The pre-release identifier. ### `buildMetadataIdentifiers` ```swift public var buildMetadataIdentifiers: [String] ``` The build metadata. ## Methods ### `init(_:_:_:prereleaseIdentifiers:buildMetadataIdentifiers:)` ```swift public init( _ major: Int, _ minor: Int, _ patch: Int, prereleaseIdentifiers: [String] = [], buildMetadataIdentifiers: [String] = [] ) ``` Create a version object. --- URL: "/generated/manifest/structs/TuistXcodeProjectOptions" LLMS_URL: "/generated/manifest/structs/TuistXcodeProjectOptions.md" --- **STRUCT** # `TuistXcodeProjectOptions` **Contents** - [Methods](#methods) - `options()` ```swift public struct TuistXcodeProjectOptions: Codable, Equatable, Sendable ``` ## Methods ### `options()` ```swift public static func options() -> Self ``` --- URL: "/generated/manifest/structs/Tuist" LLMS_URL: "/generated/manifest/structs/Tuist.md" --- **STRUCT** # `Tuist` **Contents** - [Properties](#properties) - `project` - `fullHandle` - `url` - [Methods](#methods) - `init(compatibleXcodeVersions:cloud:fullHandle:url:swiftVersion:plugins:generationOptions:installOptions:)` - `init(fullHandle:url:project:)` ```swift public struct Tuist: Codable, Equatable, Sendable ``` ## Properties ### `project` ```swift public let project: TuistProject ``` Configures the project Tuist will interact with. When no project is provided, Tuist defaults to the workspace or project in the current directory. ### `fullHandle` ```swift public let fullHandle: String? ``` The full project handle such as tuist-org/tuist. ### `url` ```swift public let url: String ``` The base URL that points to the Tuist server. ## Methods ### `init(compatibleXcodeVersions:cloud:fullHandle:url:swiftVersion:plugins:generationOptions:installOptions:)` ```swift public init( compatibleXcodeVersions: CompatibleXcodeVersions = .all, cloud: Cloud? = nil, fullHandle: String? = nil, url: String = "https://tuist.dev", swiftVersion: Version? = nil, plugins: [PluginLocation] = [], generationOptions: GenerationOptions = .options(), installOptions: InstallOptions = .options() ) ``` Creates a tuist configuration. - Parameters: - compatibleXcodeVersions: List of Xcode versions the project is compatible with. - cloud: Cloud configuration. - swiftVersion: The version of Swift that will be used by Tuist. - plugins: A list of plugins to extend Tuist. - generationOptions: List of options to use when generating the project. - installOptions: List of options to use when running `tuist install`. #### Parameters | Name | Description | | ---- | ----------- | | compatibleXcodeVersions | List of Xcode versions the project is compatible with. | | cloud | Cloud configuration. | | swiftVersion | The version of Swift that will be used by Tuist. | | plugins | A list of plugins to extend Tuist. | | generationOptions | List of options to use when generating the project. | | installOptions | List of options to use when running `tuist install`. | ### `init(fullHandle:url:project:)` ```swift public init( fullHandle: String? = nil, url: String = "https://tuist.dev", project: TuistProject ) ``` --- URL: "/generated/manifest/structs/Tuist.GenerationOptions" LLMS_URL: "/generated/manifest/structs/Tuist.GenerationOptions.md" --- **STRUCT** # `Tuist.GenerationOptions` **Contents** - [Properties](#properties) - `resolveDependenciesWithSystemScm` - `disablePackageVersionLocking` - `clonedSourcePackagesDirPath` - `staticSideEffectsWarningTargets` - `enforceExplicitDependencies` - `defaultConfiguration` - `optionalAuthentication` - `buildInsightsDisabled` - [Methods](#methods) - `options(resolveDependenciesWithSystemScm:disablePackageVersionLocking:clonedSourcePackagesDirPath:staticSideEffectsWarningTargets:defaultConfiguration:optionalAuthentication:buildInsightsDisabled:)` - `options(resolveDependenciesWithSystemScm:disablePackageVersionLocking:clonedSourcePackagesDirPath:staticSideEffectsWarningTargets:enforceExplicitDependencies:defaultConfiguration:optionalAuthentication:)` ```swift public struct GenerationOptions: Codable, Equatable, Sendable ``` Options for project generation. ## Properties ### `resolveDependenciesWithSystemScm` ```swift public var resolveDependenciesWithSystemScm: Bool ``` When passed, Xcode will resolve its Package Manager dependencies using the system-defined accounts (for example, git) instead of the Xcode-defined accounts ### `disablePackageVersionLocking` ```swift public var disablePackageVersionLocking: Bool ``` Disables locking Swift packages. This can speed up generation but does increase risk if packages are not locked in their declarations. ### `clonedSourcePackagesDirPath` ```swift public var clonedSourcePackagesDirPath: Path? ``` Allows setting a custom directory to be used when resolving package dependencies This path is passed to `xcodebuild` via the `-clonedSourcePackagesDirPath` argument ### `staticSideEffectsWarningTargets` ```swift public var staticSideEffectsWarningTargets: StaticSideEffectsWarningTargets ``` Allows configuring which targets Tuist checks for potential side effects due multiple branches of the graph including the same static library of framework as a transitive dependency. ### `enforceExplicitDependencies` ```swift public let enforceExplicitDependencies: Bool ``` The generated project has build settings and build paths modified in such a way that projects with implicit dependencies won't build until all dependencies are declared explicitly. ### `defaultConfiguration` ```swift public var defaultConfiguration: String? ``` The default configuration to be used when generating the project. If not specified, Tuist generates for the first (when alphabetically sorted) debug configuration. ### `optionalAuthentication` ```swift public var optionalAuthentication: Bool ``` Marks whether the Tuist server authentication is optional. If present, the interaction with the Tuist server will be skipped (instead of failing) if a user is not authenticated. ### `buildInsightsDisabled` ```swift public var buildInsightsDisabled: Bool ``` When disabled, build insights are not collected. Build insights are never collected unless you are connected to a remote Tuist project. Default value is `true`. ## Methods ### `options(resolveDependenciesWithSystemScm:disablePackageVersionLocking:clonedSourcePackagesDirPath:staticSideEffectsWarningTargets:defaultConfiguration:optionalAuthentication:buildInsightsDisabled:)` ```swift public static func options( resolveDependenciesWithSystemScm: Bool = false, disablePackageVersionLocking: Bool = false, clonedSourcePackagesDirPath: Path? = nil, staticSideEffectsWarningTargets: StaticSideEffectsWarningTargets = .all, defaultConfiguration: String? = nil, optionalAuthentication: Bool = false, buildInsightsDisabled: Bool = false ) -> Self ``` ### `options(resolveDependenciesWithSystemScm:disablePackageVersionLocking:clonedSourcePackagesDirPath:staticSideEffectsWarningTargets:enforceExplicitDependencies:defaultConfiguration:optionalAuthentication:)` ```swift public static func options( resolveDependenciesWithSystemScm: Bool = false, disablePackageVersionLocking: Bool = false, clonedSourcePackagesDirPath: Path? = nil, staticSideEffectsWarningTargets: StaticSideEffectsWarningTargets = .all, enforceExplicitDependencies: Bool, defaultConfiguration: String? = nil, optionalAuthentication: Bool = false ) -> Self ``` --- URL: "/generated/manifest/structs/TestingOptions" LLMS_URL: "/generated/manifest/structs/TestingOptions.md" --- **STRUCT** # `TestingOptions` **Contents** - [Properties](#properties) - `rawValue` - `parallelizable` - `randomExecutionOrdering` - [Methods](#methods) - `init(rawValue:)` ```swift public struct TestingOptions: OptionSet, Codable, Equatable, Sendable ``` Options to configure testing of autogenerated schemes. ## Properties ### `rawValue` ```swift public let rawValue: Int ``` ### `parallelizable` ```swift public static let parallelizable = TestingOptions(rawValue: 1 << 0) ``` Run tests on multiple destinations in parallel ### `randomExecutionOrdering` ```swift public static let randomExecutionOrdering = TestingOptions(rawValue: 1 << 1) ``` Execute tests in random order ## Methods ### `init(rawValue:)` ```swift public init(rawValue: Int) ``` #### Parameters | Name | Description | | ---- | ----------- | | rawValue | The raw value of the option set to create. Each bit of `rawValue` potentially represents an element of the option set, though raw values may include bits that are not defined as distinct values of the `OptionSet` type. | --- URL: "/generated/manifest/structs/TestableTarget" LLMS_URL: "/generated/manifest/structs/TestableTarget.md" --- **STRUCT** # `TestableTarget` **Contents** - [Properties](#properties) - `target` - `isSkipped` - `isParallelizable` - `parallelization` - `isRandomExecutionOrdering` - `simulatedLocation` - [Methods](#methods) - `testableTarget(target:isSkipped:isParallelizable:isRandomExecutionOrdering:simulatedLocation:)` - `testableTarget(target:isSkipped:parallelization:isRandomExecutionOrdering:simulatedLocation:)` - `init(stringLiteral:)` ```swift public struct TestableTarget: Equatable, Codable, ExpressibleByStringInterpolation, Sendable ``` ## Properties ### `target` ```swift public var target: TargetReference ``` ### `isSkipped` ```swift public var isSkipped: Bool ``` ### `isParallelizable` ```swift public var isParallelizable: Bool ``` ### `parallelization` ```swift public var parallelization: Parallelization ``` ### `isRandomExecutionOrdering` ```swift public var isRandomExecutionOrdering: Bool ``` ### `simulatedLocation` ```swift public var simulatedLocation: SimulatedLocation? ``` ## Methods ### `testableTarget(target:isSkipped:isParallelizable:isRandomExecutionOrdering:simulatedLocation:)` ```swift public static func testableTarget( target: TargetReference, isSkipped: Bool = false, isParallelizable: Bool, isRandomExecutionOrdering: Bool = false, simulatedLocation: SimulatedLocation? = nil ) -> Self ``` Returns a testable target. - Parameters: - target: The name or reference of target to test. - isSkipped: Whether to skip this test target. If true, the test target is disabled. - isParallelizable: Whether to run in parallel. - isRandomExecutionOrdering: Whether to test in random order. - simulatedLocation: The simulated GPS location to use when testing this target. Please note that the `.custom(gpxPath:)` case must refer to a valid GPX file in your project’s resources. #### Parameters | Name | Description | | ---- | ----------- | | target | The name or reference of target to test. | | isSkipped | Whether to skip this test target. If true, the test target is disabled. | | isParallelizable | Whether to run in parallel. | | isRandomExecutionOrdering | Whether to test in random order. | | simulatedLocation | The simulated GPS location to use when testing this target. Please note that the `.custom(gpxPath:)` case must refer to a valid GPX file in your project’s resources. | ### `testableTarget(target:isSkipped:parallelization:isRandomExecutionOrdering:simulatedLocation:)` ```swift public static func testableTarget( target: TargetReference, isSkipped: Bool = false, parallelization: Parallelization = .disabled, isRandomExecutionOrdering: Bool = false, simulatedLocation: SimulatedLocation? = nil ) -> Self ``` Returns a testable target. - Parameters: - target: The name or reference of target to test. - isSkipped: Whether to skip this test target. If true, the test target is disabled. - parallelization: Whether to run tests in parallel. Can be either `.disabled`, `.enabled`, or `.swiftTestingOnly`. The default value is `.disabled`. - isRandomExecutionOrdering: Whether to test in random order. - simulatedLocation: The simulated GPS location to use when testing this target. Please note that the `.custom(gpxPath:)` case must refer to a valid GPX file in your project’s resources. #### Parameters | Name | Description | | ---- | ----------- | | target | The name or reference of target to test. | | isSkipped | Whether to skip this test target. If true, the test target is disabled. | | parallelization | Whether to run tests in parallel. Can be either `.disabled`, `.enabled`, or `.swiftTestingOnly`. The default value is `.disabled`. | | isRandomExecutionOrdering | Whether to test in random order. | | simulatedLocation | The simulated GPS location to use when testing this target. Please note that the `.custom(gpxPath:)` case must refer to a valid GPX file in your project’s resources. | ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | --- URL: "/generated/manifest/structs/TestActionOptions" LLMS_URL: "/generated/manifest/structs/TestActionOptions.md" --- **STRUCT** # `TestActionOptions` **Contents** - [Properties](#properties) - `language` - `region` - `preferredScreenCaptureFormat` - `coverage` - `codeCoverageTargets` - [Methods](#methods) - `options(language:region:preferredScreenCaptureFormat:coverage:codeCoverageTargets:)` ```swift public struct TestActionOptions: Equatable, Codable, Sendable ``` The type `TestActionOptions` represents a set of options for a test action. ## Properties ### `language` ```swift public var language: SchemeLanguage? ``` Language used to run the tests. ### `region` ```swift public var region: String? ``` Region used to run the tests. ### `preferredScreenCaptureFormat` ```swift public var preferredScreenCaptureFormat: ScreenCaptureFormat? ``` Preferred screen capture format for UI tests results in Xcode 15+ ### `coverage` ```swift public var coverage: Bool ``` Whether the scheme should or not gather the test coverage data. ### `codeCoverageTargets` ```swift public var codeCoverageTargets: [TargetReference] ``` A list of targets you want to gather the test coverage data for them, which are defined in the project. ## Methods ### `options(language:region:preferredScreenCaptureFormat:coverage:codeCoverageTargets:)` ```swift public static func options( language: SchemeLanguage? = nil, region: String? = nil, preferredScreenCaptureFormat: ScreenCaptureFormat? = nil, coverage: Bool = false, codeCoverageTargets: [TargetReference] = [] ) -> TestActionOptions ``` Returns a set of options for a test action. - Parameters: - language: Language used for running the tests. - region: Region used for running the tests. - coverage: Whether test coverage should be collected. - codeCoverageTargets: List of test targets whose code coverage information should be collected. - Returns: A set of options. #### Parameters | Name | Description | | ---- | ----------- | | language | Language used for running the tests. | | region | Region used for running the tests. | | coverage | Whether test coverage should be collected. | | codeCoverageTargets | List of test targets whose code coverage information should be collected. | --- URL: "/generated/manifest/structs/TestAction" LLMS_URL: "/generated/manifest/structs/TestAction.md" --- **STRUCT** # `TestAction` **Contents** - [Properties](#properties) - `testPlans` - `targets` - `arguments` - `configuration` - `attachDebugger` - `expandVariableFromTarget` - `preActions` - `postActions` - `options` - `diagnosticsOptions` - `skippedTests` - [Methods](#methods) - `targets(_:arguments:configuration:attachDebugger:expandVariableFromTarget:preActions:postActions:options:diagnosticsOptions:skippedTests:)` - `testPlans(_:configuration:attachDebugger:preActions:postActions:)` ```swift public struct TestAction: Equatable, Codable, Sendable ``` An action that tests the built products. You can create a test action with either a set of test targets or test plans using the `.targets` or `.testPlans` static methods respectively. ## Properties ### `testPlans` ```swift public var testPlans: [Path]? ``` List of test plans. The first in the list will be the default plan. ### `targets` ```swift public var targets: [TestableTarget] ``` A list of testable targets, that are targets which are defined in the project with testable information. ### `arguments` ```swift public var arguments: Arguments? ``` Command line arguments passed on launch and environment variables. ### `configuration` ```swift public var configuration: ConfigurationName ``` Build configuration to run the test with. ### `attachDebugger` ```swift public var attachDebugger: Bool ``` Whether a debugger should be attached to the test process or not. ### `expandVariableFromTarget` ```swift public var expandVariableFromTarget: TargetReference? ``` A target that will be used to expand the variables defined inside Environment Variables definition (e.g. $SOURCE_ROOT) ### `preActions` ```swift public var preActions: [ExecutionAction] ``` A list of actions that are executed before starting the tests-run process. ### `postActions` ```swift public var postActions: [ExecutionAction] ``` A list of actions that are executed after the tests-run process. ### `options` ```swift public var options: TestActionOptions ``` List of options to set to the action. ### `diagnosticsOptions` ```swift public var diagnosticsOptions: SchemeDiagnosticsOptions ``` List of diagnostics options to set to the action. ### `skippedTests` ```swift public var skippedTests: [String]? ``` List of testIdentifiers to skip to the test ## Methods ### `targets(_:arguments:configuration:attachDebugger:expandVariableFromTarget:preActions:postActions:options:diagnosticsOptions:skippedTests:)` ```swift public static func targets( _ targets: [TestableTarget], arguments: Arguments? = nil, configuration: ConfigurationName = .debug, attachDebugger: Bool = true, expandVariableFromTarget: TargetReference? = nil, preActions: [ExecutionAction] = [], postActions: [ExecutionAction] = [], options: TestActionOptions = .options(), diagnosticsOptions: SchemeDiagnosticsOptions = .options(), skippedTests: [String] = [] ) -> Self ``` Returns a test action from a list of targets to be tested. - Parameters: - targets: List of targets to be tested. - arguments: Arguments passed when running the tests. - configuration: Configuration to be used. - attachDebugger: A boolean controlling whether a debugger is attached to the process running the tests. - expandVariableFromTarget: A target that will be used to expand the variables defined inside Environment Variables definition. When nil, it does not expand any variables. - preActions: Actions to execute before running the tests. - postActions: Actions to execute after running the tests. - options: Test options. - diagnosticsOptions: Diagnostics options. - Returns: An initialized test action. #### Parameters | Name | Description | | ---- | ----------- | | targets | List of targets to be tested. | | arguments | Arguments passed when running the tests. | | configuration | Configuration to be used. | | attachDebugger | A boolean controlling whether a debugger is attached to the process running the tests. | | expandVariableFromTarget | A target that will be used to expand the variables defined inside Environment Variables definition. When nil, it does not expand any variables. | | preActions | Actions to execute before running the tests. | | postActions | Actions to execute after running the tests. | | options | Test options. | | diagnosticsOptions | Diagnostics options. | ### `testPlans(_:configuration:attachDebugger:preActions:postActions:)` ```swift public static func testPlans( _ testPlans: [Path], configuration: ConfigurationName = .debug, attachDebugger: Bool = true, preActions: [ExecutionAction] = [], postActions: [ExecutionAction] = [] ) -> Self ``` Returns a test action from a list of test plans. - Parameters: - testPlans: List of test plans to run. - configuration: Configuration to be used. - attachDebugger: A boolean controlling whether a debugger is attached to the process running the tests. - preActions: Actions to execute before running the tests. - postActions: Actions to execute after running the tests. - Returns: A test action. #### Parameters | Name | Description | | ---- | ----------- | | testPlans | List of test plans to run. | | configuration | Configuration to be used. | | attachDebugger | A boolean controlling whether a debugger is attached to the process running the tests. | | preActions | Actions to execute before running the tests. | | postActions | Actions to execute after running the tests. | --- URL: "/generated/manifest/structs/TemplateString" LLMS_URL: "/generated/manifest/structs/TemplateString.md" --- **STRUCT** # `TemplateString` ```swift public struct TemplateString: Encodable, Decodable, Equatable ``` --- URL: "/generated/manifest/structs/TemplateString.StringInterpolation" LLMS_URL: "/generated/manifest/structs/TemplateString.StringInterpolation.md" --- **STRUCT** # `TemplateString.StringInterpolation` **Contents** - [Methods](#methods) - `init(literalCapacity:interpolationCount:)` - `appendLiteral(_:)` - `appendInterpolation(_:)` ```swift public struct StringInterpolation: StringInterpolationProtocol ``` ## Methods ### `init(literalCapacity:interpolationCount:)` ```swift public init(literalCapacity _: Int, interpolationCount _: Int) ``` #### Parameters | Name | Description | | ---- | ----------- | | literalCapacity | The approximate size of all literal segments combined. This is meant to be passed to `String.reserveCapacity(_:)`; it may be slightly larger or smaller than the sum of the counts of each literal segment. | | interpolationCount | The number of interpolations which will be appended. Use this value to estimate how much additional capacity will be needed for the interpolated segments. | ### `appendLiteral(_:)` ```swift public mutating func appendLiteral(_ literal: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | literal | A string literal containing the characters that appear next in the string literal. | ### `appendInterpolation(_:)` ```swift public mutating func appendInterpolation(_ token: TemplateString.Token) ``` --- URL: "/generated/manifest/structs/Template" LLMS_URL: "/generated/manifest/structs/Template.md" --- **STRUCT** # `Template` **Contents** - [Properties](#properties) - `description` - `attributes` - `items` - [Methods](#methods) - `init(description:attributes:items:)` ```swift public struct Template: Codable, Equatable, Sendable ``` A scaffold template model. ## Properties ### `description` ```swift public let description: String ``` Description of template ### `attributes` ```swift public let attributes: [Attribute] ``` Attributes to be passed to template ### `items` ```swift public let items: [Item] ``` Items to generate ## Methods ### `init(description:attributes:items:)` ```swift public init( description: String, attributes: [Attribute] = [], items: [Item] = [] ) ``` --- URL: "/generated/manifest/structs/Template.Item" LLMS_URL: "/generated/manifest/structs/Template.Item.md" --- **STRUCT** # `Template.Item` **Contents** - [Properties](#properties) - `path` - `contents` - [Methods](#methods) - `item(path:contents:)` ```swift public struct Item: Codable, Equatable, Sendable ``` File description for generating ## Properties ### `path` ```swift public let path: String ``` ### `contents` ```swift public let contents: Contents ``` ## Methods ### `item(path:contents:)` ```swift public static func item(path: String, contents: Contents) -> Self ``` --- URL: "/generated/manifest/structs/TargetScript" LLMS_URL: "/generated/manifest/structs/TargetScript.md" --- **STRUCT** # `TargetScript` **Contents** - [Properties](#properties) - `name` - `script` - `order` - `inputPaths` - `inputFileListPaths` - `outputPaths` - `outputFileListPaths` - `basedOnDependencyAnalysis` - `runForInstallBuildsOnly` - `shellPath` - `dependencyFile` - [Methods](#methods) - `pre(path:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` - `pre(path:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` - `post(path:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` - `post(path:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` - `pre(tool:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` - `pre(tool:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` - `post(tool:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` - `post(tool:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` - `pre(script:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` - `post(script:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` ```swift public struct TargetScript: Codable, Equatable, Sendable ``` A build phase action used to run a script. Target scripts, represented as target script build phases in the generated Xcode projects, are useful to define actions to be executed before of after the build process of a target. ## Properties ### `name` ```swift public var name: String ``` Name of the build phase when the project gets generated. ### `script` ```swift public var script: Script ``` The script that is to be executed ### `order` ```swift public var order: Order ``` Target script order. ### `inputPaths` ```swift public var inputPaths: [FileListGlob] ``` List of input file paths ### `inputFileListPaths` ```swift public var inputFileListPaths: [Path] ``` List of input filelist paths ### `outputPaths` ```swift public var outputPaths: [Path] ``` List of output file paths ### `outputFileListPaths` ```swift public var outputFileListPaths: [Path] ``` List of output filelist paths ### `basedOnDependencyAnalysis` ```swift public var basedOnDependencyAnalysis: Bool? ``` Whether to skip running this script in incremental builds, if nothing has changed ### `runForInstallBuildsOnly` ```swift public var runForInstallBuildsOnly: Bool ``` Whether this script only runs on install builds (default is false) ### `shellPath` ```swift public var shellPath: String ``` The path to the shell which shall execute this script. ### `dependencyFile` ```swift public var dependencyFile: Path? ``` The path to the dependency file ## Methods ### `pre(path:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` ```swift public static func pre( path: Path, arguments: String..., name: String, inputPaths: [FileListGlob] = [], inputFileListPaths: [Path] = [], outputPaths: [Path] = [], outputFileListPaths: [Path] = [], basedOnDependencyAnalysis: Bool? = nil, runForInstallBuildsOnly: Bool = false, shellPath: String = "/bin/sh", dependencyFile: Path? = nil ) -> TargetScript ``` Returns a target script that gets executed before the sources and resources build phase. - Parameters: - path: Path to the script to execute. - arguments: Arguments that to be passed. - name: Name of the build phase when the project gets generated. - inputPaths: Glob pattern to the files. - inputFileListPaths: List of input filelist paths. - outputPaths: List of output file paths. - outputFileListPaths: List of output filelist paths. - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds - runForInstallBuildsOnly: Whether this script only runs on install builds (default is false) - shellPath: The path to the shell which shall execute this script. Default is `/bin/sh`. - dependencyFile: The path to the dependency file. Default is `nil`. - Returns: Target script. #### Parameters | Name | Description | | ---- | ----------- | | path | Path to the script to execute. | | arguments | Arguments that to be passed. | | name | Name of the build phase when the project gets generated. | | inputPaths | Glob pattern to the files. | | inputFileListPaths | List of input filelist paths. | | outputPaths | List of output file paths. | | outputFileListPaths | List of output filelist paths. | | basedOnDependencyAnalysis | Whether to skip running this script in incremental builds | | runForInstallBuildsOnly | Whether this script only runs on install builds (default is false) | | shellPath | The path to the shell which shall execute this script. Default is `/bin/sh`. | | dependencyFile | The path to the dependency file. Default is `nil`. | ### `pre(path:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` ```swift public static func pre( path: Path, arguments: [String], name: String, inputPaths: [FileListGlob] = [], inputFileListPaths: [Path] = [], outputPaths: [Path] = [], outputFileListPaths: [Path] = [], basedOnDependencyAnalysis: Bool? = nil, runForInstallBuildsOnly: Bool = false, shellPath: String = "/bin/sh", dependencyFile: Path? = nil ) -> TargetScript ``` Returns a target script that gets executed before the sources and resources build phase. - Parameters: - path: Path to the script to execute. - arguments: Arguments that to be passed. - name: Name of the build phase when the project gets generated. - inputPaths: Glob pattern to the files. - inputFileListPaths: List of input filelist paths. - outputPaths: List of output file paths. - outputFileListPaths: List of output filelist paths. - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds - runForInstallBuildsOnly: Whether this script only runs on install builds (default is false) - shellPath: The path to the shell which shall execute this script. Default is `/bin/sh`. - dependencyFile: The path to the dependency file. Default is `nil`. - Returns: Target script. #### Parameters | Name | Description | | ---- | ----------- | | path | Path to the script to execute. | | arguments | Arguments that to be passed. | | name | Name of the build phase when the project gets generated. | | inputPaths | Glob pattern to the files. | | inputFileListPaths | List of input filelist paths. | | outputPaths | List of output file paths. | | outputFileListPaths | List of output filelist paths. | | basedOnDependencyAnalysis | Whether to skip running this script in incremental builds | | runForInstallBuildsOnly | Whether this script only runs on install builds (default is false) | | shellPath | The path to the shell which shall execute this script. Default is `/bin/sh`. | | dependencyFile | The path to the dependency file. Default is `nil`. | ### `post(path:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` ```swift public static func post( path: Path, arguments: String..., name: String, inputPaths: [FileListGlob] = [], inputFileListPaths: [Path] = [], outputPaths: [Path] = [], outputFileListPaths: [Path] = [], basedOnDependencyAnalysis: Bool? = nil, runForInstallBuildsOnly: Bool = false, shellPath: String = "/bin/sh", dependencyFile: Path? = nil ) -> TargetScript ``` Returns a target script that gets executed after the sources and resources build phase. - Parameters: - path: Path to the script to execute. - arguments: Arguments that to be passed. - name: Name of the build phase when the project gets generated. - inputPaths: Glob pattern to the files. - inputFileListPaths: List of input filelist paths. - outputPaths: List of output file paths. - outputFileListPaths: List of output filelist paths. - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds - runForInstallBuildsOnly: Whether this script only runs on install builds (default is false) - shellPath: The path to the shell which shall execute this script. Default is `/bin/sh`. - dependencyFile: The path to the dependency file. Default is `nil`. - Returns: Target script. #### Parameters | Name | Description | | ---- | ----------- | | path | Path to the script to execute. | | arguments | Arguments that to be passed. | | name | Name of the build phase when the project gets generated. | | inputPaths | Glob pattern to the files. | | inputFileListPaths | List of input filelist paths. | | outputPaths | List of output file paths. | | outputFileListPaths | List of output filelist paths. | | basedOnDependencyAnalysis | Whether to skip running this script in incremental builds | | runForInstallBuildsOnly | Whether this script only runs on install builds (default is false) | | shellPath | The path to the shell which shall execute this script. Default is `/bin/sh`. | | dependencyFile | The path to the dependency file. Default is `nil`. | ### `post(path:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` ```swift public static func post( path: Path, arguments: [String], name: String, inputPaths: [FileListGlob] = [], inputFileListPaths: [Path] = [], outputPaths: [Path] = [], outputFileListPaths: [Path] = [], basedOnDependencyAnalysis: Bool? = nil, runForInstallBuildsOnly: Bool = false, shellPath: String = "/bin/sh", dependencyFile: Path? = nil ) -> TargetScript ``` Returns a target script that gets executed after the sources and resources build phase. - Parameters: - path: Path to the script to execute. - arguments: Arguments that to be passed. - name: Name of the build phase when the project gets generated. - inputPaths: Glob pattern to the files. - inputFileListPaths: List of input filelist paths. - outputPaths: List of output file paths. - outputFileListPaths: List of output filelist paths. - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds - runForInstallBuildsOnly: Whether this script only runs on install builds (default is false) - shellPath: The path to the shell which shall execute this script. Default is `/bin/sh`. - dependencyFile: The path to the dependency file. Default is `nil`. - Returns: Target script. #### Parameters | Name | Description | | ---- | ----------- | | path | Path to the script to execute. | | arguments | Arguments that to be passed. | | name | Name of the build phase when the project gets generated. | | inputPaths | Glob pattern to the files. | | inputFileListPaths | List of input filelist paths. | | outputPaths | List of output file paths. | | outputFileListPaths | List of output filelist paths. | | basedOnDependencyAnalysis | Whether to skip running this script in incremental builds | | runForInstallBuildsOnly | Whether this script only runs on install builds (default is false) | | shellPath | The path to the shell which shall execute this script. Default is `/bin/sh`. | | dependencyFile | The path to the dependency file. Default is `nil`. | ### `pre(tool:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` ```swift public static func pre( tool: String, arguments: String..., name: String, inputPaths: [FileListGlob] = [], inputFileListPaths: [Path] = [], outputPaths: [Path] = [], outputFileListPaths: [Path] = [], basedOnDependencyAnalysis: Bool? = nil, runForInstallBuildsOnly: Bool = false, shellPath: String = "/bin/sh", dependencyFile: Path? = nil ) -> TargetScript ``` Returns a target script that gets executed before the sources and resources build phase. - Parameters: - tool: Name of the tool to execute. Tuist will look up the tool on the environment's PATH. - arguments: Arguments that to be passed. - name: Name of the build phase when the project gets generated. - inputPaths: Glob pattern to the files. - inputFileListPaths: List of input filelist paths. - outputPaths: List of output file paths. - outputFileListPaths: List of output filelist paths. - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds - runForInstallBuildsOnly: Whether this script only runs on install builds (default is false) - shellPath: The path to the shell which shall execute this script. Default is `/bin/sh`. - dependencyFile: The path to the dependency file. Default is `nil`. - Returns: Target script. #### Parameters | Name | Description | | ---- | ----------- | | tool | Name of the tool to execute. Tuist will look up the tool on the environment’s PATH. | | arguments | Arguments that to be passed. | | name | Name of the build phase when the project gets generated. | | inputPaths | Glob pattern to the files. | | inputFileListPaths | List of input filelist paths. | | outputPaths | List of output file paths. | | outputFileListPaths | List of output filelist paths. | | basedOnDependencyAnalysis | Whether to skip running this script in incremental builds | | runForInstallBuildsOnly | Whether this script only runs on install builds (default is false) | | shellPath | The path to the shell which shall execute this script. Default is `/bin/sh`. | | dependencyFile | The path to the dependency file. Default is `nil`. | ### `pre(tool:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` ```swift public static func pre( tool: String, arguments: [String], name: String, inputPaths: [FileListGlob] = [], inputFileListPaths: [Path] = [], outputPaths: [Path] = [], outputFileListPaths: [Path] = [], basedOnDependencyAnalysis: Bool? = nil, runForInstallBuildsOnly: Bool = false, shellPath: String = "/bin/sh", dependencyFile: Path? = nil ) -> TargetScript ``` Returns a target script that gets executed before the sources and resources build phase. - Parameters: - tool: Name of the tool to execute. Tuist will look up the tool on the environment's PATH. - arguments: Arguments that to be passed. - name: Name of the build phase when the project gets generated. - inputPaths: Glob pattern to the files. - inputFileListPaths: List of input filelist paths. - outputPaths: List of output file paths. - outputFileListPaths: List of output filelist paths. - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds - runForInstallBuildsOnly: Whether this script only runs on install builds (default is false) - shellPath: The path to the shell which shall execute this script. Default is `/bin/sh`. - dependencyFile: The path to the dependency file. Default is `nil`. - Returns: Target script. #### Parameters | Name | Description | | ---- | ----------- | | tool | Name of the tool to execute. Tuist will look up the tool on the environment’s PATH. | | arguments | Arguments that to be passed. | | name | Name of the build phase when the project gets generated. | | inputPaths | Glob pattern to the files. | | inputFileListPaths | List of input filelist paths. | | outputPaths | List of output file paths. | | outputFileListPaths | List of output filelist paths. | | basedOnDependencyAnalysis | Whether to skip running this script in incremental builds | | runForInstallBuildsOnly | Whether this script only runs on install builds (default is false) | | shellPath | The path to the shell which shall execute this script. Default is `/bin/sh`. | | dependencyFile | The path to the dependency file. Default is `nil`. | ### `post(tool:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` ```swift public static func post( tool: String, arguments: String..., name: String, inputPaths: [FileListGlob] = [], inputFileListPaths: [Path] = [], outputPaths: [Path] = [], outputFileListPaths: [Path] = [], basedOnDependencyAnalysis: Bool? = nil, runForInstallBuildsOnly: Bool = false, shellPath: String = "/bin/sh", dependencyFile: Path? = nil ) -> TargetScript ``` Returns a target script that gets executed after the sources and resources build phase. - Parameters: - tool: Name of the tool to execute. Tuist will look up the tool on the environment's PATH. - arguments: Arguments that to be passed. - name: Name of the build phase when the project gets generated. - inputPaths: Glob pattern to the files. - inputFileListPaths: List of input filelist paths. - outputPaths: List of output file paths. - outputFileListPaths: List of output filelist paths. - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds - runForInstallBuildsOnly: Whether this script only runs on install builds (default is false) - shellPath: The path to the shell which shall execute this script. Default is `/bin/sh`. - dependencyFile: The path to the dependency file. Default is `nil`. - Returns: Target script. #### Parameters | Name | Description | | ---- | ----------- | | tool | Name of the tool to execute. Tuist will look up the tool on the environment’s PATH. | | arguments | Arguments that to be passed. | | name | Name of the build phase when the project gets generated. | | inputPaths | Glob pattern to the files. | | inputFileListPaths | List of input filelist paths. | | outputPaths | List of output file paths. | | outputFileListPaths | List of output filelist paths. | | basedOnDependencyAnalysis | Whether to skip running this script in incremental builds | | runForInstallBuildsOnly | Whether this script only runs on install builds (default is false) | | shellPath | The path to the shell which shall execute this script. Default is `/bin/sh`. | | dependencyFile | The path to the dependency file. Default is `nil`. | ### `post(tool:arguments:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` ```swift public static func post( tool: String, arguments: [String], name: String, inputPaths: [FileListGlob] = [], inputFileListPaths: [Path] = [], outputPaths: [Path] = [], outputFileListPaths: [Path] = [], basedOnDependencyAnalysis: Bool? = nil, runForInstallBuildsOnly: Bool = false, shellPath: String = "/bin/sh", dependencyFile: Path? = nil ) -> TargetScript ``` Returns a target script that gets executed after the sources and resources build phase. - Parameters: - tool: Name of the tool to execute. Tuist will look up the tool on the environment's PATH. - arguments: Arguments that to be passed. - name: Name of the build phase when the project gets generated. - inputPaths: Glob pattern to the files. - inputFileListPaths: List of input filelist paths. - outputPaths: List of output file paths. - outputFileListPaths: List of output filelist paths. - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds - runForInstallBuildsOnly: Whether this script only runs on install builds (default is false) - shellPath: The path to the shell which shall execute this script. Default is `/bin/sh`. - dependencyFile: The path to the dependency file. Default is `nil`. - Returns: Target script. #### Parameters | Name | Description | | ---- | ----------- | | tool | Name of the tool to execute. Tuist will look up the tool on the environment’s PATH. | | arguments | Arguments that to be passed. | | name | Name of the build phase when the project gets generated. | | inputPaths | Glob pattern to the files. | | inputFileListPaths | List of input filelist paths. | | outputPaths | List of output file paths. | | outputFileListPaths | List of output filelist paths. | | basedOnDependencyAnalysis | Whether to skip running this script in incremental builds | | runForInstallBuildsOnly | Whether this script only runs on install builds (default is false) | | shellPath | The path to the shell which shall execute this script. Default is `/bin/sh`. | | dependencyFile | The path to the dependency file. Default is `nil`. | ### `pre(script:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` ```swift public static func pre( script: String, name: String, inputPaths: [FileListGlob] = [], inputFileListPaths: [Path] = [], outputPaths: [Path] = [], outputFileListPaths: [Path] = [], basedOnDependencyAnalysis: Bool? = nil, runForInstallBuildsOnly: Bool = false, shellPath: String = "/bin/sh", dependencyFile: Path? = nil ) -> TargetScript ``` Returns a target script that gets executed before the sources and resources build phase. - Parameters: - script: The text of the script to run. This should be kept small. - arguments: Arguments that to be passed. - name: Name of the build phase when the project gets generated. - inputPaths: Glob pattern to the files. - inputFileListPaths: List of input filelist paths. - outputPaths: List of output file paths. - outputFileListPaths: List of output filelist paths. - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds - runForInstallBuildsOnly: Whether this script only runs on install builds (default is false) - shellPath: The path to the shell which shall execute this script. Default is `/bin/sh`. - dependencyFile: The path to the dependency file. Default is `nil`. - Returns: Target script. #### Parameters | Name | Description | | ---- | ----------- | | script | The text of the script to run. This should be kept small. | | arguments | Arguments that to be passed. | | name | Name of the build phase when the project gets generated. | | inputPaths | Glob pattern to the files. | | inputFileListPaths | List of input filelist paths. | | outputPaths | List of output file paths. | | outputFileListPaths | List of output filelist paths. | | basedOnDependencyAnalysis | Whether to skip running this script in incremental builds | | runForInstallBuildsOnly | Whether this script only runs on install builds (default is false) | | shellPath | The path to the shell which shall execute this script. Default is `/bin/sh`. | | dependencyFile | The path to the dependency file. Default is `nil`. | ### `post(script:name:inputPaths:inputFileListPaths:outputPaths:outputFileListPaths:basedOnDependencyAnalysis:runForInstallBuildsOnly:shellPath:dependencyFile:)` ```swift public static func post( script: String, name: String, inputPaths: [FileListGlob] = [], inputFileListPaths: [Path] = [], outputPaths: [Path] = [], outputFileListPaths: [Path] = [], basedOnDependencyAnalysis: Bool? = nil, runForInstallBuildsOnly: Bool = false, shellPath: String = "/bin/sh", dependencyFile: Path? = nil ) -> TargetScript ``` Returns a target script that gets executed after the sources and resources build phase. - Parameters: - script: The script to be executed. - name: Name of the build phase when the project gets generated. - inputPaths: Glob pattern to the files. - inputFileListPaths: List of input filelist paths. - outputPaths: List of output file paths. - outputFileListPaths: List of output filelist paths. - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds - runForInstallBuildsOnly: Whether this script only runs on install builds (default is false) - shellPath: The path to the shell which shall execute this script. Default is `/bin/sh`. - dependencyFile: The path to the dependency file. Default is `nil`. - Returns: Target script. #### Parameters | Name | Description | | ---- | ----------- | | script | The script to be executed. | | name | Name of the build phase when the project gets generated. | | inputPaths | Glob pattern to the files. | | inputFileListPaths | List of input filelist paths. | | outputPaths | List of output file paths. | | outputFileListPaths | List of output filelist paths. | | basedOnDependencyAnalysis | Whether to skip running this script in incremental builds | | runForInstallBuildsOnly | Whether this script only runs on install builds (default is false) | | shellPath | The path to the shell which shall execute this script. Default is `/bin/sh`. | | dependencyFile | The path to the dependency file. Default is `nil`. | --- URL: "/generated/manifest/structs/TargetReference" LLMS_URL: "/generated/manifest/structs/TargetReference.md" --- **STRUCT** # `TargetReference` **Contents** - [Properties](#properties) - `projectPath` - `targetName` - [Methods](#methods) - `init(stringLiteral:)` - `project(path:target:)` - `target(_:)` ```swift public struct TargetReference: Hashable, Codable, ExpressibleByStringInterpolation, Sendable ``` A target reference for a specified project. The project is specified through the path and should contain the target name. ## Properties ### `projectPath` ```swift public var projectPath: Path? ``` Path to the target's project directory. ### `targetName` ```swift public var targetName: String ``` Name of the target. ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | ### `project(path:target:)` ```swift public static func project(path: Path, target: String) -> TargetReference ``` ### `target(_:)` ```swift public static func target(_ name: String) -> TargetReference ``` --- URL: "/generated/manifest/structs/TargetMetadata" LLMS_URL: "/generated/manifest/structs/TargetMetadata.md" --- **STRUCT** # `TargetMetadata` **Contents** - [Properties](#properties) - `tags` - `default` - [Methods](#methods) - `metadata(tags:)` ```swift public struct TargetMetadata: Codable, Equatable, Sendable ``` ## Properties ### `tags` ```swift public var tags: [String] ``` ### `default` ```swift public static var `default`: TargetMetadata ``` ## Methods ### `metadata(tags:)` ```swift public static func metadata( tags: [String] = [] ) -> TargetMetadata ``` Returns a target metadata. - Parameters: - tags: The list of tags to use. Used to select focused targets when generating the project. - Returns: Target metadata #### Parameters | Name | Description | | ---- | ----------- | | tags | The list of tags to use. Used to select focused targets when generating the project. | --- URL: "/generated/manifest/structs/Target" LLMS_URL: "/generated/manifest/structs/Target.md" --- **STRUCT** # `Target` **Contents** - [Properties](#properties) - `name` - `destinations` - `product` - `productName` - `bundleId` - `deploymentTargets` - `infoPlist` - `sources` - `resources` - `copyFiles` - `headers` - `entitlements` - `scripts` - `dependencies` - `settings` - `coreDataModels` - `environmentVariables` - `launchArguments` - `additionalFiles` - `buildRules` - `mergedBinaryType` - `mergeable` - `onDemandResourcesTags` - `metadata` - [Methods](#methods) - `target(name:destinations:product:productName:bundleId:deploymentTargets:infoPlist:sources:resources:copyFiles:headers:entitlements:scripts:dependencies:settings:coreDataModels:environmentVariables:launchArguments:additionalFiles:buildRules:mergedBinaryType:mergeable:onDemandResourcesTags:metadata:)` ```swift public struct Target: Codable, Equatable, Sendable ``` A target of a project. ## Properties ### `name` ```swift public var name: String ``` The name of the target. Also, the product name if not specified with ``productName``. ### `destinations` ```swift public var destinations: Destinations ``` The destinations this target supports, e.g. iPhone, appleVision, macCatalyst ### `product` ```swift public var product: Product ``` The type of build product this target will output. ### `productName` ```swift public var productName: String? ``` The built product name. If nil, it will be equal to ``name``. ### `bundleId` ```swift public var bundleId: String ``` The product bundle identifier. ### `deploymentTargets` ```swift public var deploymentTargets: DeploymentTargets? ``` The minimum deployment targets your product will support. ### `infoPlist` ```swift public var infoPlist: InfoPlist? ``` The Info.plist representation. ### `sources` ```swift public var sources: SourceFilesList? ``` The source files of the target. Note: any playgrounds matched by the globs used in this property will be automatically added. ### `resources` ```swift public var resources: ResourceFileElements? ``` The resource files of target. Note: localizable files, `*.lproj`, are supported. ### `copyFiles` ```swift public var copyFiles: [CopyFilesAction]? ``` The build phase copy files actions for the target. ### `headers` ```swift public var headers: Headers? ``` The headers for the target. ### `entitlements` ```swift public var entitlements: Entitlements? ``` The entitlements representation ### `scripts` ```swift public var scripts: [TargetScript] ``` The build phase scripts actions for the target. ### `dependencies` ```swift public var dependencies: [TargetDependency] ``` The target's dependencies. ### `settings` ```swift public var settings: Settings? ``` The target's settings. ### `coreDataModels` ```swift public var coreDataModels: [CoreDataModel] ``` The Core Data models. ### `environmentVariables` ```swift public var environmentVariables: [String: EnvironmentVariable] ``` The environment variables. Used by autogenerated schemes for the target. ### `launchArguments` ```swift public var launchArguments: [LaunchArgument] ``` The launch arguments. Used by autogenerated schemes for the target. ### `additionalFiles` ```swift public var additionalFiles: [FileElement] ``` The additional files for the target. For project's additional files, see ``Project/additionalFiles``. ### `buildRules` ```swift public var buildRules: [BuildRule] ``` The build rules used for transformation of source files during compilation. ### `mergedBinaryType` ```swift public var mergedBinaryType: MergedBinaryType ``` Specifies whether if the target can merge or not the dynamic dependencies as part of its binary ### `mergeable` ```swift public var mergeable: Bool ``` Specifies whether if the target can be merged as part of another binary or not ### `onDemandResourcesTags` ```swift public var onDemandResourcesTags: OnDemandResourcesTags? ``` The target's tags associated with on demand resources ### `metadata` ```swift public var metadata: TargetMetadata ``` The target's metadata. ## Methods ### `target(name:destinations:product:productName:bundleId:deploymentTargets:infoPlist:sources:resources:copyFiles:headers:entitlements:scripts:dependencies:settings:coreDataModels:environmentVariables:launchArguments:additionalFiles:buildRules:mergedBinaryType:mergeable:onDemandResourcesTags:metadata:)` ```swift public static func target( name: String, destinations: Destinations, product: Product, productName: String? = nil, bundleId: String, deploymentTargets: DeploymentTargets? = nil, infoPlist: InfoPlist? = .default, sources: SourceFilesList? = nil, resources: ResourceFileElements? = nil, copyFiles: [CopyFilesAction]? = nil, headers: Headers? = nil, entitlements: Entitlements? = nil, scripts: [TargetScript] = [], dependencies: [TargetDependency] = [], settings: Settings? = nil, coreDataModels: [CoreDataModel] = [], environmentVariables: [String: EnvironmentVariable] = [:], launchArguments: [LaunchArgument] = [], additionalFiles: [FileElement] = [], buildRules: [BuildRule] = [], mergedBinaryType: MergedBinaryType = .disabled, mergeable: Bool = false, onDemandResourcesTags: OnDemandResourcesTags? = nil, metadata: TargetMetadata = .default ) -> Self ``` --- URL: "/generated/manifest/structs/SourceFilesList" LLMS_URL: "/generated/manifest/structs/SourceFilesList.md" --- **STRUCT** # `SourceFilesList` **Contents** - [Properties](#properties) - `globs` - [Methods](#methods) - `sourceFilesList(globs:)` - `sourceFilesList(globs:)` - `paths(_:)` ```swift public struct SourceFilesList: Codable, Equatable, Sendable ``` A collection of source file globs. ## Properties ### `globs` ```swift public var globs: [SourceFileGlob] ``` List glob patterns. ## Methods ### `sourceFilesList(globs:)` ```swift public static func sourceFilesList(globs: [SourceFileGlob]) -> Self ``` Creates the source files list with the glob patterns. - Parameter globs: Glob patterns. #### Parameters | Name | Description | | ---- | ----------- | | globs | Glob patterns. | ### `sourceFilesList(globs:)` ```swift public static func sourceFilesList(globs: [String]) -> Self ``` Creates the source files list with the glob patterns as strings. - Parameter globs: Glob patterns. #### Parameters | Name | Description | | ---- | ----------- | | globs | Glob patterns. | ### `paths(_:)` ```swift public static func paths(_ paths: [Path]) -> SourceFilesList ``` Returns a sources list from a list of paths. - Parameter paths: Source paths. #### Parameters | Name | Description | | ---- | ----------- | | paths | Source paths. | --- URL: "/generated/manifest/structs/SourceFileGlob" LLMS_URL: "/generated/manifest/structs/SourceFileGlob.md" --- **STRUCT** # `SourceFileGlob` **Contents** - [Properties](#properties) - `glob` - `excluding` - `compilerFlags` - `codeGen` - `compilationCondition` - `type` - [Methods](#methods) - `glob(_:excluding:compilerFlags:codeGen:compilationCondition:)` - `glob(_:excluding:compilerFlags:codeGen:compilationCondition:)` - `generated(_:compilerFlags:codeGen:compilationCondition:)` ```swift public struct SourceFileGlob: Codable, Equatable, Sendable ``` A glob pattern configuration representing source files and its compiler flags, if any. ## Properties ### `glob` ```swift public var glob: Path ``` Glob pattern to the source files. ### `excluding` ```swift public var excluding: [Path] ``` Glob patterns for source files that will be excluded. ### `compilerFlags` ```swift public var compilerFlags: String? ``` The compiler flags to be set to the source files in the sources build phase. ### `codeGen` ```swift public var codeGen: FileCodeGen? ``` The source file attribute to be set in the build phase. ### `compilationCondition` ```swift public var compilationCondition: PlatformCondition? ``` Source file condition for compilation ### `type` ```swift public var type: FileType ``` Type of the file. ## Methods ### `glob(_:excluding:compilerFlags:codeGen:compilationCondition:)` ```swift public static func glob( _ glob: Path, excluding: [Path] = [], compilerFlags: String? = nil, codeGen: FileCodeGen? = nil, compilationCondition: PlatformCondition? = nil ) -> Self ``` Returns a source glob pattern configuration. Used for file there were already present during the generation. - Parameters: - glob: Glob pattern to the source files. - excluding: Glob patterns for source files that will be excluded. - compilerFlags: The compiler flags to be set to the source files in the sources build phase. - codeGen: The source file attribute to be set in the build phase. - compilationCondition: Condition for file compilation. #### Parameters | Name | Description | | ---- | ----------- | | glob | Glob pattern to the source files. | | excluding | Glob patterns for source files that will be excluded. | | compilerFlags | The compiler flags to be set to the source files in the sources build phase. | | codeGen | The source file attribute to be set in the build phase. | | compilationCondition | Condition for file compilation. | ### `glob(_:excluding:compilerFlags:codeGen:compilationCondition:)` ```swift public static func glob( _ glob: Path, excluding: Path?, compilerFlags: String? = nil, codeGen: FileCodeGen? = nil, compilationCondition: PlatformCondition? = nil ) -> Self ``` ### `generated(_:compilerFlags:codeGen:compilationCondition:)` ```swift public static func generated( _ path: Path, compilerFlags: String? = nil, codeGen: FileCodeGen? = nil, compilationCondition: PlatformCondition? = nil ) -> Self ``` Returns a source generated source file configuration, for a single generated file. - Parameters: - path: Path to the generated file. Assumed to be a specific path (as oppose to a glob pattern). - compilerFlags: The compiler flags to be set to the source files in the sources build phase. - codeGen: The source file attribute to be set in the build phase. - compilationCondition: Condition for file compilation. #### Parameters | Name | Description | | ---- | ----------- | | path | Path to the generated file. Assumed to be a specific path (as oppose to a glob pattern). | | compilerFlags | The compiler flags to be set to the source files in the sources build phase. | | codeGen | The source file attribute to be set in the build phase. | | compilationCondition | Condition for file compilation. | --- URL: "/generated/manifest/structs/SimulatedLocation" LLMS_URL: "/generated/manifest/structs/SimulatedLocation.md" --- **STRUCT** # `SimulatedLocation` **Contents** - [Properties](#properties) - `identifier` - `gpxFile` - `london` - `johannesburg` - `moscow` - `mumbai` - `tokyo` - `sydney` - `hongKong` - `honolulu` - `sanFrancisco` - `mexicoCity` - `newYork` - `rioDeJaneiro` - [Methods](#methods) - `custom(gpxFile:)` ```swift public struct SimulatedLocation: Codable, Equatable, Sendable ``` Simulated location represents a GPS location that is used when running an app on the simulator. ## Properties ### `identifier` ```swift public var identifier: String? ``` The identifier of the location (e.g. London, England) ### `gpxFile` ```swift public var gpxFile: Path? ``` Path to a .gpx file that indicates the location ### `london` ```swift public static var london: SimulatedLocation ``` ### `johannesburg` ```swift public static var johannesburg: SimulatedLocation ``` ### `moscow` ```swift public static var moscow: SimulatedLocation ``` ### `mumbai` ```swift public static var mumbai: SimulatedLocation ``` ### `tokyo` ```swift public static var tokyo: SimulatedLocation ``` ### `sydney` ```swift public static var sydney: SimulatedLocation ``` ### `hongKong` ```swift public static var hongKong: SimulatedLocation ``` ### `honolulu` ```swift public static var honolulu: SimulatedLocation ``` ### `sanFrancisco` ```swift public static var sanFrancisco: SimulatedLocation ``` ### `mexicoCity` ```swift public static var mexicoCity: SimulatedLocation ``` ### `newYork` ```swift public static var newYork: SimulatedLocation ``` ### `rioDeJaneiro` ```swift public static var rioDeJaneiro: SimulatedLocation ``` ## Methods ### `custom(gpxFile:)` ```swift public static func custom(gpxFile: Path) -> SimulatedLocation ``` --- URL: "/generated/manifest/structs/Settings" LLMS_URL: "/generated/manifest/structs/Settings.md" --- **STRUCT** # `Settings` **Contents** - [Properties](#properties) - `base` - `configurations` - `defaultSettings` - [Methods](#methods) - `settings(base:debug:release:defaultSettings:)` - `settings(base:configurations:defaultSettings:)` ```swift public struct Settings: Equatable, Codable, Sendable ``` A group of settings configuration. ## Properties ### `base` ```swift public var base: SettingsDictionary ``` A dictionary with build settings that are inherited from all the configurations. ### `configurations` ```swift public var configurations: [Configuration] ``` ### `defaultSettings` ```swift public var defaultSettings: DefaultSettings ``` ## Methods ### `settings(base:debug:release:defaultSettings:)` ```swift public static func settings( base: SettingsDictionary = [:], debug: SettingsDictionary = [:], release: SettingsDictionary = [:], defaultSettings: DefaultSettings = .recommended ) -> Settings ``` Creates settings with default.configurations `Debug` and `Release` - Parameters: - base: A dictionary with build settings that are inherited from all the configurations. - debug: The debug configuration settings. - release: The release configuration settings. - defaultSettings: An enum specifying the set of default settings. - Note: To specify custom configurations (e.g. `Debug`, `Beta` & `Release`) or to specify xcconfigs, you can use the alternate static method `.settings(base:configurations:defaultSettings:)` - seealso: Configuration - seealso: DefaultSettings #### Parameters | Name | Description | | ---- | ----------- | | base | A dictionary with build settings that are inherited from all the configurations. | | debug | The debug configuration settings. | | release | The release configuration settings. | | defaultSettings | An enum specifying the set of default settings. | ### `settings(base:configurations:defaultSettings:)` ```swift public static func settings( base: SettingsDictionary = [:], configurations: [Configuration], defaultSettings: DefaultSettings = .recommended ) -> Settings ``` Creates settings with any number of configurations. - Parameters: - base: A dictionary with build settings that are inherited from all the configurations. - configurations: A list of configurations. - defaultSettings: An enum specifying the set of default settings. - Note: Configurations shouldn't be empty, please use the alternate static method `.settings(base:debug:release:defaultSettings:)` to leverage the default configurations if you don't have any custom configurations. - seealso: Configuration - seealso: DefaultSettings #### Parameters | Name | Description | | ---- | ----------- | | base | A dictionary with build settings that are inherited from all the configurations. | | configurations | A list of configurations. | | defaultSettings | An enum specifying the set of default settings. | --- URL: "/generated/manifest/structs/SchemeLanguage" LLMS_URL: "/generated/manifest/structs/SchemeLanguage.md" --- **STRUCT** # `SchemeLanguage` **Contents** - [Properties](#properties) - `identifier` - [Methods](#methods) - `init(identifier:)` - `init(stringLiteral:)` ```swift public struct SchemeLanguage: Codable, Equatable, ExpressibleByStringLiteral, Sendable ``` A language to use for run and test actions. ## Properties ### `identifier` ```swift public let identifier: String ``` ## Methods ### `init(identifier:)` ```swift public init(identifier: String) ``` Creates a new scheme language. - Parameter identifier: A valid language code or a pre-defined pseudo language. #### Parameters | Name | Description | | ---- | ----------- | | identifier | A valid language code or a pre-defined pseudo language. | ### `init(stringLiteral:)` ```swift public init(stringLiteral: String) ``` Creates a new scheme language. - Parameter stringLiteral: A valid language code or a pre-defined pseudo language. #### Parameters | Name | Description | | ---- | ----------- | | stringLiteral | A valid language code or a pre-defined pseudo language. | --- URL: "/generated/manifest/structs/SchemeDiagnosticsOptions" LLMS_URL: "/generated/manifest/structs/SchemeDiagnosticsOptions.md" --- **STRUCT** # `SchemeDiagnosticsOptions` **Contents** - [Properties](#properties) - `addressSanitizerEnabled` - `detectStackUseAfterReturnEnabled` - `threadSanitizerEnabled` - `mainThreadCheckerEnabled` - `performanceAntipatternCheckerEnabled` - [Methods](#methods) - `options(addressSanitizerEnabled:detectStackUseAfterReturnEnabled:threadSanitizerEnabled:mainThreadCheckerEnabled:performanceAntipatternCheckerEnabled:)` ```swift public struct SchemeDiagnosticsOptions: Equatable, Codable, Sendable ``` Options to configure scheme diagnostics for run and test actions. ## Properties ### `addressSanitizerEnabled` ```swift public var addressSanitizerEnabled: Bool ``` Enable the address sanitizer ### `detectStackUseAfterReturnEnabled` ```swift public var detectStackUseAfterReturnEnabled: Bool ``` Enable the detect use of stack after return of address sanitizer ### `threadSanitizerEnabled` ```swift public var threadSanitizerEnabled: Bool ``` Enable the thread sanitizer ### `mainThreadCheckerEnabled` ```swift public var mainThreadCheckerEnabled: Bool ``` Enable the main thread cheker ### `performanceAntipatternCheckerEnabled` ```swift public var performanceAntipatternCheckerEnabled: Bool ``` Enable thread performance checker ## Methods ### `options(addressSanitizerEnabled:detectStackUseAfterReturnEnabled:threadSanitizerEnabled:mainThreadCheckerEnabled:performanceAntipatternCheckerEnabled:)` ```swift public static func options( addressSanitizerEnabled: Bool = false, detectStackUseAfterReturnEnabled: Bool = false, threadSanitizerEnabled: Bool = false, mainThreadCheckerEnabled: Bool = true, performanceAntipatternCheckerEnabled: Bool = true ) -> SchemeDiagnosticsOptions ``` --- URL: "/generated/manifest/structs/Scheme" LLMS_URL: "/generated/manifest/structs/Scheme.md" --- **STRUCT** # `Scheme` **Contents** - [Properties](#properties) - `name` - `shared` - `hidden` - `buildAction` - `testAction` - `runAction` - `archiveAction` - `profileAction` - `analyzeAction` - [Methods](#methods) - `scheme(name:shared:hidden:buildAction:testAction:runAction:archiveAction:profileAction:analyzeAction:)` ```swift public struct Scheme: Equatable, Codable, Sendable ``` A custom scheme for a project. A scheme defines a collection of targets to Build, Run, Test, Profile, Analyze and Archive. ## Properties ### `name` ```swift public var name: String ``` The name of the scheme. ### `shared` ```swift public var shared: Bool ``` Marks the scheme as shared (i.e. one that is checked in to the repository and is visible to xcodebuild from the command line). ### `hidden` ```swift public var hidden: Bool ``` When `true` the scheme doesn't show up in the dropdown scheme's list. ### `buildAction` ```swift public var buildAction: BuildAction? ``` Action that builds the project targets. ### `testAction` ```swift public var testAction: TestAction? ``` Action that runs the project tests. ### `runAction` ```swift public var runAction: RunAction? ``` Action that runs project built products. ### `archiveAction` ```swift public var archiveAction: ArchiveAction? ``` Action that runs the project archive. ### `profileAction` ```swift public var profileAction: ProfileAction? ``` Action that profiles the project. ### `analyzeAction` ```swift public var analyzeAction: AnalyzeAction? ``` Action that analyze the project. ## Methods ### `scheme(name:shared:hidden:buildAction:testAction:runAction:archiveAction:profileAction:analyzeAction:)` ```swift public static func scheme( name: String, shared: Bool = true, hidden: Bool = false, buildAction: BuildAction? = nil, testAction: TestAction? = nil, runAction: RunAction? = nil, archiveAction: ArchiveAction? = nil, profileAction: ProfileAction? = nil, analyzeAction: AnalyzeAction? = nil ) -> Self ``` Creates a new instance of a scheme. - Parameters: - name: Name of the scheme. - shared: Whether the scheme is shared. - hidden: When true, the scheme is hidden in the list of schemes from Xcode's dropdown. - buildAction: Action that builds the project targets. - testAction: Action that runs the project tests. - runAction: Action that runs project built products. - archiveAction: Action that runs the project archive. - profileAction: Action that profiles the project. - analyzeAction: Action that analyze the project. #### Parameters | Name | Description | | ---- | ----------- | | name | Name of the scheme. | | shared | Whether the scheme is shared. | | hidden | When true, the scheme is hidden in the list of schemes from Xcode’s dropdown. | | buildAction | Action that builds the project targets. | | testAction | Action that runs the project tests. | | runAction | Action that runs project built products. | | archiveAction | Action that runs the project archive. | | profileAction | Action that profiles the project. | | analyzeAction | Action that analyze the project. | --- URL: "/generated/manifest/structs/RunActionOptions" LLMS_URL: "/generated/manifest/structs/RunActionOptions.md" --- **STRUCT** # `RunActionOptions` **Contents** - [Properties](#properties) - `language` - `region` - `storeKitConfigurationPath` - `simulatedLocation` - `enableGPUFrameCaptureMode` - [Methods](#methods) - `options(language:region:storeKitConfigurationPath:simulatedLocation:enableGPUFrameCaptureMode:)` ```swift public struct RunActionOptions: Equatable, Codable, Sendable ``` Options for the `RunAction` action ## Properties ### `language` ```swift public var language: SchemeLanguage? ``` Language to use when running the app. ### `region` ```swift public var region: String? ``` Region to use when running the app. ### `storeKitConfigurationPath` ```swift public var storeKitConfigurationPath: Path? ``` The path of the [StoreKit configuration file](https://developer.apple.com/documentation/xcode/setting_up_storekit_testing_in_xcode#3625700). ### `simulatedLocation` ```swift public var simulatedLocation: ProjectDescription.SimulatedLocation? ``` A simulated GPS location to use when running the app. ### `enableGPUFrameCaptureMode` ```swift public var enableGPUFrameCaptureMode: GPUFrameCaptureMode ``` Configure your project to work with the Metal frame debugger. ## Methods ### `options(language:region:storeKitConfigurationPath:simulatedLocation:enableGPUFrameCaptureMode:)` ```swift public static func options( language: SchemeLanguage? = nil, region: String? = nil, storeKitConfigurationPath: Path? = nil, simulatedLocation: ProjectDescription.SimulatedLocation? = nil, enableGPUFrameCaptureMode: GPUFrameCaptureMode = GPUFrameCaptureMode.default ) -> Self ``` Creates an `RunActionOptions` instance - Parameters: - language: language (e.g. "en"). - region: region (e.g. "US"). - storeKitConfigurationPath: The path of the [StoreKit configuration file](https://developer.apple.com/documentation/xcode/setting_up_storekit_testing_in_xcode#3625700). Please note that this file is automatically added to the Project/Workpace. You should not add it manually. The default value is `nil`, which results in no configuration defined for the scheme - simulatedLocation: The simulated GPS location to use when running the app. Please note that the `.custom(gpxPath:)` case must refer to a valid GPX file in your project's resources. - enableGPUFrameCaptureMode: The Metal Frame Capture mode to use. e.g: .disabled If your target links to the Metal framework, Xcode enables GPU Frame Capture. You can disable it to test your app in best perfomance. #### Parameters | Name | Description | | ---- | ----------- | | language | language (e.g. “en”). | | region | region (e.g. “US”). | | storeKitConfigurationPath | The path of the . Please note that this file is automatically added to the Project/Workpace. You should not add it manually. The default value is `nil`, which results in no configuration defined for the scheme | | simulatedLocation | The simulated GPS location to use when running the app. Please note that the `.custom(gpxPath:)` case must refer to a valid GPX file in your project’s resources. | | enableGPUFrameCaptureMode | The Metal Frame Capture mode to use. e.g: .disabled If your target links to the Metal framework, Xcode enables GPU Frame Capture. You can disable it to test your app in best perfomance. | --- URL: "/generated/manifest/structs/RunAction" LLMS_URL: "/generated/manifest/structs/RunAction.md" --- **STRUCT** # `RunAction` **Contents** - [Properties](#properties) - `configuration` - `attachDebugger` - `customLLDBInitFile` - `preActions` - `postActions` - `executable` - `arguments` - `options` - `diagnosticsOptions` - `metalOptions` - `expandVariableFromTarget` - `launchStyle` - [Methods](#methods) - `runAction(configuration:attachDebugger:customLLDBInitFile:preActions:postActions:executable:arguments:options:diagnosticsOptions:metalOptions:expandVariableFromTarget:launchStyle:)` ```swift public struct RunAction: Equatable, Codable, Sendable ``` An action that runs the built products. It's initialized with the .runAction static method. ## Properties ### `configuration` ```swift public var configuration: ConfigurationName ``` Indicates the build configuration the product should run with. ### `attachDebugger` ```swift public var attachDebugger: Bool ``` Whether a debugger should be attached to the run process or not. ### `customLLDBInitFile` ```swift public var customLLDBInitFile: Path? ``` The path of custom lldbinit file. ### `preActions` ```swift public var preActions: [ExecutionAction] ``` A list of actions that are executed before starting the run process. ### `postActions` ```swift public var postActions: [ExecutionAction] ``` A list of actions that are executed after the run process. ### `executable` ```swift public var executable: TargetReference? ``` The name of the executable or target to run. ### `arguments` ```swift public var arguments: Arguments? ``` Command line arguments passed on launch and environment variables. ### `options` ```swift public var options: RunActionOptions ``` List of options to set to the action. ### `diagnosticsOptions` ```swift public var diagnosticsOptions: SchemeDiagnosticsOptions ``` List of diagnostics options to set to the action. ### `metalOptions` ```swift public var metalOptions: MetalOptions ``` List of metal options to set to the action ### `expandVariableFromTarget` ```swift public var expandVariableFromTarget: TargetReference? ``` A target that will be used to expand the variables defined inside Environment Variables definition (e.g. $SOURCE_ROOT) ### `launchStyle` ```swift public var launchStyle: LaunchStyle ``` The launch style of the action ## Methods ### `runAction(configuration:attachDebugger:customLLDBInitFile:preActions:postActions:executable:arguments:options:diagnosticsOptions:metalOptions:expandVariableFromTarget:launchStyle:)` ```swift public static func runAction( configuration: ConfigurationName = .debug, attachDebugger: Bool = true, customLLDBInitFile: Path? = nil, preActions: [ExecutionAction] = [], postActions: [ExecutionAction] = [], executable: TargetReference? = nil, arguments: Arguments? = nil, options: RunActionOptions = .options(), diagnosticsOptions: SchemeDiagnosticsOptions = .options(), metalOptions: MetalOptions = .options(), expandVariableFromTarget: TargetReference? = nil, launchStyle: LaunchStyle = .automatically ) -> RunAction ``` Returns a run action. - Parameters: - configuration: Indicates the build configuration the product should run with. - attachDebugger: Whether a debugger should be attached to the run process or not. - preActions: A list of actions that are executed before starting the run process. - postActions: A list of actions that are executed after the run process. - executable: The name of the executable or target to run. - arguments: Command line arguments passed on launch and environment variables. - options: List of options to set to the action. - diagnosticsOptions: List of diagnostics options to set to the action. - metalOptions: List of metal options to set to the action. - expandVariableFromTarget: A target that will be used to expand the variables defined inside Environment Variables definition (e.g. $SOURCE_ROOT). When nil, it does not expand any variables. - launchStyle: The launch style of the action - Returns: Run action. #### Parameters | Name | Description | | ---- | ----------- | | configuration | Indicates the build configuration the product should run with. | | attachDebugger | Whether a debugger should be attached to the run process or not. | | preActions | A list of actions that are executed before starting the run process. | | postActions | A list of actions that are executed after the run process. | | executable | The name of the executable or target to run. | | arguments | Command line arguments passed on launch and environment variables. | | options | List of options to set to the action. | | diagnosticsOptions | List of diagnostics options to set to the action. | | metalOptions | List of metal options to set to the action. | | expandVariableFromTarget | A target that will be used to expand the variables defined inside Environment Variables definition (e.g. $SOURCE_ROOT). When nil, it does not expand any variables. | | launchStyle | The launch style of the action | --- URL: "/generated/manifest/structs/ResourceSynthesizer" LLMS_URL: "/generated/manifest/structs/ResourceSynthesizer.md" --- **STRUCT** # `ResourceSynthesizer` **Contents** - [Properties](#properties) - `templateType` - `parser` - `parserOptions` - `extensions` - [Methods](#methods) - `strings(parserOptions:)` - `strings(plugin:parserOptions:)` - `assets(parserOptions:)` - `assets(plugin:parserOptions:)` - `fonts(parserOptions:)` - `fonts(plugin:parserOptions:)` - `plists(parserOptions:)` - `plists(plugin:parserOptions:)` - `coreData(plugin:parserOptions:)` - `coreData(parserOptions:)` - `interfaceBuilder(plugin:parserOptions:)` - `interfaceBuilder(parserOptions:)` - `json(plugin:parserOptions:)` - `json(parserOptions:)` - `yaml(plugin:parserOptions:)` - `yaml(parserOptions:)` - `files(plugin:parserOptions:extensions:)` - `files(parserOptions:extensions:)` - `custom(plugin:parser:parserOptions:extensions:resourceName:)` - `custom(name:parser:parserOptions:extensions:)` ```swift public struct ResourceSynthesizer: Codable, Equatable, Sendable ``` A resource synthesizer for given file extensions. For example to synthesize resource accessors for strings, you can use: - `.strings()` for tuist's default - `.strings(parserOptions: ["separator": "/"])` to use strings template with SwiftGen Parser Options - `.strings(plugin: "MyPlugin")` to use strings template from a plugin - `.strings(templatePath: "Templates/Strings.stencil")` to use strings template at a given path ## Properties ### `templateType` ```swift public var templateType: TemplateType ``` Templates can be of multiple types ### `parser` ```swift public var parser: Parser ``` ### `parserOptions` ```swift public var parserOptions: [String: Parser.Option] ``` ### `extensions` ```swift public var extensions: Set ``` ## Methods ### `strings(parserOptions:)` ```swift public static func strings(parserOptions: [String: Parser.Option] = [:]) -> Self ``` Default strings synthesizer defined in `Tuist/{ProjectName}` or tuist itself ### `strings(plugin:parserOptions:)` ```swift public static func strings( plugin: String, parserOptions: [String: Parser.Option] = [:] ) -> Self ``` Strings synthesizer defined in a plugin ### `assets(parserOptions:)` ```swift public static func assets(parserOptions: [String: Parser.Option] = [:]) -> Self ``` Default assets synthesizer defined in `Tuist/{ProjectName}` or tuist itself ### `assets(plugin:parserOptions:)` ```swift public static func assets( plugin: String, parserOptions: [String: Parser.Option] = [:] ) -> Self ``` Assets synthesizer defined in a plugin ### `fonts(parserOptions:)` ```swift public static func fonts(parserOptions: [String: Parser.Option] = [:]) -> Self ``` Default fonts synthesizer defined in `Tuist/{ProjectName}` or tuist itself ### `fonts(plugin:parserOptions:)` ```swift public static func fonts( plugin: String, parserOptions: [String: Parser.Option] = [:] ) -> Self ``` Fonts synthesizer defined in a plugin ### `plists(parserOptions:)` ```swift public static func plists(parserOptions: [String: Parser.Option] = [:]) -> Self ``` Default plists synthesizer defined in `Tuist/{ProjectName}` or tuist itself ### `plists(plugin:parserOptions:)` ```swift public static func plists( plugin: String, parserOptions: [String: Parser.Option] = [:] ) -> Self ``` Plists synthesizer defined in a plugin ### `coreData(plugin:parserOptions:)` ```swift public static func coreData( plugin: String, parserOptions: [String: Parser.Option] = [:] ) -> Self ``` CoreData synthesizer defined in a plugin ### `coreData(parserOptions:)` ```swift public static func coreData(parserOptions: [String: Parser.Option] = [:]) -> Self ``` Default CoreData synthesizer defined in `Tuist/{ProjectName}` ### `interfaceBuilder(plugin:parserOptions:)` ```swift public static func interfaceBuilder( plugin: String, parserOptions: [String: Parser.Option] = [:] ) -> Self ``` InterfaceBuilder synthesizer defined in a plugin ### `interfaceBuilder(parserOptions:)` ```swift public static func interfaceBuilder(parserOptions: [String: Parser.Option] = [:]) -> Self ``` InterfaceBuilder synthesizer with a template defined in `Tuist/{ProjectName}` ### `json(plugin:parserOptions:)` ```swift public static func json(plugin: String, parserOptions: [String: Parser.Option] = [:]) -> Self ``` JSON synthesizer defined in a plugin ### `json(parserOptions:)` ```swift public static func json(parserOptions: [String: Parser.Option] = [:]) -> Self ``` JSON synthesizer with a template defined in `Tuist/{ProjectName}` ### `yaml(plugin:parserOptions:)` ```swift public static func yaml(plugin: String, parserOptions: [String: Parser.Option] = [:]) -> Self ``` YAML synthesizer defined in a plugin ### `yaml(parserOptions:)` ```swift public static func yaml(parserOptions: [String: Parser.Option] = [:]) -> Self ``` CoreData synthesizer with a template defined in `Tuist/{ProjectName}` ### `files(plugin:parserOptions:extensions:)` ```swift public static func files( plugin: String, parserOptions: [String: Parser.Option] = [:], extensions: Set ) -> Self ``` Files synthesizer defined in a plugin ### `files(parserOptions:extensions:)` ```swift public static func files( parserOptions: [String: Parser.Option] = [:], extensions: Set ) -> Self ``` Files synthesizer with a template defined in `Tuist/{ProjectName}` ### `custom(plugin:parser:parserOptions:extensions:resourceName:)` ```swift public static func custom( plugin: String, parser: Parser, parserOptions: [String: Parser.Option] = [:], extensions: Set, resourceName: String ) -> Self ``` Custom synthesizer from a plugin - Parameters: - plugin: Name of a plugin where resource synthesizer template is located - parser: `Parser` to use for parsing the file to obtain its data - extensions: Set of extensions that should be parsed - resourceName: Name of the template file and the resulting `.swift` file #### Parameters | Name | Description | | ---- | ----------- | | plugin | Name of a plugin where resource synthesizer template is located | | parser | `Parser` to use for parsing the file to obtain its data | | extensions | Set of extensions that should be parsed | | resourceName | Name of the template file and the resulting `.swift` file | ### `custom(name:parser:parserOptions:extensions:)` ```swift public static func custom( name: String, parser: Parser, parserOptions: [String: Parser.Option] = [:], extensions: Set ) -> Self ``` Custom local synthesizer defined `Tuist/ResourceSynthesizers/{name}.stencil` - Parameters: - name: Name of synthesizer - parser: `Parser` to use for parsing the file to obtain its data - extensions: Set of extensions that should be parsed #### Parameters | Name | Description | | ---- | ----------- | | name | Name of synthesizer | | parser | `Parser` to use for parsing the file to obtain its data | | extensions | Set of extensions that should be parsed | --- URL: "/generated/manifest/structs/ResourceFileElements" LLMS_URL: "/generated/manifest/structs/ResourceFileElements.md" --- **STRUCT** # `ResourceFileElements` **Contents** - [Properties](#properties) - `resources` - `privacyManifest` - [Methods](#methods) - `resources(_:privacyManifest:)` ```swift public struct ResourceFileElements: Codable, Equatable, Sendable ``` A collection of resource file. ## Properties ### `resources` ```swift public var resources: [ResourceFileElement] ``` List of resource file elements ### `privacyManifest` ```swift public var privacyManifest: PrivacyManifest? ``` Define your apps privacy manifest ## Methods ### `resources(_:privacyManifest:)` ```swift public static func resources(_ resources: [ResourceFileElement], privacyManifest: PrivacyManifest? = nil) -> Self ``` --- URL: "/generated/manifest/structs/Project" LLMS_URL: "/generated/manifest/structs/Project.md" --- **STRUCT** # `Project` **Contents** - [Properties](#properties) - `name` - `organizationName` - `classPrefix` - `options` - `packages` - `targets` - `schemes` - `settings` - `fileHeaderTemplate` - `additionalFiles` - `resourceSynthesizers` - `containsExternalDependencies` - [Methods](#methods) - `init(name:organizationName:classPrefix:options:packages:settings:targets:schemes:fileHeaderTemplate:additionalFiles:resourceSynthesizers:)` ```swift public struct Project: Codable, Equatable, Sendable ``` A project representation. A project manifest needs to be defined in a `Project.swift` manifest file. Manifests need to import the framework ProjectDescription which contains all the classes and enums that are available for you to describe your projects. The snippet below shows an example project manifest: ```swift import ProjectDescription let project = Project( name: "MyProject", organizationName: "MyOrg", targets: [ Target( name: "App", destinations: .iOS, product: .app, bundleId: "io.tuist.App", infoPlist: "Config/App-Info.plist", sources: ["Sources/**"], resources: [ "Resources/**", .folderReference(path: "Stubs"), .folderReference(path: "ODR", tags: ["odr_tag"]) ], headers: .headers( public: ["Sources/public/A/**", "Sources/public/B/**"], private: "Sources/private/**", project: ["Sources/project/A/**", "Sources/project/B/**"] ), dependencies: [ .project(target: "Framework1", path: "../Framework1"), .project(target: "Framework2", path: "../Framework2") ] ) ], schemes: [ Scheme( name: "App-Debug", shared: true, buildAction: .buildAction(targets: ["App"]), testAction: .targets(["AppTests"]), runAction: .runAction(executable: "App") ), Scheme( name: "App-Release", shared: true, buildAction: .buildAction(targets: ["App"]), runAction: .runAction(executable: "App") ) ], additionalFiles: [ "Dangerfile.swift", "Documentation/**", .folderReference(path: "Website") ] ) ``` ## Properties ### `name` ```swift public let name: String ``` The name of the project. Also, the file name of the generated Xcode project. ### `organizationName` ```swift public let organizationName: String? ``` The name of the organization used by Xcode as copyright. ### `classPrefix` ```swift public let classPrefix: String? ``` The prefix for class files Xcode generates when you create a project or class file. ### `options` ```swift public let options: Options ``` The project options. ### `packages` ```swift public let packages: [Package] ``` The Swift Packages used by the project. ### `targets` ```swift public let targets: [Target] ``` The targets of the project. ### `schemes` ```swift public let schemes: [Scheme] ``` The custom schemes for the project. Default schemes for each target are generated by default. ### `settings` ```swift public let settings: Settings? ``` The build settings and configuration for the project. ### `fileHeaderTemplate` ```swift public let fileHeaderTemplate: FileHeaderTemplate? ``` The custom file header template for Xcode built-in file templates. ### `additionalFiles` ```swift public let additionalFiles: [FileElement] ``` The additional files for the project. For target's additional files, see ``Target/additionalFiles``. ### `resourceSynthesizers` ```swift public let resourceSynthesizers: [ResourceSynthesizer] ``` The resource synthesizers for the project to generate accessors for resources. ### `containsExternalDependencies` ```swift public var containsExternalDependencies: Bool ``` The project contains targets that depend on external dependencies ## Methods ### `init(name:organizationName:classPrefix:options:packages:settings:targets:schemes:fileHeaderTemplate:additionalFiles:resourceSynthesizers:)` ```swift public init( name: String, organizationName: String? = nil, classPrefix: String? = nil, options: Options = .options(), packages: [Package] = [], settings: Settings? = nil, targets: [Target] = [], schemes: [Scheme] = [], fileHeaderTemplate: FileHeaderTemplate? = nil, additionalFiles: [FileElement] = [], resourceSynthesizers: [ResourceSynthesizer] = .default ) ``` --- URL: "/generated/manifest/structs/Project.Options" LLMS_URL: "/generated/manifest/structs/Project.Options.md" --- **STRUCT** # `Project.Options` **Contents** - [Properties](#properties) - `automaticSchemesOptions` - `defaultKnownRegions` - `developmentRegion` - `disableBundleAccessors` - `disableShowEnvironmentVarsInScriptPhases` - `disableSynthesizedResourceAccessors` - `textSettings` - `xcodeProjectName` - [Methods](#methods) - `options(automaticSchemesOptions:defaultKnownRegions:developmentRegion:disableBundleAccessors:disableShowEnvironmentVarsInScriptPhases:disableSynthesizedResourceAccessors:textSettings:xcodeProjectName:)` ```swift public struct Options: Codable, Equatable, Sendable ``` Options to configure a project. ## Properties ### `automaticSchemesOptions` ```swift public var automaticSchemesOptions: AutomaticSchemesOptions ``` Configures automatic target schemes generation. ### `defaultKnownRegions` ```swift public var defaultKnownRegions: [String]? ``` Configures the default known regions ### `developmentRegion` ```swift public var developmentRegion: String? ``` Configures the development region. ### `disableBundleAccessors` ```swift public var disableBundleAccessors: Bool ``` Disables generating Bundle accessors. ### `disableShowEnvironmentVarsInScriptPhases` ```swift public var disableShowEnvironmentVarsInScriptPhases: Bool ``` Suppress logging of environment in Run Script build phases. ### `disableSynthesizedResourceAccessors` ```swift public var disableSynthesizedResourceAccessors: Bool ``` Disable synthesized resource accessors. ### `textSettings` ```swift public var textSettings: TextSettings ``` Configures text settings. ### `xcodeProjectName` ```swift public var xcodeProjectName: String? ``` Configures the name of the generated .xcodeproj. ## Methods ### `options(automaticSchemesOptions:defaultKnownRegions:developmentRegion:disableBundleAccessors:disableShowEnvironmentVarsInScriptPhases:disableSynthesizedResourceAccessors:textSettings:xcodeProjectName:)` ```swift public static func options( automaticSchemesOptions: AutomaticSchemesOptions = .enabled(), defaultKnownRegions: [String]? = nil, developmentRegion: String? = nil, disableBundleAccessors: Bool = false, disableShowEnvironmentVarsInScriptPhases: Bool = false, disableSynthesizedResourceAccessors: Bool = false, textSettings: TextSettings = .textSettings(), xcodeProjectName: String? = nil ) -> Self ``` --- URL: "/generated/manifest/structs/Project.Options.TextSettings" LLMS_URL: "/generated/manifest/structs/Project.Options.TextSettings.md" --- **STRUCT** # `Project.Options.TextSettings` **Contents** - [Properties](#properties) - `usesTabs` - `indentWidth` - `tabWidth` - `wrapsLines` - [Methods](#methods) - `textSettings(usesTabs:indentWidth:tabWidth:wrapsLines:)` ```swift public struct TextSettings: Codable, Equatable, Sendable ``` The text settings options ## Properties ### `usesTabs` ```swift public var usesTabs: Bool? ``` Whether tabs should be used instead of spaces ### `indentWidth` ```swift public var indentWidth: UInt? ``` The width of space indent ### `tabWidth` ```swift public var tabWidth: UInt? ``` The width of tab indent ### `wrapsLines` ```swift public var wrapsLines: Bool? ``` Whether lines should be wrapped or not ## Methods ### `textSettings(usesTabs:indentWidth:tabWidth:wrapsLines:)` ```swift public static func textSettings( usesTabs: Bool? = nil, indentWidth: UInt? = nil, tabWidth: UInt? = nil, wrapsLines: Bool? = nil ) -> Self ``` --- URL: "/generated/manifest/structs/ProfileAction" LLMS_URL: "/generated/manifest/structs/ProfileAction.md" --- **STRUCT** # `ProfileAction` **Contents** - [Properties](#properties) - `configuration` - `preActions` - `postActions` - `executable` - `arguments` - [Methods](#methods) - `profileAction(configuration:preActions:postActions:executable:arguments:)` ```swift public struct ProfileAction: Equatable, Codable, Sendable ``` An action that profiles the built products. It's initialized with the `.profileAction` static method ## Properties ### `configuration` ```swift public var configuration: ConfigurationName ``` Indicates the build configuration the product should be profiled with. ### `preActions` ```swift public var preActions: [ExecutionAction] ``` A list of actions that are executed before starting the profile process. ### `postActions` ```swift public var postActions: [ExecutionAction] ``` A list of actions that are executed after the profile process. ### `executable` ```swift public var executable: TargetReference? ``` The name of the executable or target to profile. ### `arguments` ```swift public var arguments: Arguments? ``` Command line arguments passed on launch and environment variables. ## Methods ### `profileAction(configuration:preActions:postActions:executable:arguments:)` ```swift public static func profileAction( configuration: ConfigurationName = .release, preActions: [ExecutionAction] = [], postActions: [ExecutionAction] = [], executable: TargetReference? = nil, arguments: Arguments? = nil ) -> ProfileAction ``` Returns a profile action. - Parameters: - configuration: Indicates the build configuration the product should be profiled with. - preActions: A list of actions that are executed before starting the profile process. - postActions: A list of actions that are executed after the profile process. - executable: The name of the executable or target to profile. - arguments: Command line arguments passed on launch and environment variables. - Returns: Initialized profile action. #### Parameters | Name | Description | | ---- | ----------- | | configuration | Indicates the build configuration the product should be profiled with. | | preActions | A list of actions that are executed before starting the profile process. | | postActions | A list of actions that are executed after the profile process. | | executable | The name of the executable or target to profile. | | arguments | Command line arguments passed on launch and environment variables. | --- URL: "/generated/manifest/structs/PrivacyManifest" LLMS_URL: "/generated/manifest/structs/PrivacyManifest.md" --- **STRUCT** # `PrivacyManifest` **Contents** - [Properties](#properties) - `tracking` - `trackingDomains` - `collectedDataTypes` - `accessedApiTypes` - [Methods](#methods) - `privacyManifest(tracking:trackingDomains:collectedDataTypes:accessedApiTypes:)` ```swift public struct PrivacyManifest: Codable, Equatable, Sendable ``` Describe the data your app or third-party SDK collects and the reasons required APIs it uses. ## Properties ### `tracking` ```swift public var tracking: Bool ``` A Boolean that indicates whether your app or third-party SDK uses data for tracking as defined under the App Tracking Transparency framework. For more information, see [User Privacy and Data Use](https://developer.apple.com/app-store/user-privacy-and-data-use/). ### `trackingDomains` ```swift public var trackingDomains: [String] ``` An array of strings that lists the internet domains your app or third-party SDK connects to that engage in tracking. If the user has not granted tracking permission through the App Tracking Transparency framework, network requests to these domains fail and your app receives an error. If you set `tracking` to true then you need to provide at least one internet domain in NSPrivacyTrackingDomains; otherwise, you can provide zero or more domains. ### `collectedDataTypes` ```swift public var collectedDataTypes: [[String: Plist.Value]] ``` An array of dictionaries that describes the data types your app or third-party SDK collects. For information on the keys and values to use in the dictionaries, see [Describing data use in privacy manifests](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_data_use_in_privacy_manifests). ### `accessedApiTypes` ```swift public var accessedApiTypes: [[String: Plist.Value]] ``` An array of dictionaries that describe the API types your app or third-party SDK accesses that have been designated as APIs that require reasons to access. For information on the keys and values to use in the dictionaries, see [Describing use of required reason API](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api). ## Methods ### `privacyManifest(tracking:trackingDomains:collectedDataTypes:accessedApiTypes:)` ```swift public static func privacyManifest( tracking: Bool, trackingDomains: [String], collectedDataTypes: [[String: Plist.Value]], accessedApiTypes: [[String: Plist.Value]] ) -> Self ``` Returns a PrivacyManifest. - Parameter tracking: A Boolean that indicates whether your app or third-party SDK uses data for tracking. - Parameter trackingDomains: An array of strings that lists the internet domains your app or third-party SDK connects to that engage in tracking. - Parameter collectedDataTypes: An array of dictionaries that describes the data types your app or third-party SDK collects. - Parameter accessedApiTypes: An array of dictionaries that describe the API types your app or third-party SDK accesses that have been designated as APIs that require reasons to access. - Returns: PrivacyManifest. #### Parameters | Name | Description | | ---- | ----------- | | tracking | A Boolean that indicates whether your app or third-party SDK uses data for tracking. | | trackingDomains | An array of strings that lists the internet domains your app or third-party SDK connects to that engage in tracking. | | collectedDataTypes | An array of dictionaries that describes the data types your app or third-party SDK collects. | | accessedApiTypes | An array of dictionaries that describe the API types your app or third-party SDK accesses that have been designated as APIs that require reasons to access. | --- URL: "/generated/manifest/structs/PluginLocation" LLMS_URL: "/generated/manifest/structs/PluginLocation.md" --- **STRUCT** # `PluginLocation` **Contents** - [Properties](#properties) - `type` - [Methods](#methods) - `local(path:)` - `git(url:tag:directory:releaseUrl:)` - `git(url:sha:directory:)` ```swift public struct PluginLocation: Codable, Equatable, Sendable ``` A location to a plugin, either local or remote. ## Properties ### `type` ```swift public var type: LocationType ``` The type of location `local` or `git`. ## Methods ### `local(path:)` ```swift public static func local(path: Path) -> Self ``` A `Path` to a directory containing a `Plugin` manifest. Example: ``` .local(path: "/User/local/bin") ``` ### `git(url:tag:directory:releaseUrl:)` ```swift public static func git(url: String, tag: String, directory: String? = nil, releaseUrl: String? = nil) -> Self ``` A `URL` to a `git` repository pointing at a `tag`. You can also specify a custom directory in case the plugin is not located at the root of the repository. You can also specify a custom release URL from where the plugin binary should be downloaded. If not specified, it defaults to the GitHub release URL. Note that the URL should be publicly reachable. Example: ``` .git(url: "https://git/plugin.git", tag: "1.0.0", directory: "PluginDirectory") ``` ### `git(url:sha:directory:)` ```swift public static func git(url: String, sha: String, directory: String? = nil) -> Self ``` A `URL` to a `git` repository pointing at a commit `sha`. You can also specify a custom directory in case the plugin is not located at the root of the repository. Example: ``` .git(url: "https://git/plugin.git", sha: "d06b4b3d") ``` --- URL: "/generated/manifest/structs/Plugin" LLMS_URL: "/generated/manifest/structs/Plugin.md" --- **STRUCT** # `Plugin` **Contents** - [Properties](#properties) - `name` - [Methods](#methods) - `init(name:)` ```swift public struct Plugin: Codable, Equatable, Sendable ``` A plugin representation. Supported plugins include: - ProjectDescriptionHelpers - These are plugins designed to be usable by any other manifest excluding `Config` and `Plugin`. - The source files for these helpers must live under a ProjectDescriptionHelpers directory in the location where `Plugin` manifest lives. ## Properties ### `name` ```swift public let name: String ``` The name of the `Plugin`. ## Methods ### `init(name:)` ```swift public init(name: String) ``` Creates a new plugin. - Parameters: - name: The name of the plugin. #### Parameters | Name | Description | | ---- | ----------- | | name | The name of the plugin. | --- URL: "/generated/manifest/structs/PlatformCondition" LLMS_URL: "/generated/manifest/structs/PlatformCondition.md" --- **STRUCT** # `PlatformCondition` **Contents** - [Properties](#properties) - `platformFilters` - [Methods](#methods) - `when(_:)` ```swift public struct PlatformCondition: Codable, Hashable, Equatable, Sendable ``` A condition applied to an "entity" allowing it to only be used in certain circumstances ## Properties ### `platformFilters` ```swift public let platformFilters: Set ``` ## Methods ### `when(_:)` ```swift public static func when(_ platformFilters: Set) -> PlatformCondition? ``` Creates a condition using the specified set of filters. - Parameter platformFilters: filters to define which platforms this condition supports - Returns: a `Condition` with the given set of filters or `nil` if empty. #### Parameters | Name | Description | | ---- | ----------- | | platformFilters | filters to define which platforms this condition supports | --- URL: "/generated/manifest/structs/Path" LLMS_URL: "/generated/manifest/structs/Path.md" --- **STRUCT** # `Path` **Contents** - [Properties](#properties) - `type` - `pathString` - `callerPath` - [Methods](#methods) - `path(_:)` - `relativeToCurrentFile(_:callerPath:)` - `relativeToManifest(_:)` - `relativeToRoot(_:)` - `init(stringLiteral:)` ```swift public struct Path: ExpressibleByStringInterpolation, Codable, Hashable, Sendable ``` A path represents to a file, directory, or a group of files represented by a glob expression. Paths can be relative and absolute. We discourage using absolute paths because they create a dependency with the environment where they are defined. ## Properties ### `type` ```swift public var type: PathType ``` ### `pathString` ```swift public var pathString: String ``` ### `callerPath` ```swift public var callerPath: String? ``` ## Methods ### `path(_:)` ```swift public static func path(_ path: String) -> Self ``` Default PathType is `.relativeToManifest` ### `relativeToCurrentFile(_:callerPath:)` ```swift public static func relativeToCurrentFile(_ pathString: String, callerPath: StaticString = #file) -> Path ``` Initialize a path that is relative to the file that defines the path. ### `relativeToManifest(_:)` ```swift public static func relativeToManifest(_ pathString: String) -> Path ``` Initialize a path that is relative to the directory that contains the manifest file being loaded, for example the directory that contains the Project.swift file. ### `relativeToRoot(_:)` ```swift public static func relativeToRoot(_ pathString: String) -> Path ``` Initialize a path that is relative to the closest directory that contains a Tuist or a .git directory. ### `init(stringLiteral:)` ```swift public init(stringLiteral: String) ``` Initializer uses `.relativeToRoot` if path starts with `//` otherwise it is `.relativeToManifest` by default --- URL: "/generated/manifest/structs/PackageSettings" LLMS_URL: "/generated/manifest/structs/PackageSettings.md" --- **STRUCT** # `PackageSettings` **Contents** - [Properties](#properties) - `productTypes` - `productDestinations` - `baseSettings` - `targetSettings` - `projectOptions` - [Methods](#methods) - `init(productTypes:productDestinations:baseSettings:targetSettings:projectOptions:)` - `init(productTypes:productDestinations:baseSettings:targetSettings:projectOptions:)` ```swift public struct PackageSettings: Codable, Equatable, Sendable ``` A custom Swift Package Manager configuration ```swift // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription import ProjectDescriptionHelpers let packageSettings = PackageSettings( productTypes: [ "Alamofire": .framework, // default is .staticFramework ] ) #endif let package = Package( name: "PackageName", dependencies: [ .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), ] ) ``` ## Properties ### `productTypes` ```swift public var productTypes: [String: Product] ``` The custom `Product` type to be used for SPM targets. ### `productDestinations` ```swift public var productDestinations: [String: Destinations] ``` Custom product destinations where key of the dictionary is the name of the SPM product and the value contains the supported destinations. **Note**: This setting should only be used when using Tuist for SPM package projects, _not_ for your external dependencies. SPM implicitly always supports all platforms, but some commands like `tuist cache` depend on destinations being explicit. If a product does not support all destinations, you can use `productDestinations` to make the supported destinations explicit. ### `baseSettings` ```swift public var baseSettings: Settings ``` ### `targetSettings` ```swift public var targetSettings: [String: Settings] ``` ### `projectOptions` ```swift public var projectOptions: [String: Project.Options] ``` Custom project configurations to be used for projects generated from SwiftPackageManager. ## Methods ### `init(productTypes:productDestinations:baseSettings:targetSettings:projectOptions:)` ```swift public init( productTypes: [String: Product] = [:], productDestinations: [String: Destinations] = [:], baseSettings: Settings = .settings(), targetSettings: [String: Settings] = [:], projectOptions: [String: Project.Options] = [:] ) ``` Creates `PackageSettings` instance for custom Swift Package Manager configuration. - Parameters: - productTypes: The custom `Product` types to be used for SPM targets. - productDestinations: Custom destinations to be used for SPM products. - baseSettings: Additional settings to be added to targets generated from SwiftPackageManager. - targetSettings: Additional settings to be added to targets generated from SwiftPackageManager. - projectOptions: Custom project configurations to be used for projects generated from SwiftPackageManager. #### Parameters | Name | Description | | ---- | ----------- | | productTypes | The custom `Product` types to be used for SPM targets. | | productDestinations | Custom destinations to be used for SPM products. | | baseSettings | Additional settings to be added to targets generated from SwiftPackageManager. | | targetSettings | Additional settings to be added to targets generated from SwiftPackageManager. | | projectOptions | Custom project configurations to be used for projects generated from SwiftPackageManager. | ### `init(productTypes:productDestinations:baseSettings:targetSettings:projectOptions:)` ```swift public init( productTypes: [String: Product] = [:], productDestinations: [String: Destinations] = [:], baseSettings: Settings = .settings(), targetSettings: [String: SettingsDictionary], projectOptions: [String: Project.Options] = [:] ) ``` Creates `PackageSettings` instance for custom Swift Package Manager configuration. - Parameters: - productTypes: The custom `Product` types to be used for SPM targets. - productDestinations: Custom destinations to be used for SPM products. - baseSettings: Additional settings to be added to targets generated from SwiftPackageManager. - targetSettings: Additional settings to be added to targets generated from SwiftPackageManager. - projectOptions: Custom project configurations to be used for projects generated from SwiftPackageManager. #### Parameters | Name | Description | | ---- | ----------- | | productTypes | The custom `Product` types to be used for SPM targets. | | productDestinations | Custom destinations to be used for SPM products. | | baseSettings | Additional settings to be added to targets generated from SwiftPackageManager. | | targetSettings | Additional settings to be added to targets generated from SwiftPackageManager. | | projectOptions | Custom project configurations to be used for projects generated from SwiftPackageManager. | --- URL: "/generated/manifest/structs/OnDemandResourcesTags" LLMS_URL: "/generated/manifest/structs/OnDemandResourcesTags.md" --- **STRUCT** # `OnDemandResourcesTags` **Contents** - [Properties](#properties) - `initialInstall` - `prefetchOrder` - [Methods](#methods) - `tags(initialInstall:prefetchOrder:)` ```swift public struct OnDemandResourcesTags: Codable, Equatable, Sendable ``` On-demand resources tags associated with Initial Install and Prefetched Order categories ## Properties ### `initialInstall` ```swift public let initialInstall: [String]? ``` Initial install tags associated with on demand resources ### `prefetchOrder` ```swift public let prefetchOrder: [String]? ``` Prefetched tag order associated with on demand resources ## Methods ### `tags(initialInstall:prefetchOrder:)` ```swift public static func tags(initialInstall: [String]?, prefetchOrder: [String]?) -> Self ``` Returns OnDemandResourcesTags. - Parameter initialInstall: An array of strings that lists the tags assosiated with the Initial install tags category. - Parameter prefetchOrder: An array of strings that lists the tags associated with the Prefetch tag order category. - Returns: OnDemandResourcesTags. #### Parameters | Name | Description | | ---- | ----------- | | initialInstall | An array of strings that lists the tags assosiated with the Initial install tags category. | | prefetchOrder | An array of strings that lists the tags associated with the Prefetch tag order category. | --- URL: "/generated/manifest/structs/MetalOptions" LLMS_URL: "/generated/manifest/structs/MetalOptions.md" --- **STRUCT** # `MetalOptions` **Contents** - [Properties](#properties) - `apiValidation` - `shaderValidation` - `showGraphicsOverview` - `logGraphicsOverview` - [Methods](#methods) - `options(apiValidation:shaderValidation:showGraphicsOverview:logGraphicsOverview:)` ```swift public struct MetalOptions: Equatable, Codable, Sendable ``` Options to configure scheme metal options for run and test actions. ## Properties ### `apiValidation` ```swift public var apiValidation: Bool ``` API Validation ### `shaderValidation` ```swift public var shaderValidation: Bool ``` Shader Validation ### `showGraphicsOverview` ```swift public var showGraphicsOverview: Bool ``` Shows graphics overview ### `logGraphicsOverview` ```swift public var logGraphicsOverview: Bool ``` Log graphics overview ## Methods ### `options(apiValidation:shaderValidation:showGraphicsOverview:logGraphicsOverview:)` ```swift public static func options( apiValidation: Bool = true, shaderValidation: Bool = false, showGraphicsOverview: Bool = false, logGraphicsOverview: Bool = false ) -> MetalOptions ``` Creates a `MetalOptions` instance - Parameters: - apiValidation: Specifies whether API validation is enabled. - shaderValidation: Specifies whether shader validation is enabled. - showGraphicsOverview: Specifies whether to show the graphics overview. - logGraphicsOverview: Specifies whether to log the graphics overview. #### Parameters | Name | Description | | ---- | ----------- | | apiValidation | Specifies whether API validation is enabled. | | shaderValidation | Specifies whether shader validation is enabled. | | showGraphicsOverview | Specifies whether to show the graphics overview. | | logGraphicsOverview | Specifies whether to log the graphics overview. | --- URL: "/generated/manifest/structs/LaunchArgument" LLMS_URL: "/generated/manifest/structs/LaunchArgument.md" --- **STRUCT** # `LaunchArgument` **Contents** - [Properties](#properties) - `name` - `isEnabled` - [Methods](#methods) - `launchArgument(name:isEnabled:)` ```swift public struct LaunchArgument: Equatable, Codable, Sendable ``` A launch argument, passed when running a scheme. ## Properties ### `name` ```swift public var name: String ``` Name of argument ### `isEnabled` ```swift public var isEnabled: Bool ``` If enabled then argument is marked as active ## Methods ### `launchArgument(name:isEnabled:)` ```swift public static func launchArgument(name: String, isEnabled: Bool) -> Self ``` Create new launch argument - Parameters: - name: Name of argument - isEnabled: If enabled then argument is marked as active #### Parameters | Name | Description | | ---- | ----------- | | name | Name of argument | | isEnabled | If enabled then argument is marked as active | --- URL: "/generated/manifest/structs/Headers" LLMS_URL: "/generated/manifest/structs/Headers.md" --- **STRUCT** # `Headers` **Contents** - [Properties](#properties) - `umbrellaHeader` - `public` - `private` - `project` - `exclusionRule` - [Methods](#methods) - `headers(public:private:project:exclusionRule:)` - `allHeaders(from:umbrella:private:)` - `onlyHeaders(from:umbrella:private:)` ```swift public struct Headers: Codable, Equatable, Sendable ``` A group of public, private and project headers. ## Properties ### `umbrellaHeader` ```swift public var umbrellaHeader: Path? ``` Path to an umbrella header, which will be used to get list of public headers. ### `public` ```swift public var `public`: FileList? ``` Relative glob pattern that points to the public headers. ### `private` ```swift public var `private`: FileList? ``` Relative glob pattern that points to the private headers. ### `project` ```swift public var project: FileList? ``` Relative glob pattern that points to the project headers. ### `exclusionRule` ```swift public var exclusionRule: AutomaticExclusionRule ``` Rule, which determines how to resolve found duplicates in public/private/project scopes ## Methods ### `headers(public:private:project:exclusionRule:)` ```swift public static func headers( public: FileList? = nil, private: FileList? = nil, project: FileList? = nil, exclusionRule: AutomaticExclusionRule = .projectExcludesPrivateAndPublic ) -> Headers ``` ### `allHeaders(from:umbrella:private:)` ```swift public static func allHeaders( from list: FileList, umbrella: Path, private privateHeaders: FileList? = nil ) -> Headers ``` Headers from the file list are included as: - `public`, if the header is present in the umbrella header - `private`, if the header is present in the `private` list - `project`, otherwise - Parameters: - from: File list, which contains `public` and `project` headers - umbrella: File path to the umbrella header - private: File list, which contains `private` headers #### Parameters | Name | Description | | ---- | ----------- | | from | File list, which contains `public` and `project` headers | | umbrella | File path to the umbrella header | | private | File list, which contains `private` headers | ### `onlyHeaders(from:umbrella:private:)` ```swift public static func onlyHeaders( from list: FileList, umbrella: Path, private privateHeaders: FileList? = nil ) -> Headers ``` Headers from the file list are included as: - `public`, if the header is present in the umbrella header - `private`, if the header is present in the `private` list - not included, otherwise - Parameters: - from: File list, which contains `public` and `project` headers - umbrella: File path to the umbrella header - private: File list, which contains `private` headers #### Parameters | Name | Description | | ---- | ----------- | | from | File list, which contains `public` and `project` headers | | umbrella | File path to the umbrella header | | private | File list, which contains `private` headers | --- URL: "/generated/manifest/structs/FileListGlob" LLMS_URL: "/generated/manifest/structs/FileListGlob.md" --- **STRUCT** # `FileListGlob` **Contents** - [Properties](#properties) - `glob` - `excluding` - [Methods](#methods) - `glob(_:excluding:)` - `glob(_:excluding:)` ```swift public struct FileListGlob: Codable, Equatable, Sendable ``` A glob pattern that refers to files. ## Properties ### `glob` ```swift public var glob: Path ``` The path with a glob pattern. ### `excluding` ```swift public var excluding: [Path] ``` The excluding paths. ## Methods ### `glob(_:excluding:)` ```swift public static func glob( _ glob: Path, excluding: [Path] = [] ) -> FileListGlob ``` Returns a generic file list glob. - Parameters: - glob: The path with a glob pattern. - excluding: The excluding paths. #### Parameters | Name | Description | | ---- | ----------- | | glob | The path with a glob pattern. | | excluding | The excluding paths. | ### `glob(_:excluding:)` ```swift public static func glob( _ glob: Path, excluding: Path? ) -> FileListGlob ``` Returns a file list glob with an optional excluding path. --- URL: "/generated/manifest/structs/FileList" LLMS_URL: "/generated/manifest/structs/FileList.md" --- **STRUCT** # `FileList` **Contents** - [Properties](#properties) - `globs` - [Methods](#methods) - `list(_:)` ```swift public struct FileList: Codable, Equatable, Sendable ``` A collection of file globs. The list of files can be initialized with a string that represents the glob pattern, or an array of strings, which represents a list of glob patterns. ## Properties ### `globs` ```swift public let globs: [FileListGlob] ``` Glob pattern to the files. ## Methods ### `list(_:)` ```swift public static func list(_ globs: [FileListGlob]) -> FileList ``` Creates a file list from a collection of glob patterns. - glob: Relative glob pattern. - excluding: Relative glob patterns for excluded files. --- URL: "/generated/manifest/structs/ExecutionAction" LLMS_URL: "/generated/manifest/structs/ExecutionAction.md" --- **STRUCT** # `ExecutionAction` **Contents** - [Properties](#properties) - `title` - `scriptText` - `target` - `shellPath` - [Methods](#methods) - `executionAction(title:scriptText:target:shellPath:)` ```swift public struct ExecutionAction: Equatable, Codable, Sendable ``` An action that can be executed as part of another action for pre or post execution. ## Properties ### `title` ```swift public var title: String ``` ### `scriptText` ```swift public var scriptText: String ``` ### `target` ```swift public var target: TargetReference? ``` ### `shellPath` ```swift public var shellPath: String? ``` The path to the shell which shall execute this script. if it is nil, Xcode will use default value. ## Methods ### `executionAction(title:scriptText:target:shellPath:)` ```swift public static func executionAction( title: String = "Run Script", scriptText: String, target: TargetReference? = nil, shellPath: String? = nil ) -> Self ``` --- URL: "/generated/manifest/structs/EnvironmentVariable" LLMS_URL: "/generated/manifest/structs/EnvironmentVariable.md" --- **STRUCT** # `EnvironmentVariable` **Contents** - [Properties](#properties) - `value` - `isEnabled` - [Methods](#methods) - `environmentVariable(value:isEnabled:)` - `init(stringLiteral:)` ```swift public struct EnvironmentVariable: Equatable, Codable, Hashable, ExpressibleByStringLiteral, Sendable ``` It represents an environment variable that is passed when running a scheme's action ## Properties ### `value` ```swift public var value: String ``` The value of the environment variable ### `isEnabled` ```swift public var isEnabled: Bool ``` Whether the variable is enabled or not ## Methods ### `environmentVariable(value:isEnabled:)` ```swift public static func environmentVariable(value: String, isEnabled: Bool) -> Self ``` ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | --- URL: "/generated/manifest/structs/DeploymentTargets" LLMS_URL: "/generated/manifest/structs/DeploymentTargets.md" --- **STRUCT** # `DeploymentTargets` **Contents** - [Properties](#properties) - `iOS` - `macOS` - `watchOS` - `tvOS` - `visionOS` - [Methods](#methods) - `multiplatform(iOS:macOS:watchOS:tvOS:visionOS:)` - `iOS(_:)` - `macOS(_:)` - `watchOS(_:)` - `tvOS(_:)` - `visionOS(_:)` ```swift public struct DeploymentTargets: Hashable, Codable, Sendable ``` A struct representing the minimum deployment versions for each platform. ## Properties ### `iOS` ```swift public var iOS: String? ``` Minimum deployment version for iOS ### `macOS` ```swift public var macOS: String? ``` Minimum deployment version for macOS ### `watchOS` ```swift public var watchOS: String? ``` Minimum deployment version for watchOS ### `tvOS` ```swift public var tvOS: String? ``` Minimum deployment version for tvOS ### `visionOS` ```swift public var visionOS: String? ``` Minimum deployment version for visionOS ## Methods ### `multiplatform(iOS:macOS:watchOS:tvOS:visionOS:)` ```swift public static func multiplatform( iOS: String? = nil, macOS: String? = nil, watchOS: String? = nil, tvOS: String? = nil, visionOS: String? = nil ) -> Self ``` Multiplatform deployment target ### `iOS(_:)` ```swift public static func iOS(_ version: String) -> DeploymentTargets ``` Convenience method for `iOS` only minimum version ### `macOS(_:)` ```swift public static func macOS(_ version: String) -> DeploymentTargets ``` Convenience method for `macOS` only minimum version ### `watchOS(_:)` ```swift public static func watchOS(_ version: String) -> DeploymentTargets ``` Convenience method for `watchOS` only minimum version ### `tvOS(_:)` ```swift public static func tvOS(_ version: String) -> DeploymentTargets ``` Convenience method for `tvOS` only minimum version ### `visionOS(_:)` ```swift public static func visionOS(_ version: String) -> DeploymentTargets ``` Convenience method for `visionOS` only minimum version --- URL: "/generated/manifest/structs/CoreDataModel" LLMS_URL: "/generated/manifest/structs/CoreDataModel.md" --- **STRUCT** # `CoreDataModel` **Contents** - [Properties](#properties) - `path` - `currentVersion` - [Methods](#methods) - `coreDataModel(_:currentVersion:)` ```swift public struct CoreDataModel: Codable, Equatable, Sendable ``` A Core Data model. ## Properties ### `path` ```swift public var path: Path ``` Relative path to the model. ### `currentVersion` ```swift public var currentVersion: String? ``` Optional Current version (with or without extension) ## Methods ### `coreDataModel(_:currentVersion:)` ```swift public static func coreDataModel( _ path: Path, currentVersion: String? = nil ) -> Self ``` Creates a Core Data model from a path. - Parameters: - path: relative path to the Core Data model. - currentVersion: optional current version name (with or without the extension) By providing nil, it will try to read it from the .xccurrentversion file. #### Parameters | Name | Description | | ---- | ----------- | | path | relative path to the Core Data model. | | currentVersion | optional current version name (with or without the extension) By providing nil, it will try to read it from the .xccurrentversion file. | --- URL: "/generated/manifest/structs/CopyFilesAction" LLMS_URL: "/generated/manifest/structs/CopyFilesAction.md" --- **STRUCT** # `CopyFilesAction` **Contents** - [Properties](#properties) - `name` - `destination` - `subpath` - `files` - [Methods](#methods) - `absolutePath(name:subpath:files:)` - `productsDirectory(name:subpath:files:)` - `wrapper(name:subpath:files:)` - `executables(name:subpath:files:)` - `resources(name:subpath:files:)` - `javaResources(name:subpath:files:)` - `frameworks(name:subpath:files:)` - `sharedFrameworks(name:subpath:files:)` - `sharedSupport(name:subpath:files:)` - `plugins(name:subpath:files:)` ```swift public struct CopyFilesAction: Codable, Equatable, Sendable ``` A build phase action used to copy files. Copy files actions, represented as target copy files build phases, are useful to associate project files and products of other targets with the target and copies them to a specified destination, typically a subfolder within a product. This action may be used multiple times per target. ## Properties ### `name` ```swift public var name: String ``` Name of the build phase when the project gets generated. ### `destination` ```swift public var destination: Destination ``` Destination to copy files to. ### `subpath` ```swift public var subpath: String? ``` Path to a folder inside the destination. ### `files` ```swift public var files: [CopyFileElement] ``` Relative paths to the files to be copied. ## Methods ### `absolutePath(name:subpath:files:)` ```swift public static func absolutePath( name: String, subpath: String? = nil, files: [CopyFileElement] ) -> CopyFilesAction ``` A copy files action for an absolute path. - Parameters: - name: Name of the build phase when the project gets generated. - subpath: Path to a folder inside the destination. - files: Relative paths to the files to be copied. - Returns: Copy files action. #### Parameters | Name | Description | | ---- | ----------- | | name | Name of the build phase when the project gets generated. | | subpath | Path to a folder inside the destination. | | files | Relative paths to the files to be copied. | ### `productsDirectory(name:subpath:files:)` ```swift public static func productsDirectory( name: String, subpath: String? = nil, files: [CopyFileElement] ) -> CopyFilesAction ``` A copy files action for the products directory. - Parameters: - name: Name of the build phase when the project gets generated. - subpath: Path to a folder inside the destination. - files: Relative paths to the files to be copied. - Returns: Copy files action. #### Parameters | Name | Description | | ---- | ----------- | | name | Name of the build phase when the project gets generated. | | subpath | Path to a folder inside the destination. | | files | Relative paths to the files to be copied. | ### `wrapper(name:subpath:files:)` ```swift public static func wrapper( name: String, subpath: String? = nil, files: [CopyFileElement] ) -> CopyFilesAction ``` A copy files action for the wrapper directory. - Parameters: - name: Name of the build phase when the project gets generated. - subpath: Path to a folder inside the destination. - files: Relative paths to the files to be copied. - Returns: Copy files action. #### Parameters | Name | Description | | ---- | ----------- | | name | Name of the build phase when the project gets generated. | | subpath | Path to a folder inside the destination. | | files | Relative paths to the files to be copied. | ### `executables(name:subpath:files:)` ```swift public static func executables( name: String, subpath: String? = nil, files: [CopyFileElement] ) -> CopyFilesAction ``` A copy files action for the executables directory. - Parameters: - name: Name of the build phase when the project gets generated. - subpath: Path to a folder inside the destination. - files: Relative paths to the files to be copied. - Returns: Copy files action. #### Parameters | Name | Description | | ---- | ----------- | | name | Name of the build phase when the project gets generated. | | subpath | Path to a folder inside the destination. | | files | Relative paths to the files to be copied. | ### `resources(name:subpath:files:)` ```swift public static func resources( name: String, subpath: String? = nil, files: [CopyFileElement] ) -> CopyFilesAction ``` A copy files action for the resources directory. - Parameters: - name: Name of the build phase when the project gets generated. - subpath: Path to a folder inside the destination. - files: Relative paths to the files to be copied. - Returns: Copy files action. #### Parameters | Name | Description | | ---- | ----------- | | name | Name of the build phase when the project gets generated. | | subpath | Path to a folder inside the destination. | | files | Relative paths to the files to be copied. | ### `javaResources(name:subpath:files:)` ```swift public static func javaResources( name: String, subpath: String? = nil, files: [CopyFileElement] ) -> CopyFilesAction ``` A copy files action for the java resources directory. - Parameters: - name: Name of the build phase when the project gets generated. - subpath: Path to a folder inside the destination. - files: Relative paths to the files to be copied. - Returns: Copy files action. #### Parameters | Name | Description | | ---- | ----------- | | name | Name of the build phase when the project gets generated. | | subpath | Path to a folder inside the destination. | | files | Relative paths to the files to be copied. | ### `frameworks(name:subpath:files:)` ```swift public static func frameworks( name: String, subpath: String? = nil, files: [CopyFileElement] ) -> CopyFilesAction ``` A copy files action for the frameworks directory. - Parameters: - name: Name of the build phase when the project gets generated. - subpath: Path to a folder inside the destination. - files: Relative paths to the files to be copied. - Returns: Copy files action. #### Parameters | Name | Description | | ---- | ----------- | | name | Name of the build phase when the project gets generated. | | subpath | Path to a folder inside the destination. | | files | Relative paths to the files to be copied. | ### `sharedFrameworks(name:subpath:files:)` ```swift public static func sharedFrameworks( name: String, subpath: String? = nil, files: [CopyFileElement] ) -> CopyFilesAction ``` A copy files action for the shared frameworks directory. - Parameters: - name: Name of the build phase when the project gets generated. - subpath: Path to a folder inside the destination. - files: Relative paths to the files to be copied. - Returns: Copy files action. #### Parameters | Name | Description | | ---- | ----------- | | name | Name of the build phase when the project gets generated. | | subpath | Path to a folder inside the destination. | | files | Relative paths to the files to be copied. | ### `sharedSupport(name:subpath:files:)` ```swift public static func sharedSupport( name: String, subpath: String? = nil, files: [CopyFileElement] ) -> CopyFilesAction ``` A copy files action for the shared support directory. - Parameters: - name: Name of the build phase when the project gets generated. - subpath: Path to a folder inside the destination. - files: Relative paths to the files to be copied. - Returns: Copy files action. #### Parameters | Name | Description | | ---- | ----------- | | name | Name of the build phase when the project gets generated. | | subpath | Path to a folder inside the destination. | | files | Relative paths to the files to be copied. | ### `plugins(name:subpath:files:)` ```swift public static func plugins( name: String, subpath: String? = nil, files: [CopyFileElement] ) -> CopyFilesAction ``` A copy files action for the plugins directory. - Parameters: - name: Name of the build phase when the project gets generated. - subpath: Path to a folder inside the destination. - files: Relative paths to the files to be copied. - Returns: Copy files action. #### Parameters | Name | Description | | ---- | ----------- | | name | Name of the build phase when the project gets generated. | | subpath | Path to a folder inside the destination. | | files | Relative paths to the files to be copied. | --- URL: "/generated/manifest/structs/ConfigurationName" LLMS_URL: "/generated/manifest/structs/ConfigurationName.md" --- **STRUCT** # `ConfigurationName` **Contents** - [Properties](#properties) - `rawValue` - `debug` - `release` - [Methods](#methods) - `init(stringLiteral:)` - `configuration(_:)` ```swift public struct ConfigurationName: ExpressibleByStringLiteral, Codable, Equatable, Sendable ``` A configuration name. It has build-in support for ``debug`` and ``release`` configurations. You can extend with your own configurations using a extension: ``` import ProjectDescription extension ConfigurationName { static var beta: ConfigurationName { ConfigurationName("Beta") } } ``` ## Properties ### `rawValue` ```swift public var rawValue: String ``` The configuration name. ### `debug` ```swift public static var debug: ConfigurationName ``` Returns a configuration named "Debug" ### `release` ```swift public static var release: ConfigurationName ``` Returns a configuration named "Release" ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: StringLiteralType) ``` Creates a configuration name with its name. - Parameter value: Configuration name. #### Parameters | Name | Description | | ---- | ----------- | | value | Configuration name. | ### `configuration(_:)` ```swift public static func configuration(_ name: String) -> ConfigurationName ``` Returns a configuration name with its name. - Parameter name: Configuration name. - Returns: Initialized configuration name. #### Parameters | Name | Description | | ---- | ----------- | | name | Configuration name. | --- URL: "/generated/manifest/structs/Configuration" LLMS_URL: "/generated/manifest/structs/Configuration.md" --- **STRUCT** # `Configuration` **Contents** - [Properties](#properties) - `name` - `variant` - `settings` - `xcconfig` - [Methods](#methods) - `debug(name:settings:xcconfig:)` - `release(name:settings:xcconfig:)` ```swift public struct Configuration: Equatable, Codable, Sendable ``` A the build settings and the .xcconfig file of a project or target. It is initialized with either the `.debug` or `.release` static method. ## Properties ### `name` ```swift public var name: ConfigurationName ``` ### `variant` ```swift public var variant: Variant ``` ### `settings` ```swift public var settings: SettingsDictionary ``` ### `xcconfig` ```swift public var xcconfig: Path? ``` ## Methods ### `debug(name:settings:xcconfig:)` ```swift public static func debug( name: ConfigurationName, settings: SettingsDictionary = [:], xcconfig: Path? = nil ) -> Configuration ``` Returns a debug configuration. - Parameters: - name: The name of the configuration to use - settings: The base build settings to apply - xcconfig: The xcconfig file to associate with this configuration - Returns: A debug `CustomConfiguration` #### Parameters | Name | Description | | ---- | ----------- | | name | The name of the configuration to use | | settings | The base build settings to apply | | xcconfig | The xcconfig file to associate with this configuration | ### `release(name:settings:xcconfig:)` ```swift public static func release( name: ConfigurationName, settings: SettingsDictionary = [:], xcconfig: Path? = nil ) -> Configuration ``` Creates a release configuration - Parameters: - name: The name of the configuration to use - settings: The base build settings to apply - xcconfig: The xcconfig file to associate with this configuration - Returns: A release `CustomConfiguration` #### Parameters | Name | Description | | ---- | ----------- | | name | The name of the configuration to use | | settings | The base build settings to apply | | xcconfig | The xcconfig file to associate with this configuration | --- URL: "/generated/manifest/structs/Config.InstallOptions" LLMS_URL: "/generated/manifest/structs/Config.InstallOptions.md" --- **STRUCT** # `Config.InstallOptions` **Contents** - [Properties](#properties) - `passthroughSwiftPackageManagerArguments` - [Methods](#methods) - `options(passthroughSwiftPackageManagerArguments:)` ```swift public struct InstallOptions: Codable, Equatable, Sendable ``` Options for install. ## Properties ### `passthroughSwiftPackageManagerArguments` ```swift public var passthroughSwiftPackageManagerArguments: [String] ``` Arguments passed to the Swift Package Manager's `swift package` command when running `swift package resolve`. ## Methods ### `options(passthroughSwiftPackageManagerArguments:)` ```swift public static func options( passthroughSwiftPackageManagerArguments: [String] = [] ) -> Self ``` --- URL: "/generated/manifest/structs/Cloud" LLMS_URL: "/generated/manifest/structs/Cloud.md" --- **STRUCT** # `Cloud` **Contents** - [Properties](#properties) - `url` - `projectId` - `options` - [Methods](#methods) - `cloud(projectId:url:options:)` ```swift public struct Cloud: Codable, Equatable, Sendable ``` A cloud configuration, used for remote caching. ## Properties ### `url` ```swift public var url: String ``` The base URL that points to the Cloud server. ### `projectId` ```swift public var projectId: String ``` The project unique identifier. ### `options` ```swift public var options: [Option] ``` The configuration options. ## Methods ### `cloud(projectId:url:options:)` ```swift public static func cloud(projectId: String, url: String = "https://tuist.dev", options: [Option] = []) -> Cloud ``` Returns a generic cloud configuration. - Parameters: - projectId: Project unique identifier. - url: Base URL to the Cloud server. - options: Cloud options. - Returns: A Cloud instance. #### Parameters | Name | Description | | ---- | ----------- | | projectId | Project unique identifier. | | url | Base URL to the Cloud server. | | options | Cloud options. | --- URL: "/generated/manifest/structs/BuildRule" LLMS_URL: "/generated/manifest/structs/BuildRule.md" --- **STRUCT** # `BuildRule` **Contents** - [Properties](#properties) - `compilerSpec` - `filePatterns` - `fileType` - `name` - `outputFiles` - `inputFiles` - `outputFilesCompilerFlags` - `script` - `runOncePerArchitecture` - [Methods](#methods) - `buildRule(name:fileType:filePatterns:compilerSpec:inputFiles:outputFiles:outputFilesCompilerFlags:script:runOncePerArchitecture:)` ```swift public struct BuildRule: Codable, Equatable, Sendable ``` A BuildRule is used to specify a method for transforming an input file in to an output file(s). ## Properties ### `compilerSpec` ```swift public var compilerSpec: CompilerSpec ``` Compiler specification for element transformation. ### `filePatterns` ```swift public var filePatterns: String? ``` Regex pattern when `sourceFilesWithNamesMatching` is used. ### `fileType` ```swift public var fileType: FileType ``` File types which are processed by build rule. ### `name` ```swift public var name: String? ``` Build rule name. ### `outputFiles` ```swift public var outputFiles: [String] ``` Build rule output files. ### `inputFiles` ```swift public var inputFiles: [String] ``` Build rule input files. ### `outputFilesCompilerFlags` ```swift public var outputFilesCompilerFlags: [String] ``` Build rule output files compiler flags. ### `script` ```swift public var script: String? ``` Build rule custom script when `customScript` is used. ### `runOncePerArchitecture` ```swift public var runOncePerArchitecture: Bool? ``` Build rule run once per architecture. ## Methods ### `buildRule(name:fileType:filePatterns:compilerSpec:inputFiles:outputFiles:outputFilesCompilerFlags:script:runOncePerArchitecture:)` ```swift public static func buildRule( name: String? = nil, fileType: FileType, filePatterns: String? = nil, compilerSpec: CompilerSpec, inputFiles: [String] = [], outputFiles: [String] = [], outputFilesCompilerFlags: [String] = [], script: String? = nil, runOncePerArchitecture: Bool = false ) -> Self ``` --- URL: "/generated/manifest/structs/BuildAction" LLMS_URL: "/generated/manifest/structs/BuildAction.md" --- **STRUCT** # `BuildAction` **Contents** - [Properties](#properties) - `targets` - `preActions` - `postActions` - `runPostActionsOnFailure` - `findImplicitDependencies` - [Methods](#methods) - `buildAction(targets:preActions:postActions:runPostActionsOnFailure:findImplicitDependencies:)` ```swift public struct BuildAction: Equatable, Codable, Sendable ``` An action that builds products. It's initialized with the `.buildAction` static method. ## Properties ### `targets` ```swift public var targets: [TargetReference] ``` A list of targets to build, which are defined in the project. ### `preActions` ```swift public var preActions: [ExecutionAction] ``` A list of actions that are executed before starting the build process. ### `postActions` ```swift public var postActions: [ExecutionAction] ``` A list of actions that are executed after the build process. ### `runPostActionsOnFailure` ```swift public var runPostActionsOnFailure: Bool ``` Whether the post actions should be run in the case of a failure ### `findImplicitDependencies` ```swift public var findImplicitDependencies: Bool ``` Whether Xcode should be allowed to find dependencies implicitly. The default is `true`. ## Methods ### `buildAction(targets:preActions:postActions:runPostActionsOnFailure:findImplicitDependencies:)` ```swift public static func buildAction( targets: [TargetReference], preActions: [ExecutionAction] = [], postActions: [ExecutionAction] = [], runPostActionsOnFailure: Bool = false, findImplicitDependencies: Bool = true ) -> BuildAction ``` Returns a build action. - Parameters: - targets: A list of targets to build, which are defined in the project. - preActions: A list of actions that are executed before starting the build process. - postActions: A list of actions that are executed after the build process. - runPostActionsOnFailure: Whether the post actions should be run in the case of a failure - findImplicitDependencies: Whether Xcode should be allowed to find dependencies implicitly. The default is `true`. - Returns: Initialized build action. #### Parameters | Name | Description | | ---- | ----------- | | targets | A list of targets to build, which are defined in the project. | | preActions | A list of actions that are executed before starting the build process. | | postActions | A list of actions that are executed after the build process. | | runPostActionsOnFailure | Whether the post actions should be run in the case of a failure | | findImplicitDependencies | Whether Xcode should be allowed to find dependencies implicitly. The default is `true`. | --- URL: "/generated/manifest/structs/Arguments" LLMS_URL: "/generated/manifest/structs/Arguments.md" --- **STRUCT** # `Arguments` **Contents** - [Properties](#properties) - `environmentVariables` - `launchArguments` - [Methods](#methods) - `arguments(environmentVariables:launchArguments:)` ```swift public struct Arguments: Equatable, Codable, Sendable ``` A collection of arguments and environment variables. ## Properties ### `environmentVariables` ```swift public var environmentVariables: [String: EnvironmentVariable] ``` ### `launchArguments` ```swift public var launchArguments: [LaunchArgument] ``` ## Methods ### `arguments(environmentVariables:launchArguments:)` ```swift public static func arguments( environmentVariables: [String: EnvironmentVariable] = [:], launchArguments: [LaunchArgument] = [] ) -> Self ``` --- URL: "/generated/manifest/structs/ArchiveAction" LLMS_URL: "/generated/manifest/structs/ArchiveAction.md" --- **STRUCT** # `ArchiveAction` **Contents** - [Properties](#properties) - `configuration` - `revealArchiveInOrganizer` - `customArchiveName` - `preActions` - `postActions` - [Methods](#methods) - `archiveAction(configuration:revealArchiveInOrganizer:customArchiveName:preActions:postActions:)` ```swift public struct ArchiveAction: Equatable, Codable, Sendable ``` An action that archives the built products. It's initialized with the `.archiveAction` static method. ## Properties ### `configuration` ```swift public var configuration: ConfigurationName ``` Indicates the build configuration to run the archive with. ### `revealArchiveInOrganizer` ```swift public var revealArchiveInOrganizer: Bool ``` If set to true, Xcode will reveal the Organizer on completion. ### `customArchiveName` ```swift public var customArchiveName: String? ``` Set if you want to override Xcode's default archive name. ### `preActions` ```swift public var preActions: [ExecutionAction] ``` A list of actions that are executed before starting the archive process. ### `postActions` ```swift public var postActions: [ExecutionAction] ``` A list of actions that are executed after the archive process. ## Methods ### `archiveAction(configuration:revealArchiveInOrganizer:customArchiveName:preActions:postActions:)` ```swift public static func archiveAction( configuration: ConfigurationName, revealArchiveInOrganizer: Bool = true, customArchiveName: String? = nil, preActions: [ExecutionAction] = [], postActions: [ExecutionAction] = [] ) -> ArchiveAction ``` Initialize a `ArchiveAction` - Parameters: - configuration: Indicates the build configuration to run the archive with. - revealArchiveInOrganizer: If set to true, Xcode will reveal the Organizer on completion. - customArchiveName: Set if you want to override Xcode's default archive name. - preActions: A list of actions that are executed before starting the archive process. - postActions: A list of actions that are executed after the archive process. #### Parameters | Name | Description | | ---- | ----------- | | configuration | Indicates the build configuration to run the archive with. | | revealArchiveInOrganizer | If set to true, Xcode will reveal the Organizer on completion. | | customArchiveName | Set if you want to override Xcode’s default archive name. | | preActions | A list of actions that are executed before starting the archive process. | | postActions | A list of actions that are executed after the archive process. | --- URL: "/generated/manifest/structs/AnalyzeAction" LLMS_URL: "/generated/manifest/structs/AnalyzeAction.md" --- **STRUCT** # `AnalyzeAction` **Contents** - [Properties](#properties) - `configuration` - [Methods](#methods) - `analyzeAction(configuration:)` ```swift public struct AnalyzeAction: Equatable, Codable, Sendable ``` An action that analyzes the built products. It's initialized with the `.analyzeAction` static method ## Properties ### `configuration` ```swift public var configuration: ConfigurationName ``` Indicates the build configuration the product should be analyzed with. ## Methods ### `analyzeAction(configuration:)` ```swift public static func analyzeAction(configuration: ConfigurationName) -> AnalyzeAction ``` Returns an analyze action. - Parameter configuration: Indicates the build configuration the product should be analyzed with. - Returns: Analyze action. #### Parameters | Name | Description | | ---- | ----------- | | configuration | Indicates the build configuration the product should be analyzed with. | --- URL: "/generated/manifest/extensions/Version" LLMS_URL: "/generated/manifest/extensions/Version.md" --- **EXTENSION** # `Version` ```swift extension Version: Comparable ``` ## Properties ### `description` ```swift public var description: String ``` ## Methods ### `<(_:_:)` ```swift public static func < (lhs: Version, rhs: Version) -> Bool ``` #### Parameters | Name | Description | | ---- | ----------- | | lhs | A value to compare. | | rhs | Another value to compare. | ### `init(string:)` ```swift public init?(string: String) ``` Create a version object from string. - Parameters: - string: The string to parse. #### Parameters | Name | Description | | ---- | ----------- | | string | The string to parse. | ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | --- URL: "/generated/manifest/extensions/TemplateString" LLMS_URL: "/generated/manifest/extensions/TemplateString.md" --- **EXTENSION** # `TemplateString` ```swift extension TemplateString: ExpressibleByStringLiteral ``` ## Properties ### `description` ```swift public var description: String ``` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | ### `init(stringInterpolation:)` ```swift public init(stringInterpolation: StringInterpolation) ``` #### Parameters | Name | Description | | ---- | ----------- | | stringInterpolation | An instance of `StringInterpolation` which has had each segment of the string literal appended to it. | --- URL: "/generated/manifest/extensions/Template.Item" LLMS_URL: "/generated/manifest/extensions/Template.Item.md" --- **EXTENSION** # `Template.Item` ```swift extension Template.Item ``` ## Methods ### `string(path:contents:)` ```swift public static func string(path: String, contents: String) -> Template.Item ``` - Parameters: - path: Path where to generate file - contents: String Contents - Returns: `Template.Item` that is `.string` #### Parameters | Name | Description | | ---- | ----------- | | path | Path where to generate file | | contents | String Contents | ### `file(path:templatePath:)` ```swift public static func file(path: String, templatePath: Path) -> Template.Item ``` - Parameters: - path: Path where to generate file - templatePath: Path of file where the template is defined - Returns: `Template.Item` that is `.file` #### Parameters | Name | Description | | ---- | ----------- | | path | Path where to generate file | | templatePath | Path of file where the template is defined | ### `directory(path:sourcePath:)` ```swift public static func directory(path: String, sourcePath: Path) -> Template.Item ``` - Parameters: - path: Path where will be copied the folder - sourcePath: Path of folder which will be copied - Returns: `Template.Item` that is `.directory` #### Parameters | Name | Description | | ---- | ----------- | | path | Path where will be copied the folder | | sourcePath | Path of folder which will be copied | --- URL: "/generated/manifest/extensions/Template.Attribute.Value" LLMS_URL: "/generated/manifest/extensions/Template.Attribute.Value.md" --- **EXTENSION** # `Template.Attribute.Value` ```swift extension Template.Attribute.Value: ExpressibleByStringLiteral ``` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | ### `init(integerLiteral:)` ```swift public init(integerLiteral value: Int) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value to create. | ### `init(floatLiteral:)` ```swift public init(floatLiteral value: Double) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value to create. | ### `init(booleanLiteral:)` ```swift public init(booleanLiteral value: Bool) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | ### `init(dictionaryLiteral:)` ```swift public init(dictionaryLiteral elements: (String, Template.Attribute.Value)...) ``` ### `init(arrayLiteral:)` ```swift public init(arrayLiteral elements: Template.Attribute.Value...) ``` --- URL: "/generated/manifest/extensions/String.StringInterpolation" LLMS_URL: "/generated/manifest/extensions/String.StringInterpolation.md" --- **EXTENSION** # `String.StringInterpolation` ```swift extension String.StringInterpolation ``` ## Methods ### `appendInterpolation(_:)` ```swift public mutating func appendInterpolation(_ value: Template.Attribute) ``` --- URL: "/generated/manifest/extensions/SourceFilesList" LLMS_URL: "/generated/manifest/extensions/SourceFilesList.md" --- **EXTENSION** # `SourceFilesList` ```swift extension SourceFilesList: ExpressibleByStringInterpolation ``` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | ### `init(arrayLiteral:)` ```swift public init(arrayLiteral elements: SourceFileGlob...) ``` --- URL: "/generated/manifest/extensions/SourceFileGlob" LLMS_URL: "/generated/manifest/extensions/SourceFileGlob.md" --- **EXTENSION** # `SourceFileGlob` ```swift extension SourceFileGlob: ExpressibleByStringInterpolation ``` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | --- URL: "/generated/manifest/extensions/SettingsDictionary" LLMS_URL: "/generated/manifest/extensions/SettingsDictionary.md" --- **EXTENSION** # `SettingsDictionary` ```swift extension SettingsDictionary ``` ## Methods ### `merge(_:)` ```swift public mutating func merge(_ other: SettingsDictionary) ``` ### `merging(_:)` ```swift public func merging(_ other: SettingsDictionary) -> SettingsDictionary ``` ### `manualCodeSigning(identity:provisioningProfileSpecifier:)` ```swift public func manualCodeSigning(identity: String? = nil, provisioningProfileSpecifier: String? = nil) -> SettingsDictionary ``` Sets `"CODE_SIGN_STYLE"` to `"Manual"`,` "CODE_SIGN_IDENTITY"` to `identity`, and `"PROVISIONING_PROFILE_SPECIFIER"` to `provisioningProfileSpecifier` ### `automaticCodeSigning(devTeam:)` ```swift public func automaticCodeSigning(devTeam: String) -> SettingsDictionary ``` Sets `"CODE_SIGN_STYLE"` to `"Automatic"` and `"DEVELOPMENT_TEAM"` to `devTeam` - Parameters: - devTeam: Your Apple Developer Team ID. See [here](https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/) how you can find it. #### Parameters | Name | Description | | ---- | ----------- | | devTeam | Your Apple Developer Team ID. See how you can find it. | ### `codeSignIdentityAppleDevelopment()` ```swift public func codeSignIdentityAppleDevelopment() -> SettingsDictionary ``` Sets `"CODE_SIGN_IDENTITY"` to `"Apple Development"` ### `codeSignIdentity(_:)` ```swift public func codeSignIdentity(_ identity: String) -> SettingsDictionary ``` Sets `"CODE_SIGN_IDENTITY"` to `identity` ### `currentProjectVersion(_:)` ```swift public func currentProjectVersion(_ version: String) -> SettingsDictionary ``` Sets `"CURRENT_PROJECT_VERSION"` to `version` ### `marketingVersion(_:)` ```swift public func marketingVersion(_ version: String) -> SettingsDictionary ``` Sets `"MARKETING_VERSION"` to `version` ### `appleGenericVersioningSystem()` ```swift public func appleGenericVersioningSystem() -> SettingsDictionary ``` Sets `"VERSIONING_SYSTEM"` to `"apple-generic"` ### `versionInfo(_:prefix:suffix:)` ```swift public func versionInfo(_ version: String, prefix: String? = nil, suffix: String? = nil) -> SettingsDictionary ``` Sets "VERSION_INFO_STRING" to `version`. If `prefix` and `suffix` are not `nil`, they're used as `"VERSION_INFO_PREFIX"` and `"VERSION_INFO_SUFFIX"` respectively. ### `swiftVersion(_:)` ```swift public func swiftVersion(_ version: String) -> SettingsDictionary ``` Sets `"SWIFT_VERSION"` to `version` ### `otherSwiftFlags(_:)` ```swift public func otherSwiftFlags(_ flags: String...) -> SettingsDictionary ``` Sets `"OTHER_SWIFT_FLAGS"` to `flags` ### `otherSwiftFlags(_:)` ```swift public func otherSwiftFlags(_ flags: [String]) -> SettingsDictionary ``` Sets `"OTHER_SWIFT_FLAGS"` to `flags` ### `swiftActiveCompilationConditions(_:)` ```swift public func swiftActiveCompilationConditions(_ conditions: String...) -> SettingsDictionary ``` Sets `"SWIFT_ACTIVE_COMPILATION_CONDITIONS"` to `conditions` ### `swiftActiveCompilationConditions(_:)` ```swift public func swiftActiveCompilationConditions(_ conditions: [String]) -> SettingsDictionary ``` Sets `"SWIFT_ACTIVE_COMPILATION_CONDITIONS"` to `conditions` ### `swiftCompilationMode(_:)` ```swift public func swiftCompilationMode(_ mode: SwiftCompilationMode) -> SettingsDictionary ``` Sets `"SWIFT_COMPILATION_MODE"` to the available `SwiftCompilationMode` (`"singlefile"` or `"wholemodule"`) ### `swiftOptimizationLevel(_:)` ```swift public func swiftOptimizationLevel(_ level: SwiftOptimizationLevel) -> SettingsDictionary ``` Sets `"SWIFT_OPTIMIZATION_LEVEL"` to the available `SwiftOptimizationLevel` (`"-O"`, `"-Onone"` or `"-Osize"`) ### `swiftOptimizeObjectLifetimes(_:)` ```swift public func swiftOptimizeObjectLifetimes(_ enabled: Bool) -> SettingsDictionary ``` Sets `"SWIFT_OPTIMIZE_OBJECT_LIFETIME"` to `"YES"` or `"NO"` ### `swiftObjcBridgingHeaderPath(_:)` ```swift public func swiftObjcBridgingHeaderPath(_ path: String) -> SettingsDictionary ``` Sets `"SWIFT_OBJC_BRIDGING_HEADER"` to `path` ### `otherCFlags(_:)` ```swift public func otherCFlags(_ flags: [String]) -> SettingsDictionary ``` Sets `"OTHER_CFLAGS"` to `flags` ### `otherLinkerFlags(_:)` ```swift public func otherLinkerFlags(_ flags: [String]) -> SettingsDictionary ``` Sets `"OTHER_LDFLAGS"` to `flags` ### `bitcodeEnabled(_:)` ```swift public func bitcodeEnabled(_ enabled: Bool) -> SettingsDictionary ``` Sets `"ENABLE_BITCODE"` to `"YES"` or `"NO"` ### `debugInformationFormat(_:)` ```swift public func debugInformationFormat(_ format: DebugInformationFormat) -> SettingsDictionary ``` Sets `"DEBUG_INFORMATION_FORMAT"`to `"dwarf"` or `"dwarf-with-dsym"` ### `betaFeature_enableExplicitModules(_:)` ```swift public func betaFeature_enableExplicitModules(_ enabled: Bool) -> SettingsDictionary ``` Sets `"_EXPERIMENTAL_SWIFT_EXPLICIT_MODULES"` NOTE: This is only available when using Xcode 16 or later. This setting may change and is not guaranteed to work across all beta versions. --- URL: "/generated/manifest/extensions/SchemeLanguage" LLMS_URL: "/generated/manifest/extensions/SchemeLanguage.md" --- **EXTENSION** # `SchemeLanguage` ```swift extension SchemeLanguage ``` ## Properties ### `doubleLengthPseudoLanguage` ```swift public static var doubleLengthPseudoLanguage: SchemeLanguage ``` ### `rightToLeftPseudoLanguage` ```swift public static var rightToLeftPseudoLanguage: SchemeLanguage ``` ### `accentedPseudoLanguage` ```swift public static var accentedPseudoLanguage: SchemeLanguage ``` ### `boundedStringPseudoLanguage` ```swift public static var boundedStringPseudoLanguage: SchemeLanguage ``` ### `rightToLeftWithStringsPseudoLanguage` ```swift public static var rightToLeftWithStringsPseudoLanguage: SchemeLanguage ``` --- URL: "/generated/manifest/extensions/ResourceSynthesizer.Parser.Option" LLMS_URL: "/generated/manifest/extensions/ResourceSynthesizer.Parser.Option.md" --- **EXTENSION** # `ResourceSynthesizer.Parser.Option` ```swift extension ResourceSynthesizer.Parser.Option: ExpressibleByStringInterpolation ``` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | ### `init(integerLiteral:)` ```swift public init(integerLiteral value: Int) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value to create. | ### `init(floatLiteral:)` ```swift public init(floatLiteral value: Double) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value to create. | ### `init(booleanLiteral:)` ```swift public init(booleanLiteral value: Bool) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | ### `init(dictionaryLiteral:)` ```swift public init(dictionaryLiteral elements: (String, Self)...) ``` ### `init(arrayLiteral:)` ```swift public init(arrayLiteral elements: Self...) ``` --- URL: "/generated/manifest/extensions/ResourceFileElements" LLMS_URL: "/generated/manifest/extensions/ResourceFileElements.md" --- **EXTENSION** # `ResourceFileElements` ```swift extension ResourceFileElements: ExpressibleByStringInterpolation ``` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | ### `init(arrayLiteral:)` ```swift public init(arrayLiteral elements: ResourceFileElement...) ``` --- URL: "/generated/manifest/extensions/ResourceFileElement" LLMS_URL: "/generated/manifest/extensions/ResourceFileElement.md" --- **EXTENSION** # `ResourceFileElement` ```swift extension ResourceFileElement: ExpressibleByStringInterpolation ``` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | --- URL: "/generated/manifest/extensions/Range" LLMS_URL: "/generated/manifest/extensions/Range.md" --- **EXTENSION** # `Range` ```swift extension Range where Bound == Version ``` ## Methods ### `contains(_:)` ```swift public func contains(_: Version) -> Bool ``` Marked as unavailable because we have custom rules for contains. ### `contains(version:)` ```swift public func contains(version: Version) -> Bool ``` --- URL: "/generated/manifest/extensions/Plist.Value" LLMS_URL: "/generated/manifest/extensions/Plist.Value.md" --- **EXTENSION** # `Plist.Value` ```swift extension Plist.Value: ExpressibleByStringInterpolation ``` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | ### `init(integerLiteral:)` ```swift public init(integerLiteral value: Int) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value to create. | ### `init(floatLiteral:)` ```swift public init(floatLiteral value: Double) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value to create. | ### `init(booleanLiteral:)` ```swift public init(booleanLiteral value: Bool) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | ### `init(dictionaryLiteral:)` ```swift public init(dictionaryLiteral elements: (String, Plist.Value)...) ``` ### `init(arrayLiteral:)` ```swift public init(arrayLiteral elements: Plist.Value...) ``` --- URL: "/generated/manifest/extensions/PlatformFilters" LLMS_URL: "/generated/manifest/extensions/PlatformFilters.md" --- **EXTENSION** # `PlatformFilters` ```swift extension PlatformFilters ``` ## Properties ### `all` ```swift public static let all = Set(PlatformFilter.allCases) ``` --- URL: "/generated/manifest/extensions/Package" LLMS_URL: "/generated/manifest/extensions/Package.md" --- **EXTENSION** # `Package` ```swift extension Package ``` ## Methods ### `package(url:from:)` ```swift public static func package(url: String, from version: Version) -> Package ``` Create a package dependency that uses the version requirement, starting with the given minimum version, going up to the next major version. This is the recommended way to specify a remote package dependency. It allows you to specify the minimum version you require, allows updates that include bug fixes and backward-compatible feature updates, but requires you to explicitly update to a new major version of the dependency. This approach provides the maximum flexibility on which version to use, while making sure you don't update to a version with breaking changes, and helps to prevent conflicts in your dependency graph. The following example allows the Swift package manager to select a version like a `1.2.3`, `1.2.4`, or `1.3.0`, but not `2.0.0`. .package(url: "https://example.com/example-package.git", from: "1.2.3"), - Parameters: - url: The valid Git URL of the package. - version: The minimum version requirement. #### Parameters | Name | Description | | ---- | ----------- | | url | The valid Git URL of the package. | | version | The minimum version requirement. | ### `package(url:_:)` ```swift public static func package(url: String, _ requirement: Package.Requirement) -> Package ``` Add a remote package dependency given a version requirement. - Parameters: - url: The valid Git URL of the package. - requirement: A dependency requirement. See static methods on `Package.Requirement` for available options. #### Parameters | Name | Description | | ---- | ----------- | | url | The valid Git URL of the package. | | requirement | A dependency requirement. See static methods on `Package.Requirement` for available options. | ### `package(url:_:)` ```swift public static func package(url: String, _ range: Range) -> Package ``` Add a package dependency starting with a specific minimum version, up to but not including a specified maximum version. The following example allows the Swift package manager to pick versions `1.2.3`, `1.2.4`, `1.2.5`, but not `1.2.6`. .package(url: "https://example.com/example-package.git", "1.2.3"..<"1.2.6"), - Parameters: - url: The valid Git URL of the package. - range: The custom version range requirement. #### Parameters | Name | Description | | ---- | ----------- | | url | The valid Git URL of the package. | | range | The custom version range requirement. | ### `package(url:_:)` ```swift public static func package(url: String, _ range: ClosedRange) -> Package ``` Add a package dependency starting with a specific minimum version, going up to and including a specific maximum version. The following example allows the Swift package manager to pick versions 1.2.3, 1.2.4, 1.2.5, as well as 1.2.6. .package(url: "https://example.com/example-package.git", "1.2.3"..."1.2.6"), - Parameters: - url: The valid Git URL of the package. - range: The closed version range requirement. #### Parameters | Name | Description | | ---- | ----------- | | url | The valid Git URL of the package. | | range | The closed version range requirement. | ### `package(path:)` ```swift public static func package(path: Path) -> Package ``` Add a dependency to a local package on the filesystem. The Swift Package Manager uses the package dependency as-is and does not perform any source control access. Local package dependencies are especially useful during development of a new package or when working on multiple tightly coupled packages. - Parameter path: The path of the package. #### Parameters | Name | Description | | ---- | ----------- | | path | The path of the package. | ### `package(id:from:)` ```swift public static func package(id: String, from version: Version) -> Package ``` Adds a package dependency that uses the version requirement, starting with the given minimum version, going up to the next major version. This is the recommended way to specify a remote package dependency. It allows you to specify the minimum version you require, allows updates that include bug fixes and backward-compatible feature updates, but requires you to explicitly update to a new major version of the dependency. This approach provides the maximum flexibility on which version to use, while making sure you don't update to a version with breaking changes, and helps to prevent conflicts in your dependency graph. The following example allows the Swift Package Manager to select a version like a `1.2.3`, `1.2.4`, or `1.3.0`, but not `2.0.0`. ```swift .package(id: "scope.name", from: "1.2.3"), ``` - Parameters: - id: The identity of the package. - version: The minimum version requirement. - Returns: A `Package` instance. #### Parameters | Name | Description | | ---- | ----------- | | id | The identity of the package. | | version | The minimum version requirement. | ### `package(id:exact:)` ```swift public static func package(id: String, exact version: Version) -> Package ``` Adds a package dependency that uses the exact version requirement. Specifying exact version requirements are not recommended as they can cause conflicts in your dependency graph when multiple other packages depend on a package. Because Swift packages follow the semantic versioning convention, think about specifying a version range instead. The following example instructs the Swift Package Manager to use version `1.2.3`. ```swift .package(id: "scope.name", exact: "1.2.3"), ``` - Parameters: - id: The identity of the package. - version: The exact version of the dependency for this requirement. - Returns: A `Package` instance. #### Parameters | Name | Description | | ---- | ----------- | | id | The identity of the package. | | version | The exact version of the dependency for this requirement. | ### `package(id:_:)` ```swift public static func package(id: String, _ range: Range) -> Package ``` Adds a package dependency starting with a specific minimum version, up to but not including a specified maximum version. The following example allows the Swift Package Manager to pick versions `1.2.3`, `1.2.4`, `1.2.5`, but not `1.2.6`. ```swift .package(id: "scope.name", "1.2.3"..<"1.2.6"), ``` The following example allows the Swift Package Manager to pick versions between 1.0.0 and 2.0.0 ```swift .package(id: "scope.name", .upToNextMajor(from: "1.0.0")), ``` The following example allows the Swift Package Manager to pick versions between 1.0.0 and 1.1.0 ```swift .package(id: "scope.name", .upToNextMinor(from: "1.0.0")), ``` - Parameters: - id: The identity of the package. - range: The custom version range requirement. - Returns: A `Package` instance. #### Parameters | Name | Description | | ---- | ----------- | | id | The identity of the package. | | range | The custom version range requirement. | ### `package(id:_:)` ```swift public static func package(id: String, _ range: ClosedRange) -> Package ``` Adds a package dependency starting with a specific minimum version, going up to and including a specific maximum version. The following example allows the Swift Package Manager to pick versions 1.2.3, 1.2.4, 1.2.5, as well as 1.2.6. ```swift .package(id: "scope.name", "1.2.3"..."1.2.6"), ``` - Parameters: - id: The identity of the package. - range: The closed version range requirement. - Returns: A `Package` instance. #### Parameters | Name | Description | | ---- | ----------- | | id | The identity of the package. | | range | The closed version range requirement. | ### `package(url:version:)` ### `package(url:branch:)` ### `package(url:revision:)` ### `package(url:range:)` --- URL: "/generated/manifest/extensions/InfoPlist" LLMS_URL: "/generated/manifest/extensions/InfoPlist.md" --- **EXTENSION** # `InfoPlist` ```swift extension InfoPlist: ExpressibleByStringInterpolation ``` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | --- URL: "/generated/manifest/extensions/FileListGlob" LLMS_URL: "/generated/manifest/extensions/FileListGlob.md" --- **EXTENSION** # `FileListGlob` ```swift extension FileListGlob: ExpressibleByStringInterpolation ``` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | --- URL: "/generated/manifest/extensions/FileList" LLMS_URL: "/generated/manifest/extensions/FileList.md" --- **EXTENSION** # `FileList` ```swift extension FileList: ExpressibleByStringInterpolation ``` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | ### `init(arrayLiteral:)` ```swift public init(arrayLiteral elements: String...) ``` --- URL: "/generated/manifest/extensions/FileElement" LLMS_URL: "/generated/manifest/extensions/FileElement.md" --- **EXTENSION** # `FileElement` ```swift extension FileElement: ExpressibleByStringInterpolation ``` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | --- URL: "/generated/manifest/extensions/Environment.Value_" LLMS_URL: "/generated/manifest/extensions/Environment.Value_.md" --- **EXTENSION** # `Environment.Value?` ```swift extension Environment.Value? ``` ## Methods ### `getString(default:)` ```swift public func getString(default defaultString: String) -> String ``` Retrieve the Environment value as a string or return the specified default string value - Parameters: - default: default String value to be returned - Returns: String #### Parameters | Name | Description | | ---- | ----------- | | default | default String value to be returned | ### `getBoolean(default:)` ```swift public func getBoolean(default defaultBoolean: Bool) -> Bool ``` Retrieve the Environment value as a boolean or return the specified default boolean value - Parameters: - default: default Boolean value to be returned - Returns: Bool #### Parameters | Name | Description | | ---- | ----------- | | default | default Boolean value to be returned | --- URL: "/generated/manifest/extensions/Entitlements" LLMS_URL: "/generated/manifest/extensions/Entitlements.md" --- **EXTENSION** # `Entitlements` ```swift extension Entitlements: ExpressibleByStringInterpolation ``` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | --- URL: "/generated/manifest/extensions/Destinations" LLMS_URL: "/generated/manifest/extensions/Destinations.md" --- **EXTENSION** # `Destinations` ```swift extension Destinations ``` ## Properties ### `watchOS` ```swift public static let watchOS: Destinations = [.appleWatch] ``` ### `iOS` ```swift public static let iOS: Destinations = [.iPhone, .iPad, .macWithiPadDesign] ``` Currently we omit `.visionOSwithiPadDesign` from our default because `visionOS` is unreleased. ### `macOS` ```swift public static let macOS: Destinations = [.mac] ``` ### `tvOS` ```swift public static let tvOS: Destinations = [.appleTv] ``` ### `visionOS` ```swift public static let visionOS: Destinations = [.appleVision] ``` ### `platforms` ```swift public var platforms: Set ``` Convenience set of platforms that are supported by a set of destinations --- URL: "/generated/manifest/extensions/DefaultSettings" LLMS_URL: "/generated/manifest/extensions/DefaultSettings.md" --- **EXTENSION** # `DefaultSettings` ```swift extension DefaultSettings ``` ## Properties ### `recommended` ```swift public static var recommended: DefaultSettings ``` ### `essential` ```swift public static var essential: DefaultSettings ``` --- URL: "/generated/manifest/extensions/CopyFileElement" LLMS_URL: "/generated/manifest/extensions/CopyFileElement.md" --- **EXTENSION** # `CopyFileElement` ```swift extension CopyFileElement: ExpressibleByStringInterpolation ``` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | --- URL: "/generated/manifest/extensions/ClosedRange" LLMS_URL: "/generated/manifest/extensions/ClosedRange.md" --- **EXTENSION** # `ClosedRange` ```swift extension ClosedRange where Bound == Version ``` ## Methods ### `contains(_:)` ```swift public func contains(_: Version) -> Bool ``` Marked as unavailable because we have custom rules for contains. --- URL: "/generated/manifest/extensions/Array" LLMS_URL: "/generated/manifest/extensions/Array.md" --- **EXTENSION** # `[ResourceSynthesizer]` ## Properties ### `default` ```swift public static var `default`: Self ``` --- URL: "/generated/manifest/extensions/Array" LLMS_URL: "/generated/manifest/extensions/Array.md" --- **EXTENSION** # `[FileElement]` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | --- URL: "/generated/manifest/enums/XCFrameworkSignature" LLMS_URL: "/generated/manifest/enums/XCFrameworkSignature.md" --- **ENUM** # `XCFrameworkSignature` **Contents** - [Cases](#cases) - `unsigned` - `signedWithAppleCertificate(teamIdentifier:teamName:)` - `selfSigned(fingerprint:)` ```swift public enum XCFrameworkSignature: Equatable, Hashable, Codable, Sendable ``` Expected signature for XCFramework. Can be used to verify the authenticity of the XCFramework against the actual signature calculated from it. ## Cases ### `unsigned` ```swift case unsigned ``` The XCFramework is not signed. ### `signedWithAppleCertificate(teamIdentifier:teamName:)` ```swift case signedWithAppleCertificate(teamIdentifier: String, teamName: String) ``` The XCFramework is signed with an Apple Development certificate. ### `selfSigned(fingerprint:)` ```swift case selfSigned(fingerprint: String) ``` The XCFramework is signed by a self issued code signing identity. --- URL: "/generated/manifest/enums/TuistProject" LLMS_URL: "/generated/manifest/enums/TuistProject.md" --- **ENUM** # `TuistProject` **Contents** - [Cases](#cases) - `tuist(compatibleXcodeVersions:swiftVersion:plugins:generationOptions:installOptions:)` - `xcode(_:)` ```swift public enum TuistProject: Codable, Equatable, Sendable ``` ## Cases ### `tuist(compatibleXcodeVersions:swiftVersion:plugins:generationOptions:installOptions:)` ```swift case tuist( compatibleXcodeVersions: CompatibleXcodeVersions = .all, swiftVersion: Version? = nil, plugins: [PluginLocation] = [], generationOptions: Tuist.GenerationOptions = .options(), installOptions: Tuist.InstallOptions = .options() ) ``` Creates a configuration for a Tuist project. - Parameters: - compatibleXcodeVersions: List of Xcode versions the project is compatible with. - swiftVersion: The version of Swift that will be used by Tuist. - plugins: A list of plugins to extend Tuist. - generationOptions: List of options to use when generating the project. - installOptions: List of options to use when running `tuist install`. ### `xcode(_:)` ```swift case xcode(TuistXcodeProjectOptions = TuistXcodeProjectOptions.options()) ``` --- URL: "/generated/manifest/enums/TestableTarget.Parallelization" LLMS_URL: "/generated/manifest/enums/TestableTarget.Parallelization.md" --- **ENUM** # `TestableTarget.Parallelization` **Contents** - [Cases](#cases) - `disabled` ```swift public enum Parallelization: Equatable, Codable, Sendable ``` With the introduction of Swift Testing and Xcode 16, you can now choose to run your tests in parallel across either the full suite of tests in a target with `.enabled`, just those created under Swift Testing with `.swiftTestingOnly`, or run them serially with the `.disabled` option. ## Cases ### `disabled` ```swift case disabled ``` --- URL: "/generated/manifest/enums/TemplateString.Token" LLMS_URL: "/generated/manifest/enums/TemplateString.Token.md" --- **ENUM** # `TemplateString.Token` **Contents** - [Cases](#cases) - `projectName` ```swift public enum Token: String, Equatable ``` Provides a template for existing project properties. - projectName: The name of the project. ## Cases ### `projectName` ```swift case projectName = "${project_name}" ``` --- URL: "/generated/manifest/enums/Template.Contents" LLMS_URL: "/generated/manifest/enums/Template.Contents.md" --- **ENUM** # `Template.Contents` **Contents** - [Cases](#cases) - `string(_:)` - `file(_:)` - `directory(_:)` ```swift public enum Contents: Codable, Equatable, Sendable ``` Enum containing information about how to generate item ## Cases ### `string(_:)` ```swift case string(String) ``` String Contents is defined in `name_of_template.swift` and contains a simple `String` Can not contain any additional logic apart from plain `String` from `arguments` ### `file(_:)` ```swift case file(Path) ``` File content is defined in a different file from `name_of_template.swift` Can contain additional logic and anything that is defined in `ProjectDescriptionHelpers` ### `directory(_:)` ```swift case directory(Path) ``` Directory content is defined in a path It is just for copying files without modifications and logic inside --- URL: "/generated/manifest/enums/Template.Attribute" LLMS_URL: "/generated/manifest/enums/Template.Attribute.md" --- **ENUM** # `Template.Attribute` **Contents** - [Cases](#cases) - `required(_:)` - `optional(_:default:)` ```swift public enum Attribute: Codable, Equatable, Sendable ``` Attribute to be passed to `tuist scaffold` for generating with `Template` ## Cases ### `required(_:)` ```swift case required(String) ``` Required attribute with a given name ### `optional(_:default:)` ```swift case optional(String, default: Value) ``` Optional attribute with a given name and a default value used when attribute not provided by user --- URL: "/generated/manifest/enums/Template.Attribute.Value" LLMS_URL: "/generated/manifest/enums/Template.Attribute.Value.md" --- **ENUM** # `Template.Attribute.Value` **Contents** - [Cases](#cases) - `string(_:)` - `integer(_:)` - `real(_:)` - `boolean(_:)` - `dictionary(_:)` - `array(_:)` ```swift public indirect enum Value: Codable, Equatable, Sendable ``` This represents the default value type of Attribute ## Cases ### `string(_:)` ```swift case string(String) ``` It represents a string value. ### `integer(_:)` ```swift case integer(Int) ``` It represents an integer value. ### `real(_:)` ```swift case real(Double) ``` It represents a floating value. ### `boolean(_:)` ```swift case boolean(Bool) ``` It represents a boolean value. ### `dictionary(_:)` ```swift case dictionary([String: Value]) ``` It represents a dictionary value. ### `array(_:)` ```swift case array([Value]) ``` It represents an array value. --- URL: "/generated/manifest/enums/TargetScript.Script" LLMS_URL: "/generated/manifest/enums/TargetScript.Script.md" --- **ENUM** # `TargetScript.Script` **Contents** - [Cases](#cases) - `tool(path:args:)` - `scriptPath(path:args:)` - `embedded(_:)` ```swift public enum Script: Equatable, Codable, Sendable ``` Specifies how to execute the target script - tool: Executes the tool with the given arguments. Tuist will look up the tool on the environment's PATH. - scriptPath: Executes the file at the path with the given arguments. - text: Executes the embedded script. This should be a short command. ## Cases ### `tool(path:args:)` ```swift case tool(path: String, args: [String]) ``` ### `scriptPath(path:args:)` ```swift case scriptPath(path: Path, args: [String]) ``` ### `embedded(_:)` ```swift case embedded(String) ``` --- URL: "/generated/manifest/enums/TargetScript.Order" LLMS_URL: "/generated/manifest/enums/TargetScript.Order.md" --- **ENUM** # `TargetScript.Order` **Contents** - [Cases](#cases) - `pre` - `post` ```swift public enum Order: String, Codable, Equatable, Sendable ``` Order when the script gets executed. - pre: Before the sources and resources build phase. - post: After the sources and resources build phase. ## Cases ### `pre` ```swift case pre ``` ### `post` ```swift case post ``` --- URL: "/generated/manifest/enums/TargetDependency" LLMS_URL: "/generated/manifest/enums/TargetDependency.md" --- **ENUM** # `TargetDependency` **Contents** - [Cases](#cases) - `target(name:status:condition:)` - `macro(name:)` - `project(target:path:status:condition:)` - `framework(path:status:condition:)` - `library(path:publicHeaders:swiftModuleMap:condition:)` - `package(product:type:condition:)` - `sdk(name:type:status:condition:)` - `xcframework(path:expectedSignature:status:condition:)` - `xctest` - `external(name:condition:)` - [Properties](#properties) - `typeName` - [Methods](#methods) - `sdk(name:type:condition:)` - `target(_:condition:)` ```swift public enum TargetDependency: Codable, Hashable, Sendable ``` A target dependency. ## Cases ### `target(name:status:condition:)` ```swift case target(name: String, status: LinkingStatus = .required, condition: PlatformCondition? = nil) ``` Dependency on another target within the same project - Parameters: - name: Name of the target to depend on - status: The dependency status (optional dependencies are weakly linked) - condition: condition under which to use this dependency, `nil` if this should always be used ### `macro(name:)` ```swift case macro(name: String) ``` Dependency on a macro target within the same project - Parameters: - name: Name of the target to depend on ### `project(target:path:status:condition:)` ```swift case project(target: String, path: Path, status: LinkingStatus = .required, condition: PlatformCondition? = nil) ``` Dependency on a target within another project - Parameters: - target: Name of the target to depend on - path: Relative path to the other project directory - status: The dependency status (optional dependencies are weakly linked) - condition: condition under which to use this dependency, `nil` if this should always be used ### `framework(path:status:condition:)` ```swift case framework(path: Path, status: LinkingStatus = .required, condition: PlatformCondition? = nil) ``` Dependency on a prebuilt framework - Parameters: - path: Relative path to the prebuilt framework - status: The dependency status (optional dependencies are weakly linked) - condition: condition under which to use this dependency, `nil` if this should always be used ### `library(path:publicHeaders:swiftModuleMap:condition:)` ```swift case library(path: Path, publicHeaders: Path, swiftModuleMap: Path?, condition: PlatformCondition? = nil) ``` Dependency on prebuilt library - Parameters: - path: Relative path to the prebuilt library - publicHeaders: Relative path to the library's public headers directory - swiftModuleMap: Relative path to the library's swift module map file - condition: condition under which to use this dependency, `nil` if this should always be used ### `package(product:type:condition:)` ```swift case package(product: String, type: PackageType = .runtime, condition: PlatformCondition? = nil) ``` Dependency on a swift package manager product using Xcode native integration. It's recommended to use `external` instead. For more info, check the [external dependencies documentation ](https://docs.tuist.io/documentation/tuist/dependencies/#External-dependencies). - Parameters: - product: The name of the output product. ${PRODUCT_NAME} inside Xcode. e.g. RxSwift - type: The type of package being integrated. - condition: condition under which to use this dependency, `nil` if this should always be used ### `sdk(name:type:status:condition:)` ```swift case sdk(name: String, type: SDKType, status: LinkingStatus, condition: PlatformCondition? = nil) ``` Dependency on system library or framework - Parameters: - name: Name of the system library or framework (not including extension) e.g. `ARKit`, `c++` - type: The dependency type - status: The dependency status (optional dependencies are weakly linked) - condition: condition under which to use this dependency, `nil` if this should always be used ### `xcframework(path:expectedSignature:status:condition:)` ```swift case xcframework( path: Path, expectedSignature: XCFrameworkSignature? = nil, status: LinkingStatus = .required, condition: PlatformCondition? = nil ) ``` Dependency on a xcframework - Parameters: - path: Relative path to the xcframework - expectedSignature: The expected signature if the xcframework is signed. Used for verifying the xcframework's integrity against the actual fingerprint derived from the given xcframeowrk - status: The dependency status (optional dependencies are weakly linked) - condition: condition under which to use this dependency, `nil` if this should always be used ### `xctest` ```swift case xctest ``` Dependency on XCTest. ### `external(name:condition:)` ```swift case external(name: String, condition: PlatformCondition? = nil) ``` Dependency on an external dependency imported through `Package.swift`. - Parameters: - name: Name of the external dependency - condition: condition under which to use this dependency, `nil` if this should always be used ## Properties ### `typeName` ```swift public var typeName: String ``` ## Methods ### `sdk(name:type:condition:)` ```swift public static func sdk(name: String, type: SDKType, condition: PlatformCondition? = nil) -> TargetDependency ``` Dependency on system library or framework - Parameters: - name: Name of the system library or framework (including extension) e.g. `ARKit.framework`, `libc++.tbd` - type: Whether or not this dependecy is required. Defaults to `.required` - condition: condition under which to use this dependency, `nil` if this should always be used #### Parameters | Name | Description | | ---- | ----------- | | name | Name of the system library or framework (including extension) e.g. `ARKit.framework`, `libc++.tbd` | | type | Whether or not this dependecy is required. Defaults to `.required` | | condition | condition under which to use this dependency, `nil` if this should always be used | ### `target(_:condition:)` ```swift public static func target(_ target: Target, condition: PlatformCondition? = nil) -> TargetDependency ``` Dependency on another target within the same project. This is just syntactic sugar for `.target(name: target.name)`. - Parameters: - target: Instance of the target to depend on - condition: condition under which to use this dependency, `nil` if this should always be used #### Parameters | Name | Description | | ---- | ----------- | | target | Instance of the target to depend on | | condition | condition under which to use this dependency, `nil` if this should always be used | --- URL: "/generated/manifest/enums/TargetDependency.PackageType" LLMS_URL: "/generated/manifest/enums/TargetDependency.PackageType.md" --- **ENUM** # `TargetDependency.PackageType` **Contents** - [Cases](#cases) - `runtime` - `runtimeEmbedded` - `plugin` - `macro` ```swift public enum PackageType: Codable, Hashable, Sendable ``` ## Cases ### `runtime` ```swift case runtime ``` A runtime package type represents a standard package whose sources are linked at runtime. For example importing the framework and consuming from dependent targets. ### `runtimeEmbedded` ```swift case runtimeEmbedded ``` A runtime embedded package type represents a package that's embedded in the product at runtime. ### `plugin` ```swift case plugin ``` A plugin package represents a package that's loaded by the build system at compile-time to extend the compilation process. ### `macro` ```swift case macro ``` A macro package represents a package that contains a Swift Macro. --- URL: "/generated/manifest/enums/SwiftOptimizationLevel" LLMS_URL: "/generated/manifest/enums/SwiftOptimizationLevel.md" --- **ENUM** # `SwiftOptimizationLevel` **Contents** - [Cases](#cases) - `o` - `oNone` - `oSize` ```swift public enum SwiftOptimizationLevel: String ``` ## Cases ### `o` ```swift case o = "-O" ``` ### `oNone` ```swift case oNone = "-Onone" ``` ### `oSize` ```swift case oSize = "-Osize" ``` --- URL: "/generated/manifest/enums/SwiftCompilationMode" LLMS_URL: "/generated/manifest/enums/SwiftCompilationMode.md" --- **ENUM** # `SwiftCompilationMode` **Contents** - [Cases](#cases) - `singlefile` - `wholemodule` ```swift public enum SwiftCompilationMode: String ``` ## Cases ### `singlefile` ```swift case singlefile ``` ### `wholemodule` ```swift case wholemodule ``` --- URL: "/generated/manifest/enums/SourceFileGlob.FileType" LLMS_URL: "/generated/manifest/enums/SourceFileGlob.FileType.md" --- **ENUM** # `SourceFileGlob.FileType` **Contents** - [Cases](#cases) - `alwaysPresent` - `generated` ```swift public enum FileType: String, Codable, Sendable ``` Type of the source file. ## Cases ### `alwaysPresent` ```swift case alwaysPresent ``` File is already present on disk before generating the project. ### `generated` ```swift case generated ``` File is generated, meaning it wasn't initially present on disk at the time of project generation. For example, a file created by a pre-build phase script. - Important: Since generated files do not exist at the time of project generation, their content cannot be hashed, which affects the ability to identify changes in their content during caching. Note that specifically for files generated by a pre-build phase script, the input and output file paths are part of the target's hash, if they are specified. --- URL: "/generated/manifest/enums/SettingValue" LLMS_URL: "/generated/manifest/enums/SettingValue.md" --- **ENUM** # `SettingValue` **Contents** - [Cases](#cases) - `string(_:)` - `array(_:)` - [Methods](#methods) - `init(stringLiteral:)` - `init(arrayLiteral:)` - `init(booleanLiteral:)` - `init(_:)` ```swift public enum SettingValue: ExpressibleByStringInterpolation, ExpressibleByArrayLiteral, ExpressibleByBooleanLiteral, Equatable, Codable, Sendable ``` A value or a collection of values used for settings configuration. ## Cases ### `string(_:)` ```swift case string(String) ``` ### `array(_:)` ```swift case array([String]) ``` ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | ### `init(arrayLiteral:)` ```swift public init(arrayLiteral elements: String...) ``` ### `init(booleanLiteral:)` ```swift public init(booleanLiteral value: Bool) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | ### `init(_:)` ```swift public init(_ stringRawRepresentable: T) where T: RawRepresentable, T.RawValue == String ``` --- URL: "/generated/manifest/enums/ScreenCaptureFormat" LLMS_URL: "/generated/manifest/enums/ScreenCaptureFormat.md" --- **ENUM** # `ScreenCaptureFormat` **Contents** - [Cases](#cases) - `screenshots` - `screenRecording` ```swift public enum ScreenCaptureFormat: String, Codable, Sendable ``` Preferred screen capture format for UI tests results in Xcode 15+ Available options are screen recordings and screenshots. In Xcode 15 screen recordings are enabled by default (in favour of screenshots). This setting is ignored by Xcode 14.x and prior. ## Cases ### `screenshots` ```swift case screenshots ``` Screenshots ### `screenRecording` ```swift case screenRecording ``` Automatic screen recordings --- URL: "/generated/manifest/enums/SDKType" LLMS_URL: "/generated/manifest/enums/SDKType.md" --- **ENUM** # `SDKType` **Contents** - [Cases](#cases) - `library` - `swiftLibrary` - `framework` ```swift public enum SDKType: String, Codable, Hashable, Sendable ``` Dependency type used by `.sdk` target dependencies ## Cases ### `library` ```swift case library ``` Library SDK dependency Libraries are located in: `{path-to-xcode}.app/Contents/Developer/Platforms/{platform}.platform/Developer/SDKs/{runtime}.sdk/usr/lib` ### `swiftLibrary` ```swift case swiftLibrary ``` Swift library SDK dependency Swift libraries are located in: `{path-to-xcode}.app/Contents/Developer/Platforms/{platform}.platform/Developer/SDKs/{runtime}.sdk/usr/lib/swift` ### `framework` ```swift case framework ``` Framework SDK dependency --- URL: "/generated/manifest/enums/RunActionOptions.GPUFrameCaptureMode" LLMS_URL: "/generated/manifest/enums/RunActionOptions.GPUFrameCaptureMode.md" --- **ENUM** # `RunActionOptions.GPUFrameCaptureMode` **Contents** - [Cases](#cases) - `autoEnabled` - `metal` - `openGL` - `disabled` - [Properties](#properties) - `default` ```swift public enum GPUFrameCaptureMode: String, Codable, Equatable, Sendable ``` ## Cases ### `autoEnabled` ```swift case autoEnabled ``` ### `metal` ```swift case metal ``` ### `openGL` ```swift case openGL ``` ### `disabled` ```swift case disabled ``` ## Properties ### `default` ```swift public static var `default`: GPUFrameCaptureMode ``` --- URL: "/generated/manifest/enums/ResourceSynthesizer.TemplateType" LLMS_URL: "/generated/manifest/enums/ResourceSynthesizer.TemplateType.md" --- **ENUM** # `ResourceSynthesizer.TemplateType` **Contents** - [Cases](#cases) - `plugin(name:resourceName:)` - `defaultTemplate(resourceName:)` ```swift public enum TemplateType: Codable, Equatable, Sendable ``` Templates can be either a local template file, from a plugin, or a default template from tuist ## Cases ### `plugin(name:resourceName:)` ```swift case plugin(name: String, resourceName: String) ``` Plugin template file `name` is a name of a plugin `resourceName` is a name of the resource - that is used for finding a template as well as naming the resulting `.swift` file ### `defaultTemplate(resourceName:)` ```swift case defaultTemplate(resourceName: String) ``` Default template defined `Tuist/{ProjectName}`, or if not present there, in tuist itself `resourceName` is used for the name of the resulting `.swift` file --- URL: "/generated/manifest/enums/ResourceSynthesizer.Parser" LLMS_URL: "/generated/manifest/enums/ResourceSynthesizer.Parser.md" --- **ENUM** # `ResourceSynthesizer.Parser` **Contents** - [Cases](#cases) - `strings` - `assets` - `plists` - `fonts` - `coreData` - `interfaceBuilder` - `json` - `yaml` - `files` ```swift public enum Parser: String, Codable, Sendable ``` There are multiple parsers you can choose from Each parser will give you different metadata from a file You can read more about available parsers and how to use their metadata here: https://github.com/SwiftGen/SwiftGen#available-parsers ## Cases ### `strings` ```swift case strings ``` ### `assets` ```swift case assets ``` ### `plists` ```swift case plists ``` ### `fonts` ```swift case fonts ``` ### `coreData` ```swift case coreData ``` ### `interfaceBuilder` ```swift case interfaceBuilder ``` ### `json` ```swift case json ``` ### `yaml` ```swift case yaml ``` ### `files` ```swift case files ``` --- URL: "/generated/manifest/enums/ResourceFileElement" LLMS_URL: "/generated/manifest/enums/ResourceFileElement.md" --- **ENUM** # `ResourceFileElement` **Contents** - [Cases](#cases) - `glob(pattern:excluding:tags:inclusionCondition:)` - `folderReference(path:tags:inclusionCondition:)` ```swift public enum ResourceFileElement: Codable, Equatable, Sendable, Hashable ``` A resource file element from a glob pattern or a folder reference. - glob: a glob pattern for files to include - folderReference: a single path to a directory Note: For convenience, an element can be represented as a string literal `"some/pattern/**"` is the equivalent of `ResourceFileElement.glob(pattern: "some/pattern/**")` ## Cases ### `glob(pattern:excluding:tags:inclusionCondition:)` ```swift case glob(pattern: Path, excluding: [Path] = [], tags: [String] = [], inclusionCondition: PlatformCondition? = nil) ``` A glob pattern of files to include and ODR tags ### `folderReference(path:tags:inclusionCondition:)` ```swift case folderReference(path: Path, tags: [String] = [], inclusionCondition: PlatformCondition? = nil) ``` Relative path to a directory to include as a folder reference and ODR tags --- URL: "/generated/manifest/enums/Project.Options.AutomaticSchemesOptions" LLMS_URL: "/generated/manifest/enums/Project.Options.AutomaticSchemesOptions.md" --- **ENUM** # `Project.Options.AutomaticSchemesOptions` **Contents** - [Cases](#cases) - `enabled(targetSchemesGrouping:codeCoverageEnabled:testingOptions:testLanguage:testRegion:testScreenCaptureFormat:runLanguage:runRegion:)` - `disabled` ```swift public enum AutomaticSchemesOptions: Codable, Equatable, Sendable ``` Automatic schemes options allow customizing the generation of the target schemes. ## Cases ### `enabled(targetSchemesGrouping:codeCoverageEnabled:testingOptions:testLanguage:testRegion:testScreenCaptureFormat:runLanguage:runRegion:)` ```swift case enabled( targetSchemesGrouping: TargetSchemesGrouping = .byNameSuffix( build: ["Implementation", "Interface", "Mocks", "Testing"], test: ["Tests", "IntegrationTests", "UITests", "SnapshotTests"], run: ["App", "Demo", "Example"] ), codeCoverageEnabled: Bool = false, testingOptions: TestingOptions = [], testLanguage: SchemeLanguage? = nil, testRegion: String? = nil, testScreenCaptureFormat: ScreenCaptureFormat? = nil, runLanguage: SchemeLanguage? = nil, runRegion: String? = nil ) ``` Enable autogenerated schemes ### `disabled` ```swift case disabled ``` Disable autogenerated schemes --- URL: "/generated/manifest/enums/Product" LLMS_URL: "/generated/manifest/enums/Product.md" --- **ENUM** # `Product` **Contents** - [Cases](#cases) - `app` - `staticLibrary` - `dynamicLibrary` - `framework` - `staticFramework` - `unitTests` - `uiTests` - `bundle` - `commandLineTool` - `appClip` - `appExtension` - `watch2App` - `watch2Extension` - `tvTopShelfExtension` - `messagesExtension` - `stickerPackExtension` - `xpc` - `systemExtension` - `extensionKitExtension` - `macro` ```swift public enum Product: String, Codable, Equatable, Sendable ``` Possible products types. ## Cases ### `app` ```swift case app ``` An application. ### `staticLibrary` ```swift case staticLibrary = "static_library" ``` A static library. ### `dynamicLibrary` ```swift case dynamicLibrary = "dynamic_library" ``` A dynamic library. ### `framework` ```swift case framework ``` A dynamic framework. ### `staticFramework` ```swift case staticFramework ``` A static framework. ### `unitTests` ```swift case unitTests = "unit_tests" ``` A unit tests bundle. ### `uiTests` ```swift case uiTests = "ui_tests" ``` A UI tests bundle. ### `bundle` ```swift case bundle ``` A custom bundle. (currently only iOS resource bundles are supported). ### `commandLineTool` ```swift case commandLineTool ``` A command line tool (macOS platform only). ### `appClip` ```swift case appClip ``` An appClip. (iOS platform only). ### `appExtension` ```swift case appExtension = "app_extension" ``` An application extension. ### `watch2App` ```swift case watch2App ``` A Watch application. (watchOS platform only) . ### `watch2Extension` ```swift case watch2Extension ``` A Watch application extension. (watchOS platform only). ### `tvTopShelfExtension` ```swift case tvTopShelfExtension ``` A TV Top Shelf Extension. ### `messagesExtension` ```swift case messagesExtension ``` An iMessage extension. (iOS platform only) ### `stickerPackExtension` ```swift case stickerPackExtension = "sticker_pack_extension" ``` A sticker pack extension. ### `xpc` ```swift case xpc ``` An XPC. (macOS platform only). ### `systemExtension` ```swift case systemExtension ``` An system extension. (macOS platform only). ### `extensionKitExtension` ```swift case extensionKitExtension = "extension_kit_extension" ``` An ExtensionKit extension. ### `macro` ```swift case macro ``` A Swift Macro Although Apple doesn't officially support Swift Macro Xcode Project targets, we enable them by adding a command line tool target, a target dependency in the dependent targets, and the right build settings to use the macro executable. --- URL: "/generated/manifest/enums/PluginLocation.LocationType" LLMS_URL: "/generated/manifest/enums/PluginLocation.LocationType.md" --- **ENUM** # `PluginLocation.LocationType` **Contents** - [Cases](#cases) - `local(path:)` - `gitWithTag(url:tag:directory:releaseUrl:)` - `gitWithSha(url:sha:directory:)` ```swift public enum LocationType: Codable, Equatable, Sendable ``` ## Cases ### `local(path:)` ```swift case local(path: Path) ``` ### `gitWithTag(url:tag:directory:releaseUrl:)` ```swift case gitWithTag(url: String, tag: String, directory: String?, releaseUrl: String?) ``` ### `gitWithSha(url:sha:directory:)` ```swift case gitWithSha(url: String, sha: String, directory: String?) ``` --- URL: "/generated/manifest/enums/Plist" LLMS_URL: "/generated/manifest/enums/Plist.md" --- **ENUM** # `Plist` ```swift public enum Plist ``` --- URL: "/generated/manifest/enums/Plist.Value" LLMS_URL: "/generated/manifest/enums/Plist.Value.md" --- **ENUM** # `Plist.Value` **Contents** - [Cases](#cases) - `string(_:)` - `integer(_:)` - `real(_:)` - `boolean(_:)` - `dictionary(_:)` - `array(_:)` ```swift public indirect enum Value: Codable, Equatable, Sendable ``` It represents the values of the .plist or .entitlements file dictionary. It ensures that the values used to define the content of the dynamically generated .plist or .entitlements files are valid ## Cases ### `string(_:)` ```swift case string(String) ``` It represents a string value. ### `integer(_:)` ```swift case integer(Int) ``` It represents an integer value. ### `real(_:)` ```swift case real(Double) ``` It represents a floating value. ### `boolean(_:)` ```swift case boolean(Bool) ``` It represents a boolean value. ### `dictionary(_:)` ```swift case dictionary([String: Value]) ``` It represents a dictionary value. ### `array(_:)` ```swift case array([Value]) ``` It represents an array value. --- URL: "/generated/manifest/enums/PlatformFilter" LLMS_URL: "/generated/manifest/enums/PlatformFilter.md" --- **ENUM** # `PlatformFilter` **Contents** - [Cases](#cases) - `ios` - `macos` - `tvos` - `catalyst` - `driverkit` - `watchos` - `visionos` ```swift public enum PlatformFilter: Comparable, Hashable, Codable, CaseIterable, Sendable ``` ## Cases ### `ios` ```swift case ios ``` ### `macos` ```swift case macos ``` ### `tvos` ```swift case tvos ``` ### `catalyst` ```swift case catalyst ``` ### `driverkit` ```swift case driverkit ``` ### `watchos` ```swift case watchos ``` ### `visionos` ```swift case visionos ``` --- URL: "/generated/manifest/enums/Platform" LLMS_URL: "/generated/manifest/enums/Platform.md" --- **ENUM** # `Platform` **Contents** - [Cases](#cases) - `iOS` - `macOS` - `watchOS` - `tvOS` - `visionOS` ```swift public enum Platform: String, Codable, Equatable, CaseIterable, Sendable ``` A supported platform representation. ## Cases ### `iOS` ```swift case iOS = "ios" ``` The iOS platform ### `macOS` ```swift case macOS = "macos" ``` The macOS platform ### `watchOS` ```swift case watchOS = "watchos" ``` The watchOS platform ### `tvOS` ```swift case tvOS = "tvos" ``` The tvOS platform ### `visionOS` ```swift case visionOS = "visionos" ``` The visionOS platform --- URL: "/generated/manifest/enums/Path.PathType" LLMS_URL: "/generated/manifest/enums/Path.PathType.md" --- **ENUM** # `Path.PathType` **Contents** - [Cases](#cases) - `relativeToCurrentFile` - `relativeToManifest` - `relativeToRoot` ```swift public enum PathType: String, Codable, Sendable ``` ## Cases ### `relativeToCurrentFile` ```swift case relativeToCurrentFile ``` ### `relativeToManifest` ```swift case relativeToManifest ``` ### `relativeToRoot` ```swift case relativeToRoot ``` --- URL: "/generated/manifest/enums/Parser.Option" LLMS_URL: "/generated/manifest/enums/Parser.Option.md" --- **ENUM** # `Parser.Option` **Contents** - [Cases](#cases) - `string(_:)` - `integer(_:)` - `double(_:)` - `boolean(_:)` - `dictionary(_:)` - `array(_:)` ```swift public enum Option: Equatable, Codable, Sendable ``` ## Cases ### `string(_:)` ```swift case string(String) ``` It represents a string value. ### `integer(_:)` ```swift case integer(Int) ``` It represents an integer value. ### `double(_:)` ```swift case double(Double) ``` It represents a floating value. ### `boolean(_:)` ```swift case boolean(Bool) ``` It represents a boolean value. ### `dictionary(_:)` ```swift case dictionary([String: Option]) ``` It represents a dictionary value. ### `array(_:)` ```swift case array([Option]) ``` It represents an array value. --- URL: "/generated/manifest/enums/PackagePlatform" LLMS_URL: "/generated/manifest/enums/PackagePlatform.md" --- **ENUM** # `PackagePlatform` **Contents** - [Cases](#cases) - `iOS` - `macOS` - `macCatalyst` - `watchOS` - `tvOS` - `visionOS` ```swift public enum PackagePlatform: String, Codable, Equatable, CaseIterable, Sendable ``` A supported Swift Package Manager platform representation. ## Cases ### `iOS` ```swift case iOS = "ios" ``` The iOS platform ### `macOS` ```swift case macOS = "macos" ``` The macOS platform ### `macCatalyst` ```swift case macCatalyst = "maccatalyst" ``` The Mac Catalyst platform ### `watchOS` ```swift case watchOS = "watchos" ``` The watchOS platform ### `tvOS` ```swift case tvOS = "tvos" ``` The tvOS platform ### `visionOS` ```swift case visionOS = "visionos" ``` The visionOS platform --- URL: "/generated/manifest/enums/Package" LLMS_URL: "/generated/manifest/enums/Package.md" --- **ENUM** # `Package` **Contents** - [Cases](#cases) - `remote(url:requirement:)` - `registry(identifier:requirement:)` - `local(path:)` ```swift public enum Package: Equatable, Codable, Sendable ``` A dependency of a Swift package. A package dependency can be either: - remote: A Git URL to the source of the package, and a requirement for the version of the package. - local: A relative path to the package. ## Cases ### `remote(url:requirement:)` ```swift case remote(url: String, requirement: Requirement) ``` ### `registry(identifier:requirement:)` ```swift case registry(identifier: String, requirement: Requirement) ``` ### `local(path:)` ```swift case local(path: Path) ``` --- URL: "/generated/manifest/enums/Package.Requirement" LLMS_URL: "/generated/manifest/enums/Package.Requirement.md" --- **ENUM** # `Package.Requirement` **Contents** - [Cases](#cases) - `upToNextMajor(from:)` - `upToNextMinor(from:)` - `range(from:to:)` - `exact(_:)` - `branch(_:)` - `revision(_:)` ```swift public enum Requirement: Codable, Equatable, Sendable ``` ## Cases ### `upToNextMajor(from:)` ```swift case upToNextMajor(from: Version) ``` ### `upToNextMinor(from:)` ```swift case upToNextMinor(from: Version) ``` ### `range(from:to:)` ```swift case range(from: Version, to: Version) ``` ### `exact(_:)` ```swift case exact(Version) ``` ### `branch(_:)` ```swift case branch(String) ``` ### `revision(_:)` ```swift case revision(String) ``` --- URL: "/generated/manifest/enums/MergedBinaryType" LLMS_URL: "/generated/manifest/enums/MergedBinaryType.md" --- **ENUM** # `MergedBinaryType` **Contents** - [Cases](#cases) - `disabled` - `automatic` - `manual(mergeableDependencies:)` ```swift public enum MergedBinaryType: Equatable, Codable, Sendable ``` Represents the different options to configure a target for mergeable libraries https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries ## Cases ### `disabled` ```swift case disabled ``` Target is never going to merge available dependencies ### `automatic` ```swift case automatic ``` Target is going to merge direct target dependencies (just the ones declared as part of it's project). With this build setting, Xcode treats mergeable dependencies like normal dynamic libraries in debug builds, but performs steps in release mode to automatically handle merging for **direct dependencies** A direct dependency is a library that meets two criteria: - The library is listed in your target’s Link Binary with Libraries build phase. - The library is the product of another target in your project. ### `manual(mergeableDependencies:)` ```swift case manual(mergeableDependencies: Set) ``` Target is going to merge direct and specified dependencies that are not part of the project. The set of dependencies is going to reflect the list of precompiled dynamic dependencies you want to merge as part of the target. These binaries must be compiled with `MAKE_MERGEABLE` flag set to true In some cases, you may want to manually configure merging between your app or framework target and dependent libraries. For example, you might not want to automatically merge dependencies that you share between an app and an app extension if you’re concerned about the app extension’s binary size. To set up manual merging, configure your app or framework target, then configure your dependent libraries. In your app or framework target, add the flag `mergedBinaryType` and set it to manual. After you add that setting to your target: - In release builds, Xcode merges the products of any of its direct dependencies which have MAKE_MERGEABLE enabled using the linker flags -merge_framework, -merge-l and so on. - In debug builds, Xcode links any of your target’s direct dependencies which have MERGEABLE_LIBRARY enabled, but not MAKE_MERGEABLE with the linker flags -reexport_framework, -reexport-l, and so on. - Xcode uses normal linking for targets that don’t have MERGEABLE_LIBRARY enabled. This is the same linking that Xcode uses for static libraries, or dynamic libraries that aren’t mergeable. --- URL: "/generated/manifest/enums/LinkingStatus" LLMS_URL: "/generated/manifest/enums/LinkingStatus.md" --- **ENUM** # `LinkingStatus` **Contents** - [Cases](#cases) - `required` - `optional` - `none` ```swift public enum LinkingStatus: String, Codable, Hashable, Sendable ``` Dependency status used by dependencies ## Cases ### `required` ```swift case required ``` Required dependency ### `optional` ```swift case optional ``` Optional dependency (weakly linked) ### `none` ```swift case none ``` Skip linking --- URL: "/generated/manifest/enums/LaunchStyle" LLMS_URL: "/generated/manifest/enums/LaunchStyle.md" --- **ENUM** # `LaunchStyle` **Contents** - [Cases](#cases) - `automatically` - `waitForExecutableToBeLaunched` ```swift public enum LaunchStyle: Codable, Sendable ``` ## Cases ### `automatically` ```swift case automatically ``` ### `waitForExecutableToBeLaunched` ```swift case waitForExecutableToBeLaunched ``` --- URL: "/generated/manifest/enums/InfoPlist" LLMS_URL: "/generated/manifest/enums/InfoPlist.md" --- **ENUM** # `InfoPlist` **Contents** - [Cases](#cases) - `file(path:)` - `dictionary(_:)` - `extendingDefault(with:)` - [Properties](#properties) - `default` - `path` ```swift public enum InfoPlist: Codable, Equatable, Sendable ``` A info plist from a file, a custom dictonary or a extended defaults. ## Cases ### `file(path:)` ```swift case file(path: Path) ``` The path to an existing Info.plist file. ### `dictionary(_:)` ```swift case dictionary([String: Plist.Value]) ``` A dictionary with the Info.plist content. Tuist generates the Info.plist file at the generation time. ### `extendingDefault(with:)` ```swift case extendingDefault(with: [String: Plist.Value]) ``` Generate an Info.plist file with the default content for the target product extended with the values in the given dictionary. ## Properties ### `default` ```swift public static var `default`: InfoPlist ``` Generate the default content for the target the InfoPlist belongs to. ### `path` ```swift public var path: Path? ``` --- URL: "/generated/manifest/enums/InfoPlist.CodingError" LLMS_URL: "/generated/manifest/enums/InfoPlist.CodingError.md" --- **ENUM** # `InfoPlist.CodingError` **Contents** - [Cases](#cases) - `invalidType(_:)` ```swift public enum CodingError: Error ``` ## Cases ### `invalidType(_:)` ```swift case invalidType(String) ``` --- URL: "/generated/manifest/enums/Headers.AutomaticExclusionRule" LLMS_URL: "/generated/manifest/enums/Headers.AutomaticExclusionRule.md" --- **ENUM** # `Headers.AutomaticExclusionRule` **Contents** - [Cases](#cases) - `projectExcludesPrivateAndPublic` - `publicExcludesPrivateAndProject` ```swift public enum AutomaticExclusionRule: Int, Codable, Sendable ``` Determine how to resolve cases, when the same files found in different header scopes ## Cases ### `projectExcludesPrivateAndPublic` ```swift case projectExcludesPrivateAndPublic ``` Project headers = all found - private headers - public headers Order of tuist search: 1) Public headers 2) Private headers (with auto excludes all found public headers) 3) Project headers (with excluding public/private headers) Also tuist doesn't ignore all excludes, which had been set by `excluding` param ### `publicExcludesPrivateAndProject` ```swift case publicExcludesPrivateAndProject ``` Public headers = all found - private headers - project headers Order of tuist search (reverse search): 1) Project headers 2) Private headers (with auto excludes all found project headers) 3) Public headers (with excluding project/private headers) Also tuist doesn't ignore all excludes, which had been set by `excluding` param --- URL: "/generated/manifest/enums/GenerationOptions.StaticSideEffectsWarningTargets" LLMS_URL: "/generated/manifest/enums/GenerationOptions.StaticSideEffectsWarningTargets.md" --- **ENUM** # `GenerationOptions.StaticSideEffectsWarningTargets` **Contents** - [Cases](#cases) - `all` - `none` - `excluding(_:)` ```swift public enum StaticSideEffectsWarningTargets: Codable, Equatable, Sendable ``` This enum represents the targets against which Tuist will run the check for potential side effects caused by static transitive dependencies. ## Cases ### `all` ```swift case all ``` ### `none` ```swift case none ``` ### `excluding(_:)` ```swift case excluding([String]) ``` --- URL: "/generated/manifest/enums/GenerationOptions.AutogeneratedWorkspaceSchemes" LLMS_URL: "/generated/manifest/enums/GenerationOptions.AutogeneratedWorkspaceSchemes.md" --- **ENUM** # `GenerationOptions.AutogeneratedWorkspaceSchemes` **Contents** - [Cases](#cases) - `disabled` - `enabled(codeCoverageMode:testingOptions:testLanguage:testRegion:testScreenCaptureFormat:)` ```swift public enum AutogeneratedWorkspaceSchemes: Codable, Equatable, Sendable ``` Contains options for autogenerated workspace schemes ## Cases ### `disabled` ```swift case disabled ``` Tuist will not automatically generate any schemes ### `enabled(codeCoverageMode:testingOptions:testLanguage:testRegion:testScreenCaptureFormat:)` ```swift case enabled( codeCoverageMode: CodeCoverageMode = .disabled, testingOptions: TestingOptions = [], testLanguage: SchemeLanguage? = nil, testRegion: String? = nil, testScreenCaptureFormat: ScreenCaptureFormat? = nil ) ``` Tuist will generate schemes with the associated testing options --- URL: "/generated/manifest/enums/FileHeaderTemplate" LLMS_URL: "/generated/manifest/enums/FileHeaderTemplate.md" --- **ENUM** # `FileHeaderTemplate` **Contents** - [Cases](#cases) - `file(_:)` - `string(_:)` - [Methods](#methods) - `init(stringLiteral:)` ```swift public enum FileHeaderTemplate: Codable, Equatable, ExpressibleByStringInterpolation, Sendable ``` A header template from a file or a string. Lets you define custom file header template for built-in Xcode templates, e.g. when you create new Swift file you can automatically have your custom define file header. Tuist automatically performs several template transformations for you - if your template starts with comment slashes (`//`) we remove them as they are added automatically by Xcode - if your template doesn't start with comment and whitespace or newline, we add a space - otherwise your header would be glued to implicit comment slashes which you probably do not want - if your template has trailing newline, we remove it as it is implicitly added by Xcode ## Cases ### `file(_:)` ```swift case file(Path) ``` Load template stored in file ### `string(_:)` ```swift case string(String) ``` Use inline string as template ## Methods ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` Creates file template as `.string(value)` --- URL: "/generated/manifest/enums/FileElement" LLMS_URL: "/generated/manifest/enums/FileElement.md" --- **ENUM** # `FileElement` **Contents** - [Cases](#cases) - `glob(pattern:)` - `folderReference(path:)` ```swift public enum FileElement: Codable, Equatable, Sendable ``` A file element from a glob pattern or a folder reference. - glob: a glob pattern for files to include - folderReference: a single path to a directory Note: For convenience, an element can be represented as a string literal `"some/pattern/**"` is the equivalent of `FileElement.glob(pattern: "some/pattern/**")` ## Cases ### `glob(pattern:)` ```swift case glob(pattern: Path) ``` A file path (or glob pattern) to include. For convenience, a string literal can be used as an alternate way to specify this option. ### `folderReference(path:)` ```swift case folderReference(path: Path) ``` A directory path to include as a folder reference. --- URL: "/generated/manifest/enums/FileCodeGen" LLMS_URL: "/generated/manifest/enums/FileCodeGen.md" --- **ENUM** # `FileCodeGen` **Contents** - [Cases](#cases) - `public` - `private` - `project` - `disabled` ```swift public enum FileCodeGen: String, Codable, Equatable, Sendable ``` Options for source file code generation. ## Cases ### `public` ```swift case `public` ``` Public codegen ### `private` ```swift case `private` ``` Private codegen ### `project` ```swift case project ``` Project codegen ### `disabled` ```swift case disabled ``` Disabled codegen --- URL: "/generated/manifest/enums/Environment" LLMS_URL: "/generated/manifest/enums/Environment.md" --- **ENUM** # `Environment` ```swift public enum Environment ``` A convenience structure to read environment variables. --- URL: "/generated/manifest/enums/Environment.Value" LLMS_URL: "/generated/manifest/enums/Environment.Value.md" --- **ENUM** # `Environment.Value` **Contents** - [Cases](#cases) - `string(_:)` ```swift public enum Value: Equatable ``` ## Cases ### `string(_:)` ```swift case string(String) ``` --- URL: "/generated/manifest/enums/Entitlements" LLMS_URL: "/generated/manifest/enums/Entitlements.md" --- **ENUM** # `Entitlements` **Contents** - [Cases](#cases) - `file(path:)` - `dictionary(_:)` - `variable(_:)` - [Properties](#properties) - `path` ```swift public enum Entitlements: Codable, Equatable, Sendable ``` ## Cases ### `file(path:)` ```swift case file(path: Path) ``` The path to an existing .entitlements file. ### `dictionary(_:)` ```swift case dictionary([String: Plist.Value]) ``` A dictionary with the entitlements content. Tuist generates the .entitlements file at the generation time. ### `variable(_:)` ```swift case variable(String) ``` A user defined xcconfig variable map to .entitlements file. This should be used when the project has different entitlements files per config (aka: debug,release,staging,etc) ```` .target( ... entitlements: .variable("$(ENTITLEMENT_FILE_VARIABLE)"), ) ```` Or as literal string ```` .target( ... entitlements: $(ENTITLEMENT_FILE_VARIABLE), ) ```` ## Properties ### `path` ```swift public var path: Path? ``` --- URL: "/generated/manifest/enums/Entitlements.CodingError" LLMS_URL: "/generated/manifest/enums/Entitlements.CodingError.md" --- **ENUM** # `Entitlements.CodingError` **Contents** - [Cases](#cases) - `invalidType(_:)` ```swift public enum CodingError: Error ``` ## Cases ### `invalidType(_:)` ```swift case invalidType(String) ``` --- URL: "/generated/manifest/enums/Destination" LLMS_URL: "/generated/manifest/enums/Destination.md" --- **ENUM** # `Destination` **Contents** - [Cases](#cases) - `iPhone` - `iPad` - `mac` - `macWithiPadDesign` - `macCatalyst` - `appleWatch` - `appleTv` - `appleVision` - `appleVisionWithiPadDesign` - [Properties](#properties) - `platform` ```swift public enum Destination: String, Codable, Equatable, CaseIterable, Sendable ``` A supported deployment destination representation. ## Cases ### `iPhone` ```swift case iPhone ``` iPhone support ### `iPad` ```swift case iPad ``` iPad support ### `mac` ```swift case mac ``` Native macOS support ### `macWithiPadDesign` ```swift case macWithiPadDesign ``` macOS support using iPad design ### `macCatalyst` ```swift case macCatalyst ``` mac Catalyst support ### `appleWatch` ```swift case appleWatch ``` watchOS support ### `appleTv` ```swift case appleTv ``` tvOS support ### `appleVision` ```swift case appleVision ``` visionOS support ### `appleVisionWithiPadDesign` ```swift case appleVisionWithiPadDesign ``` visionOS support using iPad design ## Properties ### `platform` ```swift public var platform: Platform ``` SDK Platform of a destination --- URL: "/generated/manifest/enums/DefaultSettings" LLMS_URL: "/generated/manifest/enums/DefaultSettings.md" --- **ENUM** # `DefaultSettings` **Contents** - [Cases](#cases) - `recommended(excluding:)` - `essential(excluding:)` - `none` ```swift public enum DefaultSettings: Codable, Equatable, Sendable ``` Specifies the default set of settings applied to all the projects and targets. The default settings can be overridden via `Settings base: SettingsDictionary` and `Configuration settings: SettingsDictionary`. ## Cases ### `recommended(excluding:)` ```swift case recommended(excluding: Set = []) ``` Recommended settings including warning flags to help you catch some of the bugs at the early stage of development. If you need to override certain settings in a `Configuration` it's possible to add those keys to `excluding`. ### `essential(excluding:)` ```swift case essential(excluding: Set = []) ``` A minimal set of settings to make the project compile without any additional settings for example `PRODUCT_NAME` or `TARGETED_DEVICE_FAMILY`. If you need to override certain settings in a Configuration it's possible to add those keys to `excluding`. ### `none` ```swift case none ``` Tuist won't generate any build settings for the target or project. --- URL: "/generated/manifest/enums/DebugInformationFormat" LLMS_URL: "/generated/manifest/enums/DebugInformationFormat.md" --- **ENUM** # `DebugInformationFormat` **Contents** - [Cases](#cases) - `dwarf` - `dwarfWithDsym` ```swift public enum DebugInformationFormat: String ``` ## Cases ### `dwarf` ```swift case dwarf ``` ### `dwarfWithDsym` ```swift case dwarfWithDsym = "dwarf-with-dsym" ``` --- URL: "/generated/manifest/enums/CopyFilesAction.Destination" LLMS_URL: "/generated/manifest/enums/CopyFilesAction.Destination.md" --- **ENUM** # `CopyFilesAction.Destination` **Contents** - [Cases](#cases) - `absolutePath` - `productsDirectory` - `wrapper` - `executables` - `resources` - `javaResources` - `frameworks` - `sharedFrameworks` - `sharedSupport` - `plugins` - `other` ```swift public enum Destination: String, Codable, Equatable, Sendable ``` Destination path. ## Cases ### `absolutePath` ```swift case absolutePath ``` ### `productsDirectory` ```swift case productsDirectory ``` ### `wrapper` ```swift case wrapper ``` ### `executables` ```swift case executables ``` ### `resources` ```swift case resources ``` ### `javaResources` ```swift case javaResources ``` ### `frameworks` ```swift case frameworks ``` ### `sharedFrameworks` ```swift case sharedFrameworks ``` ### `sharedSupport` ```swift case sharedSupport ``` ### `plugins` ```swift case plugins ``` ### `other` ```swift case other ``` --- URL: "/generated/manifest/enums/CopyFileElement" LLMS_URL: "/generated/manifest/enums/CopyFileElement.md" --- **ENUM** # `CopyFileElement` **Contents** - [Cases](#cases) - `glob(pattern:condition:codeSignOnCopy:)` - `folderReference(path:condition:codeSignOnCopy:)` ```swift public enum CopyFileElement: Codable, Equatable, Sendable ``` A file element from a glob pattern or a folder reference which is conditionally applied to specific platforms with an optional "Code Sign On Copy" flag. ## Cases ### `glob(pattern:condition:codeSignOnCopy:)` ```swift case glob(pattern: Path, condition: PlatformCondition? = nil, codeSignOnCopy: Bool = false) ``` A file path (or glob pattern) to include with an optional PlatformCondition to control which platforms it applies. "Code Sign on Copy" can be optionally enabled for the glob. ### `folderReference(path:condition:codeSignOnCopy:)` ```swift case folderReference(path: Path, condition: PlatformCondition? = nil, codeSignOnCopy: Bool = false) ``` A directory path to include as a folder reference with an optional PlatformCondition to control which platforms it applies to. "Code Sign on Copy" can be optionally enabled for the folder reference. --- URL: "/generated/manifest/enums/Configuration.Variant" LLMS_URL: "/generated/manifest/enums/Configuration.Variant.md" --- **ENUM** # `Configuration.Variant` **Contents** - [Cases](#cases) - `debug` - `release` ```swift public enum Variant: String, Codable, Sendable ``` ## Cases ### `debug` ```swift case debug ``` ### `release` ```swift case release ``` --- URL: "/generated/manifest/enums/CompatibleXcodeVersions" LLMS_URL: "/generated/manifest/enums/CompatibleXcodeVersions.md" --- **ENUM** # `CompatibleXcodeVersions` **Contents** - [Cases](#cases) - `all` - `exact(_:)` - `upToNextMajor(_:)` - `upToNextMinor(_:)` - `list(_:)` - [Methods](#methods) - `init(arrayLiteral:)` - `init(arrayLiteral:)` - `init(stringLiteral:)` ```swift public enum CompatibleXcodeVersions: ExpressibleByArrayLiteral, ExpressibleByStringInterpolation, Codable, Equatable, Sendable ``` Options of compatibles Xcode versions. ## Cases ### `all` ```swift case all ``` The project supports all Xcode versions. ### `exact(_:)` ```swift case exact(Version) ``` The project supports only a specific Xcode version. ### `upToNextMajor(_:)` ```swift case upToNextMajor(Version) ``` The project supports all Xcode versions from the specified version up to but not including the next major version. ### `upToNextMinor(_:)` ```swift case upToNextMinor(Version) ``` The project supports all Xcode versions from the specified version up to but not including the next minor version. ### `list(_:)` ```swift case list([CompatibleXcodeVersions]) ``` List of versions that are supported by the project. ## Methods ### `init(arrayLiteral:)` ```swift public init(arrayLiteral elements: [CompatibleXcodeVersions]) ``` ### `init(arrayLiteral:)` ```swift public init(arrayLiteral elements: CompatibleXcodeVersions...) ``` ### `init(stringLiteral:)` ```swift public init(stringLiteral value: String) ``` #### Parameters | Name | Description | | ---- | ----------- | | value | The value of the new instance. | --- URL: "/generated/manifest/enums/Cloud.Option" LLMS_URL: "/generated/manifest/enums/Cloud.Option.md" --- **ENUM** # `Cloud.Option` **Contents** - [Cases](#cases) - `optional` ```swift public enum Option: String, Codable, Equatable, Sendable ``` Options for cloud configuration. ## Cases ### `optional` ```swift case optional ``` Marks whether the Tuist server authentication is optional. If present, the interaction with the Tuist server will be skipped (instead of failing) if a user is not authenticated. --- URL: "/generated/manifest/enums/BuildRule.FileType" LLMS_URL: "/generated/manifest/enums/BuildRule.FileType.md" --- **ENUM** # `BuildRule.FileType` **Contents** - [Cases](#cases) - `instrumentsPackageDefinition` - `metalAIR` - `machO` - `machOObject` - `siriKitIntent` - `coreMLMachineLearning` - `rcProjectDocument` - `skyboxDocument` - `interfaceBuilderStoryboard` - `interfaceBuilder` - `documentationCatalog` - `coreMLMachineLearningModelPackage` - `assemblyAsm` - `assemblyAsmAsm` - `llvmAssembly` - `cSource` - `clipsSource` - `cppSource` - `dtraceSource` - `dylanSource` - `fortranSource` - `glslSource` - `iigSource` - `javaSource` - `lexSource` - `metalShaderSource` - `migSource` - `nasmAssembly` - `openCLSource` - `pascalSource` - `protobufSource` - `rezSource` - `swiftSource` - `yaccSource` - `localizationString` - `localizationStringDictionary` - `xcAppExtensionPoints` - `xcodeSpecificationPlist` - `dae` - `nib` - `interfaceBuilderStoryboardPackage` - `classModel` - `dataModel` - `dataModelVersion` - `mappingModel` - `sourceFilesWithNamesMatching` ```swift public enum FileType: Codable, Sendable ``` File types processed by a build rule. All the values are taken from build rule options hidden under a pup-up button's menu next to a label `Process` in a target's `Build Rules` section. ## Cases ### `instrumentsPackageDefinition` ```swift case instrumentsPackageDefinition ``` ### `metalAIR` ```swift case metalAIR ``` ### `machO` ```swift case machO ``` ### `machOObject` ```swift case machOObject ``` ### `siriKitIntent` ```swift case siriKitIntent ``` ### `coreMLMachineLearning` ```swift case coreMLMachineLearning ``` ### `rcProjectDocument` ```swift case rcProjectDocument ``` ### `skyboxDocument` ```swift case skyboxDocument ``` ### `interfaceBuilderStoryboard` ```swift case interfaceBuilderStoryboard ``` ### `interfaceBuilder` ```swift case interfaceBuilder ``` ### `documentationCatalog` ```swift case documentationCatalog ``` ### `coreMLMachineLearningModelPackage` ```swift case coreMLMachineLearningModelPackage ``` ### `assemblyAsm` ```swift case assemblyAsm ``` ### `assemblyAsmAsm` ```swift case assemblyAsmAsm ``` ### `llvmAssembly` ```swift case llvmAssembly ``` ### `cSource` ```swift case cSource ``` ### `clipsSource` ```swift case clipsSource ``` ### `cppSource` ```swift case cppSource ``` ### `dtraceSource` ```swift case dtraceSource ``` ### `dylanSource` ```swift case dylanSource ``` ### `fortranSource` ```swift case fortranSource ``` ### `glslSource` ```swift case glslSource ``` ### `iigSource` ```swift case iigSource ``` ### `javaSource` ```swift case javaSource ``` ### `lexSource` ```swift case lexSource ``` ### `metalShaderSource` ```swift case metalShaderSource ``` ### `migSource` ```swift case migSource ``` ### `nasmAssembly` ```swift case nasmAssembly ``` ### `openCLSource` ```swift case openCLSource ``` ### `pascalSource` ```swift case pascalSource ``` ### `protobufSource` ```swift case protobufSource ``` ### `rezSource` ```swift case rezSource ``` ### `swiftSource` ```swift case swiftSource ``` ### `yaccSource` ```swift case yaccSource ``` ### `localizationString` ```swift case localizationString ``` ### `localizationStringDictionary` ```swift case localizationStringDictionary ``` ### `xcAppExtensionPoints` ```swift case xcAppExtensionPoints ``` ### `xcodeSpecificationPlist` ```swift case xcodeSpecificationPlist ``` ### `dae` ```swift case dae ``` ### `nib` ```swift case nib ``` ### `interfaceBuilderStoryboardPackage` ```swift case interfaceBuilderStoryboardPackage ``` ### `classModel` ```swift case classModel ``` ### `dataModel` ```swift case dataModel ``` ### `dataModelVersion` ```swift case dataModelVersion ``` ### `mappingModel` ```swift case mappingModel ``` ### `sourceFilesWithNamesMatching` ```swift case sourceFilesWithNamesMatching ``` --- URL: "/generated/manifest/enums/BuildRule.CompilerSpec" LLMS_URL: "/generated/manifest/enums/BuildRule.CompilerSpec.md" --- **ENUM** # `BuildRule.CompilerSpec` **Contents** - [Cases](#cases) - `appIntentsMetadataExtractor` - `appShortcutStringsMetadataExtractor` - `appleClang` - `assetCatalogCompiler` - `codeSign` - `compileRealityComposerProject` - `compileSceneKitShaders` - `compileSkybox` - `compileUSDZ` - `compressPNG` - `copyPlistFile` - `copySceneKitAssets` - `copyStringsFile` - `copyTiffFile` - `coreDataMappingModelCompiler` - `coreMLModelCompiler` - `dataModelCompiler` - `defaultCompiler` - `dTrace` - `generateSpriteKitTextureAtlas` - `iconutil` - `instrumetsPackageBuilder` - `intentDefinitionCompiler` - `interfaceBuilderNIBPostprocessor` - `interfaceBuilderStoryboardCompiler` - `interfaceBuilderStoryboardLinker` - `interfaceBuilderStoryboardPostprocessor` - `interfaceBuilderXIBCompiler` - `ioKitInterfaceGenerator` - `lex` - `lsRegisterURL` - `metalCompiler` - `metalLinker` - `mig` - `nasm` - `nmedit` - `openCL` - `osaCompile` - `pbxcp` - `processSceneKitDocument` - `processXCAppExtensionPoints` - `rez` - `stripSymbols` - `swiftCompiler` - `swiftABIBaselineGenerator` - `swiftFrameworkABIChecker` - `textBasedAPITool` - `unifdef` - `yacc` - `customScript` ```swift public enum CompilerSpec: Codable, Sendable ``` The type of compiler spec which is used for a selected file type. All the values are taken from build rule options hidden under a pup-up button's menu next to a label `Using` in a target's `Build Rules` section. ## Cases ### `appIntentsMetadataExtractor` ```swift case appIntentsMetadataExtractor ``` ### `appShortcutStringsMetadataExtractor` ```swift case appShortcutStringsMetadataExtractor ``` ### `appleClang` ```swift case appleClang ``` ### `assetCatalogCompiler` ```swift case assetCatalogCompiler ``` ### `codeSign` ```swift case codeSign ``` ### `compileRealityComposerProject` ```swift case compileRealityComposerProject ``` ### `compileSceneKitShaders` ```swift case compileSceneKitShaders ``` ### `compileSkybox` ```swift case compileSkybox ``` ### `compileUSDZ` ```swift case compileUSDZ ``` ### `compressPNG` ```swift case compressPNG ``` ### `copyPlistFile` ```swift case copyPlistFile ``` ### `copySceneKitAssets` ```swift case copySceneKitAssets ``` ### `copyStringsFile` ```swift case copyStringsFile ``` ### `copyTiffFile` ```swift case copyTiffFile ``` ### `coreDataMappingModelCompiler` ```swift case coreDataMappingModelCompiler ``` ### `coreMLModelCompiler` ```swift case coreMLModelCompiler ``` ### `dataModelCompiler` ```swift case dataModelCompiler ``` ### `defaultCompiler` ```swift case defaultCompiler ``` ### `dTrace` ```swift case dTrace ``` ### `generateSpriteKitTextureAtlas` ```swift case generateSpriteKitTextureAtlas ``` ### `iconutil` ```swift case iconutil ``` ### `instrumetsPackageBuilder` ```swift case instrumetsPackageBuilder ``` ### `intentDefinitionCompiler` ```swift case intentDefinitionCompiler ``` ### `interfaceBuilderNIBPostprocessor` ```swift case interfaceBuilderNIBPostprocessor ``` ### `interfaceBuilderStoryboardCompiler` ```swift case interfaceBuilderStoryboardCompiler ``` ### `interfaceBuilderStoryboardLinker` ```swift case interfaceBuilderStoryboardLinker ``` ### `interfaceBuilderStoryboardPostprocessor` ```swift case interfaceBuilderStoryboardPostprocessor ``` ### `interfaceBuilderXIBCompiler` ```swift case interfaceBuilderXIBCompiler ``` ### `ioKitInterfaceGenerator` ```swift case ioKitInterfaceGenerator ``` ### `lex` ```swift case lex ``` ### `lsRegisterURL` ```swift case lsRegisterURL ``` ### `metalCompiler` ```swift case metalCompiler ``` ### `metalLinker` ```swift case metalLinker ``` ### `mig` ```swift case mig ``` ### `nasm` ```swift case nasm ``` ### `nmedit` ```swift case nmedit ``` ### `openCL` ```swift case openCL ``` ### `osaCompile` ```swift case osaCompile ``` ### `pbxcp` ```swift case pbxcp ``` ### `processSceneKitDocument` ```swift case processSceneKitDocument ``` ### `processXCAppExtensionPoints` ```swift case processXCAppExtensionPoints ``` ### `rez` ```swift case rez ``` ### `stripSymbols` ```swift case stripSymbols ``` ### `swiftCompiler` ```swift case swiftCompiler ``` ### `swiftABIBaselineGenerator` ```swift case swiftABIBaselineGenerator ``` ### `swiftFrameworkABIChecker` ```swift case swiftFrameworkABIChecker ``` ### `textBasedAPITool` ```swift case textBasedAPITool ``` ### `unifdef` ```swift case unifdef ``` ### `yacc` ```swift case yacc ``` ### `customScript` ```swift case customScript ``` --- URL: "/generated/manifest/enums/AutomaticSchemesOptions.TargetSchemesGrouping" LLMS_URL: "/generated/manifest/enums/AutomaticSchemesOptions.TargetSchemesGrouping.md" --- **ENUM** # `AutomaticSchemesOptions.TargetSchemesGrouping` **Contents** - [Cases](#cases) - `singleScheme` - `byNameSuffix(build:test:run:)` - `notGrouped` ```swift public enum TargetSchemesGrouping: Codable, Equatable, Sendable ``` Allows you to define what targets will be enabled for code coverage data gathering. ## Cases ### `singleScheme` ```swift case singleScheme ``` Generate a single scheme for each project. ### `byNameSuffix(build:test:run:)` ```swift case byNameSuffix(build: Set, test: Set, run: Set) ``` Group schemes according to the suffix of their names. ### `notGrouped` ```swift case notGrouped ``` Generate a scheme for each target. --- URL: "/generated/manifest/enums/AutogeneratedWorkspaceSchemes.CodeCoverageMode" LLMS_URL: "/generated/manifest/enums/AutogeneratedWorkspaceSchemes.CodeCoverageMode.md" --- **ENUM** # `AutogeneratedWorkspaceSchemes.CodeCoverageMode` **Contents** - [Cases](#cases) - `all` - `relevant` - `targets(_:)` - `disabled` ```swift public enum CodeCoverageMode: Codable, Equatable, Sendable ``` Contains options for code coverage ## Cases ### `all` ```swift case all ``` Gather code coverage data for all targets in workspace. ### `relevant` ```swift case relevant ``` Enable code coverage for targets that have enabled code coverage in any of schemes in workspace. ### `targets(_:)` ```swift case targets([TargetReference]) ``` Gather code coverage for specified target references. ### `disabled` ```swift case disabled ``` Do not gather code coverage data. --- URL: "/es/server/on-premise/metrics" LLMS_URL: "/es/server/on-premise/metrics.md" title: "Metrics" titleTemplate: ":title | On-premise | Server | Tuist" description: "Optimize your build times by caching compiled binaries and sharing them across different environments." --- # Metrics {#metrics} You can ingest metrics gathered by the Tuist server using [Prometheus](https://prometheus.io/) and a visualization tool such as [Grafana](https://grafana.com/) to create a custom dashboard tailored to your needs. The Prometheus metrics are served via the `/metrics` endpoint on port 9091. The Prometheus' [scrape_interval](https://prometheus.io/docs/introduction/first_steps/#configuring-prometheus) should be set as less than 10_000 seconds (we recommend keeping the default of 15 seconds). ## Elixir metrics {#elixir-metrics} By default we include metrics of the Elixir runtime, BEAM, Elixir, and some of the libraries we use. The following are some of the metrics you can expect to see: - [Application](https://hexdocs.pm/prom_ex/PromEx.Plugins.Application.html) - [BEAM](https://hexdocs.pm/prom_ex/PromEx.Plugins.Beam.html) - [Phoenix](https://hexdocs.pm/prom_ex/PromEx.Plugins.Phoenix.html) - [Phoenix LiveView](https://hexdocs.pm/prom_ex/PromEx.Plugins.PhoenixLiveView.html) - [Ecto](https://hexdocs.pm/prom_ex/PromEx.Plugins.Ecto.html) - [Oban](https://hexdocs.pm/prom_ex/PromEx.Plugins.Oban.html) We recommend checking those pages to know which metrics are available and how to use them. ## Runs metrics {#runs-metrics} A set of metrics related to Tuist Runs. ### `tuist_runs_total` (counter) {#tuist_runs_total-counter} The total number of Tuist Runs. #### Tags {#tuist-runs-total-tags} | Tag | Description | | -------- | ------------------------------------------------------------------------------------------- | | `name` | The name of the `tuist` command that was run, such as `build`, `test`, etc. | | `is_ci` | A boolean indicating if the executor was a CI or a developer's machine. | | `status` | `0` in case of `success`, `1` in case of `failure`. | ### `tuist_runs_duration_milliseconds` (histogram) {#tuist_runs_duration_milliseconds-histogram} The total duration of each tuist run in milliseconds. #### Tags {#tuist-runs-duration-miliseconds-tags} | Tag | Description | | -------- | ------------------------------------------------------------------------------------------- | | `name` | The name of the `tuist` command that was run, such as `build`, `test`, etc. | | `is_ci` | A boolean indicating if the executor was a CI or a developer's machine. | | `status` | `0` in case of `success`, `1` in case of `failure`. | ## Cache metrics {#cache-metrics} A set of metrics related to the Tuist Cache. ### `tuist_cache_events_total` (counter) {#tuist_cache_events_total-counter} The total number of binary cache events. #### Tags {#tuist-cache-events-total-tags} | Tag | Description | | ------------ | ---------------------------------------------------------------------- | | `event_type` | Can be either of `local_hit`, `remote_hit`, or `miss`. | ### `tuist_cache_uploads_total` (counter) {#tuist_cache_uploads_total-counter} The number of uploads to the binary cache. ### `tuist_cache_uploaded_bytes` (sum) {#tuist_cache_uploaded_bytes-sum} The number of bytes uploaded to the binary cache. ### `tuist_cache_downloads_total` (counter) {#tuist_cache_downloads_total-counter} The number of downloads to the binary cache. ### `tuist_cache_downloaded_bytes` (sum) {#tuist_cache_downloaded_bytes-sum} The number of bytes downloaded from the binary cache. --- ## Previews metrics {#previews-metrics} A set of metrics related to the previews feature. ### `tuist_previews_uploads_total` (sum) {#tuist_previews_uploads_total-counter} The total number of previews uploaded. ### `tuist_previews_downloads_total` (sum) {#tuist_previews_downloads_total-counter} The total number of previews downloaded. --- ## Storage metrics {#storage-metrics} A set of metrics related to the storage of artifacts in a remote storage (e.g. s3). > [!TIP] > These metrics are useful to understand the performance of the storage operations and to identify potential bottlenecks. ### `tuist_storage_get_object_size_size_bytes` (histogram) {#tuist_storage_get_object_size_size_bytes-histogram} The size (in bytes) of an object fetched from the remote storage. #### Tags {#tuist-storage-get-object-size-size-bytes-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_size_duration_miliseconds` (histogram) {#tuist_storage_get_object_size_duration_miliseconds-histogram} The duration (in milliseconds) of fetching an object size from the remote storage. #### Tags {#tuist-storage-get-object-size-duration-miliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_size_count` (counter) {#tuist_storage_get_object_size_count-counter} The number of times an object size was fetched from the remote storage. #### Tags {#tuist-storage-get-object-size-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_delete_all_objects_duration_milliseconds` (histogram) {#tuist_storage_delete_all_objects_duration_milliseconds-histogram} The duration (in milliseconds) of deleting all objects from the remote storage. #### Tags {#tuist-storage-delete-all-objects-duration-milliseconds-tags} | Tag | Description | | -------------- | -------------------------------------------------------------------------------- | | `project_slug` | The project slug of the project whose objects are being deleted. | ### `tuist_storage_delete_all_objects_count` (counter) {#tuist_storage_delete_all_objects_count-counter} The number of times all project objects were deleted from the remote storage. #### Tags {#tuist-storage-delete-all-objects-count-tags} | Tag | Description | | -------------- | -------------------------------------------------------------------------------- | | `project_slug` | The project slug of the project whose objects are being deleted. | ### `tuist_storage_multipart_start_upload_duration_milliseconds` (histogram) {#tuist_storage_multipart_start_upload_duration_milliseconds-histogram} The duration (in milliseconds) of starting an upload to the remote storage. #### Tags {#tuist-storage-multipart-start-upload-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_multipart_start_upload_duration_count` (counter) {#tuist_storage_multipart_start_upload_duration_count-counter} The number of times an upload was started to the remote storage. #### Tags {#tuist-storage-multipart-start-upload-duration-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_as_string_duration_milliseconds` (histogram) {#tuist_storage_get_object_as_string_duration_milliseconds-histogram} The duration (in milliseconds) of fetching an object as a string from the remote storage. #### Tags {#tuist-storage-get-object-as-string-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_as_string_count` (count) {#tuist_storage_get_object_as_string_count-count} The number of times an object was fetched as a string from the remote storage. #### Tags {#tuist-storage-get-object-as-string-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_check_object_existence_duration_milliseconds` (histogram) {#tuist_storage_check_object_existence_duration_milliseconds-histogram} The duration (in milliseconds) of checking the existence of an object in the remote storage. #### Tags {#tuist-storage-check-object-existence-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_check_object_existence_count` (count) {#tuist_storage_check_object_existence_count-count} The number of times the existence of an object was checked in the remote storage. #### Tags {#tuist-storage-check-object-existence-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_generate_download_presigned_url_duration_milliseconds` (histogram) {#tuist_storage_generate_download_presigned_url_duration_milliseconds-histogram} The duration (in milliseconds) of generating a download presigned URL for an object in the remote storage. #### Tags {#tuist-storage-generate-download-presigned-url-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_generate_download_presigned_url_count` (count) {#tuist_storage_generate_download_presigned_url_count-count} The number of times a download presigned URL was generated for an object in the remote storage. #### Tags {#tuist-storage-generate-download-presigned-url-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_multipart_generate_upload_part_presigned_url_duration_milliseconds` (histogram) {#tuist_storage_multipart_generate_upload_part_presigned_url_duration_milliseconds-histogram} The duration (in milliseconds) of generating a part upload presigned URL for an object in the remote storage. #### Tags {#tuist-storage-multipart-generate-upload-part-presigned-url-duration-milliseconds-tags} | Tag | Description | | ------------- | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `part_number` | The part number of the object being uploaded. | | `upload_id` | The upload ID of the multipart upload. | ### `tuist_storage_multipart_generate_upload_part_presigned_url_count` (count) {#tuist_storage_multipart_generate_upload_part_presigned_url_count-count} The number of times a part upload presigned URL was generated for an object in the remote storage. #### Tags {#tuist-storage-multipart-generate-upload-part-presigned-url-count-tags} | Tag | Description | | ------------- | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `part_number` | The part number of the object being uploaded. | | `upload_id` | The upload ID of the multipart upload. | ### `tuist_storage_multipart_complete_upload_duration_milliseconds` (histogram) {#tuist_storage_multipart_complete_upload_duration_milliseconds-histogram} The duration (in milliseconds) of completing an upload to the remote storage. #### Tags {#tuist-storage-multipart-complete-upload-duration-milliseconds-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `upload_id` | The upload ID of the multipart upload. | ### `tuist_storage_multipart_complete_upload_count` (count) {#tuist_storage_multipart_complete_upload_count-count} The total number of times an upload was completed to the remote storage. #### Tags {#tuist-storage-multipart-complete-upload-count-tags} | Tag | Description | | ------------ | ------------------------------------------------------------------- | | `object_key` | The lookup key of the object in the remote storage. | | `upload_id` | The upload ID of the multipart upload. | --- ## Projects metrics {#projects-metrics} A set of metrics related to the projects. ### `tuist_projects_total` (last_value) {#tuist_projects_total-last_value} The total number of projects. --- ## Accounts metrics {#accounts-metrics} A set of metrics related to accounts (users and organizations). ### `tuist_accounts_organizations_total` (last_value) {#tuist_accounts_organizations_total-last_value} The total number of organizations. ### `tuist_accounts_users_total` (last_value) {#tuist_accounts_users_total-last_value} The total number of users. ## Database metrics {#database-metrics} A set of metrics related to the database connection. ### `tuist_repo_pool_checkout_queue_length` (last_value) {#tuist_repo_pool_checkout_queue_length-last_value} The number of database queries that are sitting in a queue waiting to be assigned to a database connection. ### `tuist_repo_pool_ready_conn_count` (last_value) {#tuist_repo_pool_ready_conn_count-last_value} The number of database connections that are ready to be assigned to a database query. ### `tuist_repo_pool_db_connection_connected` (counter) {#tuist_repo_pool_db_connection_connected-counter} The number of connections that have been established to the database. ### `tuist_repo_pool_db_connection_disconnected` (counter) {#tuist_repo_pool_db_connection_disconnected-counter} The number of connections that have been disconnected from the database. ## HTTP metrics {#http-metrics} A set of metrics related to Tuist's interactions with other services via HTTP. ### `tuist_http_request_count` (counter) {#tuist_http_request_count-last_value} The number of outgoing HTTP requests. ### `tuist_http_request_duration_nanosecond_sum` (sum) {#tuist_http_request_duration_nanosecond_sum-last_value} The sum of the duration of the outgoing requests (including the time that they spent waiting to be assigned to a connection). ### `tuist_http_request_duration_nanosecond_bucket` (distribution) {#tuist_http_request_duration_nanosecond_bucket-distribution} The distribution of the duration of outgoing requests (including the time that they spent waiting to be assigned to a connection). ### `tuist_http_queue_count` (counter) {#tuist_http_queue_count-counter} The number of requests that have been retrieved from the pool. ### `tuist_http_queue_duration_nanoseconds_sum` (sum) {#tuist_http_queue_duration_nanoseconds_sum-sum} The time it takes to retrieve a connection from the pool. ### `tuist_http_queue_idle_time_nanoseconds_sum` (sum) {#tuist_http_queue_idle_time_nanoseconds_sum-sum} The time a connection has been idle waiting to be retrieved. ### `tuist_http_queue_duration_nanoseconds_bucket` (distribution) {#tuist_http_queue_duration_nanoseconds_bucket-distribution} The time it takes to retrieve a connection from the pool. ### `tuist_http_queue_idle_time_nanoseconds_bucket` (distribution) {#tuist_http_queue_idle_time_nanoseconds_bucket-distribution} The time a connection has been idle waiting to be retrieved. ### `tuist_http_connection_count` (counter) {#tuist_http_connection_count-counter} The number of connections that have been established. ### `tuist_http_connection_duration_nanoseconds_sum` (sum) {#tuist_http_connection_duration_nanoseconds_sum-sum} The time it takes to establish a connection against a host. ### `tuist_http_connection_duration_nanoseconds_bucket` (distribution) {#tuist_http_connection_duration_nanoseconds_bucket-distribution} The distribution of the time it takes to establish a connection against a host. ### `tuist_http_send_count` (counter) {#tuist_http_send_count-counter} The number of requests that have been sent once assigned to a connection from the pool. ### `tuist_http_send_duration_nanoseconds_sum` (sum) {#tuist_http_send_duration_nanoseconds_sum-sum} The time that it takes for requests to complete once assigned to a connection from the pool. ### `tuist_http_send_duration_nanoseconds_bucket` (distribution) {#tuist_http_send_duration_nanoseconds_bucket-distribution} The distribution of the time that it takes for requests to complete once assigned to a connection from the pool. ### `tuist_http_receive_count` (counter) {#tuist_http_receive_count-counter} The number of responses that have been received from sent requests. ### `tuist_http_receive_duration_nanoseconds_sum` (sum) {#tuist_http_receive_duration_nanoseconds_sum-sum} The time spent receiving responses. ### `tuist_http_receive_duration_nanoseconds_bucket` (distribution) {#tuist_http_receive_duration_nanoseconds_bucket-distribution} The distribution of the time spent receiving responses. ### `tuist_http_queue_available_connections` (last_value) {#tuist_http_queue_available_connections-last_value} The number of connections available in the queue. ### `tuist_http_queue_in_use_connections` (last_value) {#tuist_http_queue_in_use_connections-last_value} The number of queue connections that are in use. --- URL: "/es/server/on-premise/install" LLMS_URL: "/es/server/on-premise/install.md" title: "Installation" titleTemplate: ":title | On-premise | Server | Tuist" description: "Learn how to install Tuist on your infrastructure." --- # On-premise installation {#onpremise-installation} We offer a self-hosted version of the Tuist server for organizations that require more control over their infrastructure. This version allows you to host Tuist on your own infrastructure, ensuring that your data remains secure and private. > [!IMPORTANT] ENTERPRISE CUSTOMERS ONLY > The on-premise version of Tuist is available only for organizations on the Enterprise plan. If you are interested in this version, please reach out to [contact@tuist.dev](mailto:contact@tuist.dev). ## Release cadence {#release-cadence} The Tuist server is **released every Monday** and the version name follows the convention name `{MAJOR}.YY.MM.DD`. The date component is used to warn the CLI user if their hosted version is 60 days older than the release date of the CLI. It's crucial that on-premise organizations keep up with Tuist updates to ensure their developers benefit from the most recent improvements and that we can drop deprecated features with the confidence that we are not breaking any of the on-premise setups. The major component of the CLI is used to flag breaking changes in the Tuist server that will require coordination with the on-premise users. You should not expect us to use it, and in case we needed, rest asure we'll work with you in making the transition smooth. > [!NOTE] RELEASE NOTES > You'll be given access to a `tuist/registry` repository associated with the registry where images are published. Every new released will be published in that repository as a GitHub release and will contain release notes to inform you about what changes come with it. ## Runtime requirements {#runtime-requirements} This section outlines the requirements for hosting the Tuist server on your infrastructure. ### Running Docker-virtualized images {#running-dockervirtualized-images} We distribute the server as a [Docker](https://www.docker.com/) image via [GitHub’s Container Registry](https://docs.github.com/es/packages/working-with-a-github-packages-registry/working-with-the-container-registry). To run it, your infrastructure must support running Docker images. Note that most infrastructure providers support it because it’s become the standard container for distributing and running software in production environments. ### Postgres database {#postgres-database} In addition to running the Docker images, you’ll need a [Postgres database](https://www.postgresql.org/) to store relational data. Most infrastructure providers include Posgres databases in their offering (e.g., [AWS](https://aws.amazon.com/rds/postgresql/) & [Google Cloud](https://cloud.google.com/sql/docs/postgres)). For performant analytics, we use a [Timescale Postgres extension](https://www.timescale.com/). You need to make sure that TimescaleDB is installed on the machine running the Postgres database. Follow the installation instructions [here](https://docs.timescale.com/self-hosted/latest/install/) to learn more. If you are unable to install the Timescale extension, you can set up your own dashboard using the Prometheus metrics. > [!INFO] MIGRATIONS > The Docker image's entrypoint automatically runs any pending schema migrations before starting the service. ### Storage {#storage} You’ll also need a solution to store files (e.g. framework and library binaries). Currently we support any storage that's S3-compliant. ## Configuration {#configuration} The configuration of the service is done at runtime through environment variables. Given the sensitive nature of these variables, we advise encrypting and storing them in secure password management solutions. Rest assured, Tuist handles these variables with utmost care, ensuring they are never displayed in logs. > [!NOTE] LAUNCH CHECKS > The necessary variables are verified at startup. If any are missing, the launch will fail and the error message will detail the absent variables. ### License configuration {#license-configuration} As an on-premise user, you'll receive a license key that you'll need to expose as an environment variable. This key is used to validate the license and ensure that the service is running within the terms of the agreement. | Environment variable | Description | Required | Default | Example | | -------------------- | -------------------------------------------------------------- | -------- | ------- | -------- | | `TUIST_LICENSE` | The license provided after signing the service level agreement | Yes | | `******` | > [!IMPORTANT] EXPIRATION DATE > Licenses have an expiration date. Users will receive a warning while using Tuist commands that interact with the server if the license expires in less than 30 days. If you are interested in renewing your license, please reach out to [contact@tuist.dev](mailto:contact@tuist.dev). ### Base environment configuration {#base-environment-configuration} | Environment variable | Description | Required | Default | Example | | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------ | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | | `TUIST_APP_URL` | The base URL to access the instance from the Internet | Yes | | https://cloud.tuist.io | | | `TUIST_SECRET_KEY_BASE` | The key to use to encrypt information (e.g. sessions in a cookie) | Yes | | | `c5786d9f869239cbddeca645575349a570ffebb332b64400c37256e1c9cb7ec831345d03dc0188edd129d09580d8cbf3ceaf17768e2048c037d9c31da5dcacfa` | | `TUIST_SECRET_KEY_PASSWORD` | Pepper to generate hashed passwords | No | `$TUIST_SECRET_KEY_BASE` | | | | `TUIST_SECRET_KEY_TOKENS` | Secret key to generate random tokens | No | `$TUIST_SECRET_KEY_BASE` | | | | `TUIST_USE_IPV6` | When `1` it configures the app to use IPv6 addresses | No | `0` | `1` | | | `TUIST_LOG_LEVEL` | The log level to use for the app | No | `info` | [Log levels](https://hexdocs.pm/logger/1.12.3/Logger.html#module-levels) | | | `TUIST_GITHUB_APP_PRIVATE_KEY` | The private key used for the GitHub app to unlock extra functionality such as posting automatic PR comments | No | `-----BEGIN RSA...` | | | | `TUIST_OPS_USER_HANDLES` | A comma-separated list of user handles that have access to the operations URLs | No | | `user1,user2` | | ### Database configuration {#database-configuration} The following environment variables are used to configure the database connection: | Environment variable | Description | Required | Default | Example | | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | ---------------------------------------------------------------------- | | `DATABASE_URL` | The URL to access the Postgres database. Note that the URL should contain the authentication information | Yes | | `postgres://username:password@cloud.us-east-2.aws.test.com/production` | | `TUIST_USE_SSL_FOR_DATABASE` | When true, it uses [SSL](https://en.wikipedia.org/wiki/Transport_Layer_Security) to connect to the database | No | `1` | `1` | | `TUIST_DATABASE_POOL_SIZE` | The number of connections to keep open in the connection pool | No | `10` | `10` | | `TUIST_DATABASE_QUEUE_TARGET` | The interval (in miliseconds) for checking if all the connections checked out from the pool took more than the queue interval [(More information)](https://hexdocs.pm/db_connection/DBConnection.html#start_link/2-queue-config) | No | `300` | `300` | | `TUIST_DATABASE_QUEUE_INTERVAL` | The threshold time (in miliseconds) in the queue that the pool uses to determine if it should start dropping new connections [(More information)](https://hexdocs.pm/db_connection/DBConnection.html#start_link/2-queue-config) | No | `1000` | `1000` | ### Authentication environment configuration {#authentication-environment-configuration} We facilitate authentication through [identity providers (IdP)](https://en.wikipedia.org/wiki/Identity_provider). To utilize this, ensure all necessary environment variables for the chosen provider are present in the server's environment. **Missing variables** will result in Tuist bypassing that provider. #### GitHub {#github} We recommend authenticating using a [GitHub App](https://docs.github.com/es/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps) but you can also use the [OAuth App](https://docs.github.com/es/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app). Make sure to include all essential environment variables specified by GitHub in the server environment. Absent variables will cause Tuist to overlook the GitHub authentication. To properly set up the GitHub app: - In the GitHub app's general settings: - Copy the `Client ID` and set it as `TUIST_GITHUB_APP_CLIENT_ID` - Create and copy a new `client secret` and set it as `TUIST_GITHUB_APP_CLIENT_SECRET` - Set the `Callback URL` as `http://YOUR_APP_URL/users/auth/github/callback`. `YOUR_APP_URL` can also be your server's IP address. - In the `Permissions and events`'s `Account permissions` section, set the `Email addresses` permission to `Read-only`. You'll then need to expose the following environment variables in the environment where the Tuist server runs: | Environment variable | Description | Required | Default | Example | | -------------------------------- | --------------------------------------- | -------- | ------- | ------------------------------------------ | | `TUIST_GITHUB_APP_CLIENT_ID` | The client ID of the GitHub application | Yes | | `Iv1.a629723000043722` | | `TUIST_GITHUB_APP_CLIENT_SECRET` | The client secret of the application | Yes | | `232f972951033b89799b0fd24566a04d83f44ccc` | #### Google {#google} You can set up authentication with Google using [OAuth 2](https://developers.google.com/identity/protocols/oauth2). For that, you'll need to create a new credential of type OAuth client ID. When creating the credentials, select "Web Application" as application type, name it `Tuist`, and set the redirect URI to `{base_url}/users/auth/google/callback` where `base_url` is the URL your hosted-service is running at. Once you create the app, copy the client ID and secret and set them as environment variables `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` respectively. > [!NOTE] CONSENT SCREEN SCOPES > You might need to create a consent screen. When you do so, make sure to add the `userinfo.email` and `openid` scopes and mark the app as internal. #### Okta {#okta} You can enable authentication with Okta through the [OAuth 2.0](https://oauth.net/2/) protocol. You'll have to [create an app](https://developer.okta.com/docs/en/guides/implement-oauth-for-okta/main/#create-an-oauth-2-0-app-in-okta) on Okta with the following configuration: - **App integration name:** `Tuist` - **Grant type:** Enable _Authorization Code_ for _Client acting on behalf of a user_ - **Sign-in redirect URL:** `{url}/users/auth/okta/callback` where `url` is the public URL your service is accessed through. - **Assignments:** This configuration will depend on your security team requirements. Once the app is created you'll need to set the following environment variables: | Environment variable | Description | Required | Default | Example | | -------------------------- | ---------------------------------------------- | -------- | ------- | --------------------------- | | `TUIST_OKTA_SITE` | The URL of your Okta organization | Yes | | `https://your-org.okta.com` | | `TUIST_OKTA_CLIENT_ID` | The client ID to authenticate against Okta | Yes | | | | `TUIST_OKTA_CLIENT_SECRET` | The client secret to authenticate against Okta | Yes | | | ### Storage environment configuration {#storage-environment-configuration} Tuist needs storage to house artifacts uploaded through the API. It's **essential to configure one of the supported storage solutions** for Tuist to operate effectively. #### S3-compliant storages {#s3compliant-storages} You can use any S3-compliant storage provider to store artifacts. The following environment variables are required to authenticate and configure the integration with the storage provider: | Environment variable | Description | Required | Default | Example | | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | ------------------------------------------ | | `TUIST_ACCESS_KEY_ID` or `AWS_ACCESS_KEY_ID` | The access key ID to authenticate against the storage provider | Yes | | `AKIAIOSFOD` | | `TUIST_SECRET_ACCESS_KEY` or `AWS_SECRET_ACCESS_KEY` | The secret access key to authenticate against the storage provider | Yes | | `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` | | `TUIST_S3_REGION` or `AWS_REGION` | The region where the bucket is located | Yes | | `us-west-2` | | `TUIST_S3_ENDPOINT` or `AWS_ENDPOINT` | The endpoint of the storage provider | Yes | | `https://s3.us-west-2.amazonaws.com` | | `TUIST_S3_BUCKET_NAME` | The name of the bucket where the artifacts will be stored | Yes | | `tuist-artifacts` | | `TUIST_S3_REQUEST_TIMEOUT` | The timeout (in seconds) for requests to the storage provider | No | `30` | `30` | | `TUIST_S3_POOL_TIMEOUT` | The timeout (in seconds) for the connection pool to the storage provider | No | `5` | `5` | | `TUIST_S3_POOL_COUNT` | The number of pools to use for connections to the storage provider | No | `1` | `1` | | `TUIST_S3_PROTOCOL` | The protocol to use when connecting to the storage provider (`http1` or `http2`) | No | `http2` | `http2` | | `TUIST_S3_VIRTUAL_HOST` | Whether the URL should be constructed with the bucket name as a sub-domain (virtual host). | No | No | `1` | > [!NOTE] AWS authentication with Web Identity Token from environment variables > If your storage provider is AWS and you'd like to authenticate using a web identity token, you can set the environment variable `TUIST_S3_AUTHENTICATION_METHOD` to `aws_web_identity_token_from_env_vars`, and Tuist will use that method using the conventional AWS environment variables. #### Google Cloud Storage {#google-cloud-storage} For Google Cloud Storage, follow [these docs](https://cloud.google.com/storage/docs/authentication/managing-hmackeys) to get the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` pair. The `AWS_ENDPOINT` should be set to `https://storage.googleapis.com`. Other environment variables are the same as for any other S3-compliant storage. ### Git platform configuration {#git-platform-configuration} Tuist can integrate with Git platforms to provide extra features such as automatically posting comments in your pull requests. #### GitHub {#platform-github} You will need to [create a GitHub app](https://docs.github.com/es/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps). You can reuse the one you created for authentication, unless you created an OAuth GitHub app. In the `Permissions and events`'s `Repository permissions` section, you will need to additionally set the `Pull requests` permission to `Read and write`. On top of the `TUIST_GITHUB_APP_CLIENT_ID` and `TUIST_GITHUB_APP_CLIENT_SECRET`, you will need the following environment variables: | Environment variable | Description | Required | Default | Example | | ------------------------------ | ----------------------------------------- | -------- | ------- | ------------------------------------ | | `TUIST_GITHUB_APP_PRIVATE_KEY` | The private key of the GitHub application | Yes | | `-----BEGIN RSA PRIVATE KEY-----...` | ## Deployment {#deployment} On-premise users are granted access to the repository located at [tuist/registry](https://github.com/cloud/registry) which has a linked container registry for pulling images. Currently, the container registry allows authentication only as an individual user. Therefore, users with repository access must generate a **personal access token** within the Tuist organization, ensuring they have the necessary permissions to read packages. After submission, we will promptly approve this token. > [!IMPORTANT] USER VS ORGANIZATION-SCOPED TOKENS > Using a personal access token presents a challenge because it's associated with an individual who might eventually depart from the enterprise organization. GitHub recognizes this limitation and is actively developing a solution to allow GitHub apps to authenticate with app-generated tokens. ### Pulling the Docker image {#pulling-the-docker-image} After generating the token, you can retrieve the image by executing the following command: ```bash echo $TOKEN | docker login ghcr.io -u USERNAME --password-stdin docker pull ghcr.io/tuist/tuist:latest ``` ### Deploying the Docker image {#deploying-the-docker-image} The deployment process for the Docker image will differ based on your chosen cloud provider and your organization's continuous deployment approach. Since most cloud solutions and tools, like [Kubernetes](https://kubernetes.io/), utilize Docker images as fundamental units, the examples in this section should align well with your existing setup. We recommend establishing a deployment pipeline that that runs **every Tuesday**, pulling and deploying fresh images. This ensures you consistently benefit from the latest improvements. > [!IMPORTANT] > If your deployment pipeline needs to validate that the server is up and running, you can send a `GET` HTTP request to `/ready` and assert a `200` status code in the response. #### Fly {#fly} To deploy the app on [Fly](https://fly.io/), you'll require a `fly.toml` configuration file. Consider generating it dynamically within your Continuous Deployment (CD) pipeline. Below is a reference example for your use: ```toml app = "tuist" primary_region = "fra" kill_signal = "SIGINT" kill_timeout = "5s" [experimental] auto_rollback = true [env] # Your environment configuration goes here # Or exposed through Fly secrets [processes] app = "/usr/local/bin/hivemind /app/Procfile" [[services]] protocol = "tcp" internal_port = 8080 auto_stop_machines = false auto_start_machines = false processes = ["app"] http_options = { h2_backend = true } [[services.ports]] port = 80 handlers = ["http"] force_https = true [[services.ports]] port = 443 handlers = ["tls", "http"] [services.concurrency] type = "connections" hard_limit = 100 soft_limit = 80 [[services.http_checks]] interval = 10000 grace_period = "10s" method = "get" path = "/ready" protocol = "http" timeout = 2000 tls_skip_verify = false [services.http_checks.headers] [[statics]] guest_path = "/app/public" url_prefix = "/" ``` Then you can run `fly launch --local-only --no-deploy` to launch the app. On subsequent deploys, instead of running `fly launch --local-only`, you will need to run `fly deploy --local-only`. Fly.io doesn't allow to pull private Docker images, which is why we need to use the `--local-only` flag. ### Docker Compose {#docker-compose} Below is an example of a `docker-compose.yml` file that you can use as a reference to deploy the service: ```yaml version: '3.8' services: db: image: timescale/timescaledb-ha:pg16 restart: always environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - PGDATA=/var/lib/postgresql/data/pgdata ports: - '5432:5432' volumes: - db:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 pgweb: container_name: pgweb restart: always image: sosedoff/pgweb ports: - "8081:8081" links: - db:db environment: PGWEB_DATABASE_URL: postgres://postgres:postgres@db:5432/postgres?sslmode=disable depends_on: - db tuist: image: ghcr.io/tuist/tuist:latest container_name: tuist depends_on: - db ports: - "80:80" - "8080:8080" - "443:443" expose: - "80" - "8080" - "443:443" environment: # Base Tuist Env - https://docs.tuist.io/es/guides/dashboard/on-premise/install#base-environment-configuration TUIST_USE_SSL_FOR_DATABASE: "0" TUIST_LICENSE: # ... DATABASE_URL: postgres://postgres:postgres@db:5432/postgres?sslmode=disable TUIST_APP_URL: https://localhost:8080 TUIST_SECRET_KEY_BASE: # ... WEB_CONCURRENCY: 80 # Auth - one method # GitHub Auth - https://docs.tuist.io/es/guides/dashboard/on-premise/install#github TUIST_GITHUB_OAUTH_ID: TUIST_GITHUB_APP_CLIENT_SECRET: # Okta Auth - https://docs.tuist.io/es/guides/dashboard/on-premise/install#okta TUIST_OKTA_SITE: TUIST_OKTA_CLIENT_ID: TUIST_OKTA_CLIENT_SECRET: TUIST_OKTA_AUTHORIZE_URL: # Optional TUIST_OKTA_TOKEN_URL: # Optional TUIST_OKTA_USER_INFO_URL: # Optional TUIST_OKTA_EVENT_HOOK_SECRET: # Optional # Storage AWS_ACCESS_KEY_ID: # ... AWS_SECRET_ACCESS_KEY: # ... AWS_S3_REGION: # ... AWS_ENDPOINT: # https://amazonaws.com TUIST_S3_BUCKET_NAME: # ... # Other volumes: db: driver: local ``` ## Operations {#operations} Tuist provides a set of utilities under `/ops/` that you can use to manage your instance. > [!IMPORTANT] Authorization > Only people whose handles are listed in the `TUIST_OPS_USER_HANDLES` environment variable can access the `/ops/` endpoints. - **Errors (`/ops/errors`):** You can view unexpected errors that ocurred in the application. This is useful for debugging and understanding what went wrong and we might ask you to share this information with us if you're facing issues. - **Dashboard (`/ops/dashboard`):** You can view a dashboard that provides insights into the application's performance and health (e.g. memory consumption, processes running, number of requests). This dashboard can be quite useful to understand if the hardware you're using is enough to handle the load. --- URL: "/es/server/introduction/why-a-server" LLMS_URL: "/es/server/introduction/why-a-server.md" title: "Why a server?" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn why Tuist has a server and how it can help scale your app development." --- # Why a server? {#why-a-server} At a certain scale, optimizing a project and developers' interactions with them require access to data that changes over time, and integrations with other internet services where teams collaborate. This is only possible with **a server that can store data in a database, process it asynchonously, and integrate it with other services.** While the role of a server is common in other ecosystems, it's not that common in app development. Teams leaned heavily on open source solutions that leveraged the capabilities of CI services to approximate the capabilities of a server. However, as the complexity of the projects and the number of developers working on them increased, the limitations of these solutions became more evident. We believe teams shouldn't have to worry about setting up and maintaining a server to scale their projects. That's why we built a server that Tuist and [Xcode projects](https://developer.apple.com/documentation/xcode/creating-an-xcode-project-for-an-app) can integrate with to scale their projects and teams. > [!TIP] GIVING YOUR PROJECTS AND WORKFLOWS SUPERPOWERS > A way of thinking about the server is as a superpower that you can give to your projects and workflows. > Some superpowers like binary caching require you to have a Tuist project but others just work with vanilla Xcode projects. --- URL: "/es/server/introduction/integrations" LLMS_URL: "/es/server/introduction/integrations.md" title: "Integrations" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn how to connect Tuist to other tools and services." --- # Integrations {#integrations} We strongly believe we should meet developers where they are, and let's be honest, developers spend time outside of their coding environments, such as reviewing pull request on [GitHub](https://github.com) or communicating with their team on [Slack](https://slack.com). That's why we've built integrations with popular tools and services to make it easier for you to use Tuist in your workflows. This page lists the integrations we currently support. ## Git platforms {#git-platforms} Git repositories are the centerpiece of the vast majority of software projects out there. We integrate with your Git platform to provide Tuist insights right in your pull requests or to save you some configuration such as syncing your default branch. ### GitHub {#github} Install the [Tuist GitHub app](https://github.com/marketplace/tuist). Once installed, you will need to tell Tuist the URL of your repository, such as: ```sh tuist project update tuist/tuist --repository-url https://github.com/tuist/tuist ``` --- URL: "/es/server/introduction/authentication" LLMS_URL: "/es/server/introduction/authentication.md" title: "Authentication" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn how to authenticate with the Tuist server from the CLI." --- # Authentication {#authentication} To interact with the server, the CLI needs to authenticate the requests using [bearer authentication](https://swagger.io/docs/specification/authentication/bearer-authentication/). The CLI supports authenticating as a user or as a project. ## As a user {#as-a-user} When using the CLI locally on your machine, we recommend authenticating as a user. To authenticate as a user, you need to run the following command: ```bash tuist auth login ``` The command will take you through a web-based authentication flow. Once you authenticate, the CLI will store a long-lived refresh token and a short-lived access token under `~/.config/tuist/credentials`. Each file in the directory represents the domain you authenticated against, which by default should be `cloud.tuist.io.json`. The information stored in that directory is sensitive, so **make sure to keep it safe**. The CLI will automatically look up the credentials when making requests to the server. If the access token is expired, the CLI will use the refresh token to get a new access token. ### Organization SSO {#organization-sso} If you have a Google Workspace organization and you want any developer who signs in with the same Google hosted domain to be added to your Tuist organization, you can set it up with: ```bash tuist organization update sso my-organization --provider google --organization-id my-google-domain.com ``` For on-premise customers that have Okta set up, you can get the same behavior as for Google by running: ```bash tuist organization update sso my-organization --provider okta --organization-id my-okta-domain.com ``` > [!IMPORTANT] > You must be authenticated with Google using an email tied to the organization whose domain you are setting up. ## As a project {#as-a-project} In non-interactive environments like continuous integrations', you can't authenticate through an interactive flow. For those environments, we recommend authenticating as a project by using a project-scoped token: ```bash tuist project tokens create ``` The CLI expects the token to be defined as the environment variable `TUIST_CONFIG_TOKEN`, and the `CI=1` environment variable to be set. The CLI will use the token to authenticate the requests. > [!IMPORTANT] LIMITED SCOPE > The permissions of the project-scoped token are limited to the actions that we consider safe for projects to perform from a CI environment. We plan to document the permissions that the token has in the future. --- URL: "/es/server/introduction/accounts-and-projects" LLMS_URL: "/es/server/introduction/accounts-and-projects.md" title: "Accounts and projects" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn how to create and manage accounts and projects in Tuist." --- # Accounts and projects {#accounts-and-projects} ## Accounts {#accounts} To use the server, you'll need an account. There are two types of accounts: - **Personal account:** Those accounts are created automaticaly when you sign up and are identified by a handle that's obtained either from the identity provider (e.g. GitHub) or the first part of the email address. - **Organization account:** Those accounts are manually created and are identified by a handle that's defined by the developer. Organizations allow inviting other members to collaborate on projects. If you are familiar with [GitHub](https://github.com), the concept is similar to theirs, where you can have personal and organization accounts, and they are identified by a _handle_ that's used when constructing URLs. > [!NOTE] CLI-FIRST > Most operations to manage accounts and projects are done through the CLI. We are working on a web interface that will make it easier to manage accounts and projects. You can manage the organization through the subcommands under `tuist organization`. To create a new organization account, run: ```bash tuist organization create {account-handle} ``` ## Projects {#projects} Your projects, either Tuist's or raw Xcode's, need to be integrated with your account through a remote project. Continuing with the comparison with GitHub, it's like having a local and a remote repository where you push your changes. You can use the `tuist project` to create and manage projects. Projects are identified by a full handle, which is the result of concatenating the organization handle and the project handle. For example, if you have an organization with the handle `tuist`, and a project with the handle `tuist`, the full handle would be `tuist/tuist`. The binding between the local and the remote project is done through the configuration file. If you don't have any, create it at `Tuist.swift` and add the following content: ```swift let tuist = Tuist(fullHandle: "{account-handle}/{project-handle}") // e.g. tuist/tuist ``` > [!IMPORTANT] TUIST PROJECT-ONLY FEATURES > Note that there are some features like binary caching that require you having a Tuist project. If you are using raw Xcode projects, you won't be able to use those features. Your project's URL is constructed by using the full handle. For example, Tuist's dashboard, which is public, is accessible at [cloud.tuist.io/tuist/tuist](https://cloud.tuist.io/tuist/tuist), where `tuist/tuist` is the project's full handle. --- URL: "/es/references/project-description/[identifier]" LLMS_URL: "/es/references/project-description/[identifier].md" editLink: false titleTemplate: ":title · Project Description · References · Tuist" --- --- URL: "/es/references/migrations/from-v3-to-v4" LLMS_URL: "/es/references/migrations/from-v3-to-v4.md" title: "From v3 to v4" titleTemplate: ":title · Migrations · References · Tuist" description: "This page documents how to migrate the Tuist CLI from the version 3 to version 4." --- # From Tuist v3 to v4 {#from-tuist-v3-to-v4} With the release of [Tuist 4](https://github.com/tuist/tuist/releases/tag/4.0.0), we took the opportunity to introduce some breaking changes to the project, which we believed would make the project easier to use and maintain in the long run. This document outlines the changes you will need to make to your project to upgrade from Tuist 3 to Tuist 4. ### Dropped version management through `tuistenv` {#dropped-version-management-through-tuistenv} Prior to Tuist 4, the installation script installed a tool, `tuistenv`, that would get renamed to `tuist` at installation time. The tool would take care of installing and activating versions of Tuist ensuring determinism across environments. With the aim of reducing the feature surface of Tuist, we decided to drop `tuistenv` in favor of [Mise](https://mise.jdx.dev/), a tool that does the same job but is more flexible and can be used across different tools. If you were using `tuistenv`, you'll have to uninstall the current version of Tuist by running `curl -Ls https://uninstall.tuist.io | bash` and then install it using the installation method of your choice. We strongly recommend the usage of Mise because it's able to install and activate versions deterministically across environments. ::: code-group ```bash [Uninstall tuistenv] curl -Ls https://uninstall.tuist.io | bash ``` ::: > [!IMPORTANT] MISE IN CI ENVIRONMENTS AND XCODE PROJECTS > If you decide to embrace the determinism that Mise brings across the board, we recommend checking out the documentation for how to use Mise in [CI environments](https://mise.jdx.dev/continuous-integration.html) and [Xcode projects](https://mise.jdx.dev/ide-integration.html#xcode). > [!NOTE] HOMEBREW IS SUPPORTED > Note that you can still install Tuist using Homebrew, which is a popular package manager for macOS. You can find the instructions on how to install Tuist using Homebrew in the installation guide. ### Dropped `init` constructors from `ProjectDescription` models {#dropped-init-constructors-from-projectdescription-models} With the aim of improving the readability and expressiveness of the APIs, we decided to remove the `init` constructors from all the `ProjectDescription` models. Every model now provides a static constructor that you can use to create instances of the models. If you were using the `init` constructors, you'll have to update your project to use the static constructors instead. > [!TIP] NAMING CONVENTION > The naming convention that we follow is to use the name of the model as the name of the static constructor. For example, the static constructor for the `Target` model is `Target.target`. ### Renamed `--no-cache` to `--no-binary-cache` {#renamed-nocache-to-nobinarycache} Because the `--no-cache` flag was ambiguous, we decided to rename it to `--no-binary-cache` to make it clear that it refers to the binary cache. If you were using the `--no-cache` flag, you'll have to update your project to use the `--no-binary-cache` flag instead. ### Renamed `tuist fetch` to `tuist install` {#renamed-tuist-fetch-to-tuist-install} We renamed the `tuist fetch` command to `tuist install` to align with the industry convention. If you were using the `tuist fetch` command, you'll have to update your project to use the `tuist install` command instead. ### [Adopt `Package.swift` as the DSL for dependencies](https://github.com/tuist/tuist/pull/5862) {#adopt-packageswift-as-the-dsl-for-dependencieshttpsgithubcomtuisttuistpull5862} Before Tuist 4, you could define dependencies in a `Dependencies.swift` file. This proprietary format broke the support in tools like [Dependabot](https://github.com/dependabot) or [Renovatebot](https://github.com/renovatebot/renovate) to automatically update dependencies. Moreover, it introduced unnecessary indirections for users. Therefore, we decided to embrace `Package.swift` as the only way to define dependencies in Tuist. If you were using the `Dependencies.swift` file, you'll have to move the content from your `Tuist/Dependencies.swift` to a `Package.swift` at the root, and use the `#if TUIST` directive to configure the integration. You can read more about how to integrate Swift Package dependencies here ### Renamed `tuist cache warm` to `tuist cache` {#renamed-tuist-cache-warm-to-tuist-cache} For brevity, we decided to rename the `tuist cache warm` command to `tuist cache`. If you were using the `tuist cache warm` command, you'll have to update your project to use the `tuist cache` command instead. ### Renamed `tuist cache print-hashes` to `tuist cache --print-hashes` {#renamed-tuist-cache-printhashes-to-tuist-cache-printhashes} We decided to rename the `tuist cache print-hashes` command to `tuist cache --print-hashes` to make it clear that it's a flag of the `tuist cache` command. If you were using the `tuist cache print-hashes` command, you'll have to update your project to use the `tuist cache --print-hashes` flag instead. ### Removed caching profiles {#removed-caching-profiles} Before Tuist 4, you could define caching profiles in `Tuist/Config.swift` which contained a configuration for the cache. We decided to remove this feature because it could lead to confusion when using it in the generation process with a profile other than the one that was used to generate the project. Moreover, it could lead to users using a debug profile to build a release version of the app, which could lead to unexpected results. In its place, we introduced the `--configuration` option, which you can use to specify the configuration you want to use when generating the project. If you were using caching profiles, you'll have to update your project to use the `--configuration` option instead. ### Removed `--skip-cache` in favor of arguments {#removed-skipcache-in-favor-of-arguments} We removed the flag `--skip-cache` from the `generate` command in favor of controlling for which targets the binary cache should be skipped by using the arguments. If you were using the `--skip-cache` flag, you'll have to update your project to use the arguments instead. ::: code-group ```bash [Before] tuist generate --skip-cache Foo ``` ```bash [After] tuist generate Foo ``` ::: ### [Dropped signing capabilities](https://github.com/tuist/tuist/pull/5716) {#dropped-signing-capabilitieshttpsgithubcomtuisttuistpull5716} Signing is already solved by community tooling like [Fastlane](https://fastlane.tools/) and Xcode itself, which do a much better job at that. We felt that signing was an stretch goal for Tuist, and that it was better to focus on the core features of the project. If you were using Tuist signing capabilities, which consisted of encrypting the certificates and profiles in the repository and installing them in the right places at generation time, you might want to replicate that logic in your own scripts that run before project generation. In particular: - A script that decrypts the certificates and profiles using a key either stored in the file-system or in an environment variable, and installs certificates in the keychain, and the provisioning profiles in the directory `~/Library/MobileDevice/Provisioning\ Profiles`. - A script that can take an existing profiles and certificates and encrypt them. > [!TIP] SIGNING REQUIREMENTS > Signing requires the right certificates to be present in the keychain and the provisioning profiles to be present in the directory `~/Library/MobileDevice/Provisioning\ Profiles`. You can use the `security` command-line tool to install certificates in the keychain and the `cp` command to copy the provisioning profiles to the right directory. ### Dropped Carthage integration via `Dependencies.swift` {#dropped-carthage-integration-via-dependenciesswift} Before Tuist 4, Carthage dependencies could be defined in a `Dependencies.swift` file, which users could then fetch by running `tuist fetch`. We also felt that this was a stretch goal for Tuist, specially considering a future where Swift Package Manager would be the preferred way to manage dependencies. If you were using Carthage dependencies, you'll have to use `Carthage` directly to pull the pre-compiled frameworks and XCFrameworks into Carthage's standard directory, and then reference those binaries from your tagets using the `TargetDependency.xcframework` and `TargetDependency.framework` cases. > [!NOTE] CARTHAGE IS STILL SUPPORTED > Some users understood that we dropped Carthage support. We didn't. The contract between Tuist and Carthage's output is to system-stored frameworks and XCFrameworks. The only thing that changed is who is responsible for fetching the dependencies. It used to be Tuist through Carthage, now it's Carthage. ### Dropped the `TargetDependency.packagePlugin` API {#dropped-the-targetdependencypackageplugin-api} Before Tuist 4, you could define a package plugin dependency using the `TargetDependency.packagePlugin` case. After seeing the Swift Package Manager introducing new package types, we decided to iterate on the API towards something that would be more flexible and future-proof. If you were using `TargetDependency.packagePlugin`, you'll have to use `TargetDependency.package` instead, and pass the type of package you want to use as an argument. ### [Dropped deprecated APIs](https://github.com/tuist/tuist/pull/5560) {#dropped-deprecated-apishttpsgithubcomtuisttuistpull5560} We removed the APIs that were marked as deprecated in Tuist 3. If you were using any of the deprecated APIs, you'll have to update your project to use the new APIs. --- URL: "/es/references/examples/[example]" LLMS_URL: "/es/references/examples/[example].md" editLink: false titleTemplate: ":title · Examples · References · Tuist" --- Check out example --- URL: "/es" LLMS_URL: "/es.md" title: "What is Tuist?" description: "Extend your Apple native tooling to better apps at scale." --- # From idea to the store We are the only **integrated extension of Apple's native toolchain to build better apps faster.**
## Installation Install Tuist and run `tuist init` to get started: ::: code-group ```bash [Homebrew] brew tap tuist/tuist brew install --formula tuist tuist init ``` ```bash [Mise] mise x tuist@latest -- tuist init ``` ::: Check out our installation guide for more details. ## Discover more Try out Tuist in minutes and learn how to get the most out of Tuist. ## Watch our latest talks Explore our team's presentations. Stay informed and gain expertise. ## Join the community See the source code, connect with others, and get connected. --- URL: "/es/guides/tuist/about" LLMS_URL: "/es/guides/tuist/about.md" title: "About Tuist" titleTemplate: ":title · Guides · Tuist" description: "Extend your Apple native tooling to better apps at scale." --- # About Tuist {#about-tuist} In the world of app development, particularly for platforms like Apple's, organizations often encounter **productivity roadblocks.** These can include sluggish compilation times, unreliable tests, and intricate automation workflows that drain resources. Traditionally, companies address these issues by forming dedicated platform teams. These specialists maintain codebase health and integrity, freeing other developers to focus on feature creation. However, this approach can be expensive and risky, as the departure of key team members can severely impact productivity. ## What {#what} **Tuist is a toolchain designed to accelerate and enhance app development.** We integrate seamlessly with official tools and systems, meeting developers in familiar territory. By shouldering the burden of tool and system integration, we enable teams to channel their energy into feature development and improving the overall developer experience. In essence, Tuist serves as your virtual platform team. We're with you every step of the way - from the spark of an app idea to its user launch - tackling challenges as they arise. Tuist is comprised of a [CLI](https://github.com/tuist/tuist), which is the main entry point for developers, and a server that the CLI integrates with to persist state and integrate with other publicly available services. ## Why {#why} Why choose Tuist? Here are compelling reasons: ### Simplify 🌱 {#simplify} As projects grow and span multiple platforms, modularization becomes crucial. Tuist streamlines this complexity, offering tools to optimize and better understand your project's structure. **Further reading:** Projects ### Optimize workflows 🚀 {#optimize-workflows} Leveraging project information, Tuist enhances efficiency through selective test execution and deterministic binary reuse across builds. **Further reading:** Cache, Selective testing, Registry, and Previews ### Foster healthy project evolution 📈 {#foster-healthy-project-evolution} We provide insights into your project's dynamics and expert guidance for informed decision-making. This approach prevents the frustration and productivity loss associated with unhealthy projects, which can lead to developer attrition and missed business goals. **Further reading:** Server ### Break down silos 💜 {#break-down-silos} Unlike platform-specific ecosystems (e.g., Xcode's contained environment), Tuist offers web-centric experiences and integrates seamlessly with popular tools like Slack, Prometheus, and GitHub, enhancing cross-tool collaboration. **Further reading:** Projects --- If you want to know more about Tuist, the project, and the company, you can check out our [handbook](https://handbook.tuist.io/), which contains detailed information about our vision, values, and the team behind Tuist. --- URL: "/es/guides/share/previews" LLMS_URL: "/es/guides/share/previews.md" title: "Previews" titleTemplate: ":title · Share · Guides · Tuist" description: "Learn how to generate and share previews of your apps with anyone." --- # Previews {#previews} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project When building an app, you may want to share it with others to get feedback. Traditionally, this is something that teams do by building, signing, and pushing their apps to platforms like Apple's [TestFlight](https://developer.apple.com/testflight/). However, this process can be cumbersome and slow, especially when you're just looking for quick feedback from a colleague or a friend. To make this process more streamlined, Tuist provides a way to generate and share previews of your apps with anyone. > [!IMPORTANT] DEVICE BUILDS NEED TO BE SIGNED > When building for device, it is currently your responsibility to ensure the app is signed correctly. We plan to streamline this in the future. :::code-group ```bash [Tuist Project] tuist build App # Build the app for the simulator tuist build App -- -destination 'generic/platform=iOS' # Build the app for the device tuist share App ``` ```bash [Xcode Project] xcodebuild -scheme App -project App.xcodeproj -configuration Debug # Build the app for the simulator xcodebuild -scheme App -project App.xcodeproj -configuration Debug -destination 'generic/platform=iOS' # Build the app for the device tuist share App --configuration Debug --platforms iOS tuist share App.ipa # Share an existing .ipa file ``` ::: The command will generate a link that you can share with anyone to run the app – either on a simulator or an actual device. All they'll need to do is to run the command below: ```bash tuist run {url} tuist run --device "My iPhone" {url} # Run the app on a specific device ``` When sharing an `.ipa` file, you can download the app directly from the mobile device using the Preview link. The links to `.ipa` previews are by default _public_. In the future, you will have an option to make them private, so that the recipient of the link would need to authenticate with their Tuist account to download the app. `tuist run` also enables you to run a latest preview based on a specifier such as `latest`, branch name, or a specific commit hash: ```bash tuist run App@latest # Runs latest App preview associated with the project's default branch tuist run App@my-feature-branch # Runs latest App preview associated with a given branch tuist run App@00dde7f56b1b8795a26b8085a781fb3715e834be # Runs latest App preview associated with a given git commit sha ``` > [!IMPORTANT] PREVIEWS' VISIBILITY > Only people with access to the organization the project belongs to can access the previews. We plan to add support for expiring links. ## Tuist macOS app {#tuist-macos-app}

Tuist

Download
To make running Tuist Previews even easier, we developed a Tuist macOS menu bar app. Instead of running Previews via the Tuist CLI, you can [download](https://tuist.dev/download) the macOS app. You can also install the app by running `brew install --cask tuist/tuist/tuist`. When you now click on "Run" in the Preview page, the macOS app will automatically launch it on your currently selected device. > [!IMPORTANT] REQUIREMENTS > To download Previews, you need to first authenticate with the `tuist auth login` command. > In the future, you will be able to authenticate directly in the app. > > Additionally, you need to have Xcode locally installed. ## Pull/merge request comments {#pullmerge-request-comments} > [!IMPORTANT] INTEGRATION WITH GIT PLATFORM REQUIRED > To get automatic pull/merge request comments, integrate your remote project with a Git platform. Testing new functionality should be a part of any code review. But having to build an app locally adds unnecessary friction, often leading to developers skipping testing functionality on their device at all. But _what if each pull request contained a link to the build that would automatically run the app on a device you selected in the Tuist macOS app?_ Once your Tuist project is connected with your Git platform such as [GitHub](https://github.com), add a `tuist share MyApp` to your CI workflow. Tuist will then post a Preview link directly in your pull requests: ![GitHub app comment with a Tuist Preview link](/images/guides/share/github-app-with-preview.png) ## README badge {#readme-badge} To make Tuist Previews more visible in your repository, you can add a badge to your `README` file that points to the latest Tuist Preview: [![Tuist Preview](https://tuist.dev/Dimillian/IcySky/previews/latest/badge.svg)](https://tuist.dev/Dimillian/IcySky/previews/latest) To add the badge to your `README`, use the following markdown and replace the account and project handles with your own: ``` [![Tuist Preview](https://tuist.dev/{account-handle}/{project-handle}/previews/latest/badge.svg)](https://tuist.dev/{account-handle}/{project-handle}/previews/latest) ``` ## Automations {#automations} You can use the `--json` flag to get a JSON output from the `tuist share` command: ``` tuist share --json ``` The JSON output is useful to create custom automations, such as posting a Slack message using your CI provider. The JSON contains a `url` key with the full preview link and a `qrCodeURL` key with the URL to the QR code image to make it easier to download previews from a real device. An example of a JSON output is below: ```json { "id": 1234567890, "url": "https://cloud.tuist.io/preview/1234567890", "qrCodeURL": "https://cloud.tuist.io/preview/1234567890/qr-code.svg" } ``` --- URL: "/es/guides/quick-start/install-tuist" LLMS_URL: "/es/guides/quick-start/install-tuist.md" title: "Install Tuist" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "Learn how to install Tuist in your environment." --- # Install Tuist {#install-tuist} The Tuist CLI consists of an executable, dynamic frameworks, and a set of resources (for example, templates). Although you could manually build Tuist from [the sources](https://github.com/tuist/tuist), **we recommend using one of the following installation methods to ensure a valid installation.** ### Mise {#recommended-mise} :::info Mise is a recommended alternative to [Homebrew](https://brew.sh) if you are a team or organization that needs to ensure deterministic versions of tools across different environments. ::: You can install Tuist through any of the following commands: ```bash mise install tuist # Install the current version specified in .tool-versions/.mise.toml mise install tuist@x.y.z # Install a specific version number mise install tuist@3 # Install a fuzzy version number ``` Note that unlike tools like Homebrew, which install and activate a single version of the tool globally, **Mise requires the activation of a version** either globally or scoped to a project. This is done by running `mise use`: ```bash mise use tuist@x.y.z # Use tuist-x.y.z in the current project mise use tuist@latest # Use the latest tuist in the current directory mise use -g tuist@x.y.z # Use tuist-x.y.z as the global default mise use -g tuist@system # Use the system's tuist as the global default ``` ### Homebrew {#recommended-homebrew} You can install Tuist using [Homebrew](https://brew.sh) and [our formulas](https://github.com/tuist/homebrew-tuist): ```bash brew tap tuist/tuist brew install --formula tuist brew install --formula tuist@x.y.z ``` :::tip VERIFYING THE AUTHENTICITY OF THE BINARIES ```bash curl -fsSL "https://docs.tuist.dev/verify.sh" | bash ``` ::: --- URL: "/es/guides/quick-start/get-started" LLMS_URL: "/es/guides/quick-start/get-started.md" title: "Get started" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "Learn how to install Tuist in your environment." --- # Get started {#get-started} The easiest way to get started with Tuist in any directory or in the directory of your Xcode project or workspace: ::: code-group ```bash [Mise] mise x tuist@latest -- tuist init ``` ```bash [Global Tuist (Homebrew)] tuist init ``` ::: The command will walk you through the steps to create a generated project or integrate an existing Xcode project or workspace. It helps you connect your setup to the remote server, giving you access to features like selective testing, previews, and the registry. > [!NOTE] MIGRATE AN EXISTING PROJECT > If you want to migrate an existing project to generated projects to improve the developer experience and take advantage of our cache, check out our migration guide. --- URL: "/es/guides/quick-start/gather-insights" LLMS_URL: "/es/guides/quick-start/gather-insights.md" title: "Gather insights" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "Learn how to gather insights about your project." --- # Gather insights {#gather-insights} Tuist can integrate with a server to extend its capabilities. One of those capabilities is gathering insights about your project and builds. All you need is to have an account with a project in the server. First of all, you'll need to authenticate by running: ```bash tuist auth login ``` ## Create a project {#create-a-project} You can then create a project by running: ```bash tuist project create my-handle/MyApp # Tuist project my-handle/MyApp was successfully created 🎉 {#tuist-project-myhandlemyapp-was-successfully-created-} ``` Copy `my-handle/MyApp`, which represents the full handle of the project. ## Connect projects {#connect-projects} After creating the project on the server, you'll have to connect it to your local project. Run `tuist edit` and edit the `Tuist.swift` file to include the full handle of the project: ```swift import ProjectDescription let tuist = Tuist(fullHandle: "my-handle/MyApp") ``` Voilà! You're now ready to gather insights about your project and builds. Run `tuist test` to run the tests reporting the results to the server. > [!NOTE] > Tuist enqueues the results locally and tries to send them without blocking the command. Therefore, they might not be sent immediately after the command finishes. In CI, the results are sent immediately. ![An image that shows a list of runs in the server](/images/guides/quick-start/runs.png) Having data from your projects and builds is crucial in making informed decisions. Tuist will continue to extend its capabilities, and you'll benefit from them without having to change your project configuration. Magic, right? 🪄 --- URL: "/es/guides/quick-start/add-dependencies" LLMS_URL: "/es/guides/quick-start/add-dependencies.md" title: "Add dependencies" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "Learn how to add dependencies to your first Swift project" --- # Add dependencies {#add-dependencies} It's common for projects to depend on third-party libraries to provide additional functionality. To do so, run the following command to have the best experience editing your project: ```bash tuist edit ``` An Xcode project will open containing your project files. Edit the `Package.swift` and add the ```swift // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies .package(url: "https://github.com/onevcat/Kingfisher", .upToNextMajor(from: "7.12.0")) // [!code ++] ] ) ``` Then edit the application target in your project to declare `Kingfisher` as a dependency: ```swift import ProjectDescription let project = Project( name: "MyApp", targets: [ .target( name: "MyApp", destinations: .iOS, product: .app, bundleId: "io.tuist.MyApp", infoPlist: .extendingDefault( with: [ "UILaunchStoryboardName": "LaunchScreen.storyboard", ] ), sources: ["MyApp/Sources/**"], resources: ["MyApp/Resources/**"], dependencies: [ .external(name: "Kingfisher") // [!code ++] ] ), .target( name: "MyAppTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyAppTests", infoPlist: .default, sources: ["MyApp/Tests/**"], resources: [], dependencies: [.target(name: "MyApp")] ), ] ) ``` Then run `tuist install` to resolve and pull the dependencies using the [Swift Package Manager](https://www.swift.org/documentation/package-manager/). > [!NOTE] SPM AS A DEPENDENCY RESOLVER > Tuist recommended approach to dependencies uses the Swift Package Manager (SPM) only to resolve dependencies. Tuist then converts them into Xcode projects and targets for maximum configurability and control. ## Visualize the project {#visualize-the-project} You can visualize the project structure by running: ```bash tuist graph ``` The command will output and open a `graph.png` file in the project's directory: ![Project graph](/images/guides/quick-start/graph.png) ## Use the dependency {#use-the-dependency} Run `tuist generate` to open the project in Xcode, and make the following changes to the `ContentView.swift` file: ```swift import SwiftUI import Kingfisher // [!code ++] public struct ContentView: View { public init() {} public var body: some View { Text("Hello, World!") // [!code --] .padding() // [!code --] KFImage(URL(string: "https://cloud.tuist.io/images/tuist_logo_32x32@2x.png")!) // [!code ++] } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } ``` Run the app from Xcode, and you should see the image loaded from the URL. --- URL: "/es/guides/develop/test" LLMS_URL: "/es/guides/develop/test.md" title: "tuist test" titleTemplate: ":title · Develop · Guides · Tuist" description: "Learn how to run tests efficiently with Tuist." --- # Test {#test} Tuist provides a command, `tuist test` to generate the project if needed, and then run the tests with the the platform-specific build tool (e.g. `xcodebuild` for Apple platforms). You might wonder what's the value of using `tuist test` over generating the project with `tuist generate` and running the tests with the platform-specific build tool. - **Single command:** `tuist test` ensures the project is generated if needed before compiling the project. - **Beautified output:** Tuist enriches the output using tools like [xcbeautify](https://github.com/cpisciotta/xcbeautify) that make the output more user-friendly. - Cache: It optimizes the build by deterministically reusing the build artifacts from a remote cache. - Smart runner: It runs only the tests that need to be run, saving time and resources. - Flakiness: Prevent, detect, and fix flaky tests. ## Usage {#usage} To run the tests of a project, you can use the `tuist test` command. This command will generate the project if needed, and then run the tests using the platform-specific build tool. We support the use of the `--` terminator to forward all subsequent arguments directly to the build tool. ::: code-group ```bash [Running scheme tests] tuist test MyScheme ``` ```bash [Running all tests without binary cache] tuist test --no-binary-cache ``` ```bash [Running all tests without selective testing] tuist test --no-selective-testing ``` ::: ## Pull/merge request comments {#pullmerge-request-comments} > [!IMPORTANT] REQUIREMENTS > To get automatic pull/merge request comments, integrate your remote project with a Git platform. When running tests in your CI environments we can correlate the test results with the pull/merge request that triggered the CI build. This allows us to post a comment on the pull/merge request with the test results. ![GitHub App example](/images/contributors/scheme-arguments.png) --- URL: "/es/guides/develop/selective-testing/xcodebuild" LLMS_URL: "/es/guides/develop/selective-testing/xcodebuild.md" title: "xcodebuild" titleTemplate: ":title · Selective testing · Develop · Guides · Tuist" description: "Learn how to leverage selective testing with `xcodebuild`." --- # xcodebuild {#xcodebuild} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project To run tests selectively using `xcodebuild`, you can prepend your `xcodebuild` command with `tuist` – for example, `tuist xcodebuild test -scheme App`. The command hashes your project and on success, it persists the hashes to determine what has changed in future runs. In future runs `tuist xcodebuild test` transparently uses the hashes to filter down the tests to run only the ones that have changed since the last successful test run. For example, assuming the following dependency graph: - `FeatureA` has tests `FeatureATests`, and depends on `Core` - `FeatureB` has tests `FeatureBTests`, and depends on `Core` - `Core` has tests `CoreTests` `tuist xcodebuild test` will behave as such: | Action | Description | Internal state | | ---------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | `tuist xcodebuild test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The hashes of `FeatureATests`, `FeatureBTests` and `CoreTests` are persisted | | `FeatureA` is updated | The developer modifies the code of a target | Same as before | | `tuist xcodebuild test` invocation | Runs the tests in `FeatureATests` because it hash has changed | The new hash of `FeatureATests` is persisted | | `Core` is updated | The developer modifies the code of a target | Same as before | | `tuist xcodebuild test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The new hash of `FeatureATests` `FeatureBTests`, and `CoreTests` are persisted | To use `tuist xcodebuild test` on your CI, follow the instructions in the Continuous integration guide. --- URL: "/es/guides/develop/selective-testing/generated-project" LLMS_URL: "/es/guides/develop/selective-testing/generated-project.md" title: "Generated project" titleTemplate: ":title · Selective testing · Develop · Guides · Tuist" description: "Learn how to leverage selective testing with a generated project." --- # Generated project {#generated-project} > [!IMPORTANT] REQUIREMENTS > > - A generated project > - A Tuist account and project To run tests selectively with your generated project, use the `tuist test` command. The command hashes your Xcode project the same way it does for warming the cache, and on success, it persists the hashes on to determine what has changed in future runs. In future runs `tuist test` transparently uses the hashes to filter down the tests to run only the ones that have changed since the last successful test run. For example, assuming the following dependency graph: - `FeatureA` has tests `FeatureATests`, and depends on `Core` - `FeatureB` has tests `FeatureBTests`, and depends on `Core` - `Core` has tests `CoreTests` `tuist test` will behave as such: | Action | Description | Internal state | | ----------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | `tuist test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The hashes of `FeatureATests`, `FeatureBTests` and `CoreTests` are persisted | | `FeatureA` is updated | The developer modifies the code of a target | Same as before | | `tuist test` invocation | Runs the tests in `FeatureATests` because it hash has changed | The new hash of `FeatureATests` is persisted | | `Core` is updated | The developer modifies the code of a target | Same as before | | `tuist test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The new hash of `FeatureATests` `FeatureBTests`, and `CoreTests` are persisted | `tuist test` integrates directly with binary caching to use as many binaries from your local or remote storage to improve the build time when running your test suite. The combination of selective testing with binary caching can dramatically reduce the time it takes to run tests on your CI. ## UI Tests {#ui-tests} Tuist supports selective testing of UI tests. However, Tuist needs to know the destination in advance. Only if you specify the `destination` parameter, Tuist will run the UI tests selectively, such as: ```sh tuist test --device 'iPhone 14 Pro' # or tuist test -- -destination 'name=iPhone 14 Pro' # or tuist test -- -destination 'id=SIMULATOR_ID' ``` --- URL: "/es/guides/develop/selective-testing" LLMS_URL: "/es/guides/develop/selective-testing.md" title: "Selective testing" titleTemplate: ":title · Develop · Guides · Tuist" description: "Use selective testing to run only the tests that have changed since the last successful test run." --- # Selective testing {#selective-testing} As your project grows, so does the amount of your tests. For a long time, running all tests on every PR or push to `main` takes tens of seconds. But this solution does not scale to thousands of tests your team might have. On every test run on the CI, you most likely re-run all the tests, regardless of the changes. Tuist's selective testing helps you to drastically speed up running the tests themselves by running only the tests that have changed since the last successful test run based on our hashing algorithm. Selective testing works with `xcodebuild`, which supports any Xcode project, or if you generate your projects with Tuist, you can use the `tuist test` command instead that provides some extra convenience such as integration with the binary cache. To get started with selective testing, follow the instructions based on your project setup: - xcodebuild - Generated project > [!WARNING] MODULE VS FILE-LEVEL GRANULARITY > Due to the impossibility of detecting the in-code dependencies between tests and sources, the maximum granularity of selective testing is at the target level. Therefore, we recommend keeping your targets small and focused to maximize the benefits of selective testing. ## Pull/merge request comments {#pullmerge-request-comments} > [!IMPORTANT] INTEGRATION WITH GIT PLATFORM REQUIRED > To get automatic pull/merge request comments, integrate your Tuist project with a Git platform. Once your Tuist project is connected with your Git platform such as [GitHub](https://github.com), and you start using `tuist xcodebuild test` or `tuist test` as part of your CI wortkflow, Tuist will post a comment directly in your pull/merge requests, including which tests were run and which skipped: ![GitHub app comment with a Tuist Preview link](/images/guides/develop/github-app-comment.png) --- URL: "/es/guides/develop/registry/xcodeproj-integration" LLMS_URL: "/es/guides/develop/registry/xcodeproj-integration.md" title: "Generated project with the XcodeProj-based package integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in a generated Xcode project with the XcodeProj-based package integration." --- # Generated project with the XcodeProj-based package integration {#generated-project-with-xcodeproj-based-integration} When using the XcodeProj-based integration, you can use the `--replace-scm-with-registry` flag to resolve dependencies from the registry if they are available. Add it to the `installOptions` in your `Tuist.swift` file: ```swift import ProjectDescription let tuist = Tuist( fullHandle: "{account-handle}/{project-handle}", project: .tuist( installOptions: .options(passthroughSwiftPackageManagerArguments: ["--replace-scm-with-registry"]) ) ) ``` If you want to ensure that the registry is used every time you resolve dependencies, you will need to update `dependencies` in your `Tuist/Package.swift` file to use the registry identifier instead of a URL. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `swift-composable-architecture` package, do the following: ```diff dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") + .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ] ``` --- URL: "/es/guides/develop/registry/xcode-project" LLMS_URL: "/es/guides/develop/registry/xcode-project.md" title: "Xcode project" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in an Xcode project." --- # Xcode project {#xcode-project} To add packages using the registry in your Xcode project, use the default Xcode UI. You can search for packages in the registry by clicking on the `+` button in the `Package Dependencies` tab in Xcode. If the package is available in the registry, you will see the `tuist.dev` registry in the top right: ![Adding package dependencies](/images/guides/develop/build/registry/registry-add-package.png) > [!NOTE] > Xcode currently doesn't support automatically replacing source control packages with their registry equivalents. You will need to manually remove the source control package and add the registry package to speed up the resolution. --- URL: "/es/guides/develop/registry/swift-package" LLMS_URL: "/es/guides/develop/registry/swift-package.md" title: "Swift package" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in a Swift package." --- # Swift package {#swift-package} If you are working on a Swift package, you can use the `--replace-scm-with-registry` flag to resolve dependencies from the registry if they are available: ```bash swift package --replace-scm-with-registry resolve ``` If you want to ensure that the registry is used every time you resolve dependencies, you will need to update `dependencies` in your `Package.swift` file to use the registry identifier instead of a URL. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `swift-composable-architecture` package, do the following: ```diff dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") + .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ] ``` --- URL: "/es/guides/develop/registry/generated-project" LLMS_URL: "/es/guides/develop/registry/generated-project.md" title: "Generated project with the Xcode package integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in a generated Xcode project with the Xcode package integration." --- # Generated project with the Xcode package integration {#generated-project-with-xcode-based-integration} If you are using the Xcode's default integration of packages with Tuist Projects, you need to use the registry identifier instead of a URL when adding a package: ```swift import ProjectDescription let project = Project( name: "MyProject", packages: [ // Source control resolution // .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") // Registry resolution .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ], .target( name: "App", product: .app, bundleId: "io.tuist.App", dependencies: [ .package(product: "ComposableArchitecture"), ] ) ) ``` --- URL: "/es/guides/develop/registry/continuous-integration" LLMS_URL: "/es/guides/develop/registry/continuous-integration.md" title: "Continuous integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in continuous integration." --- # Continuous Integration (CI) {#continuous-integration-ci} To use the registry on your CI, you need to ensure that you have logged in to the registry by running `tuist registry login` as part of your workflow. > [!NOTE] ONLY XCODE INTEGRATION > Creating a new pre-unlocked keychain is required only if you are using the Xcode integration of packages. Since the registry credentials are stored in a keychain, you need to ensure the keychain can be accessed in the CI environment. Note some CI providers or automation tools like [Fastlane](https://fastlane.tools/) already create a temporary keychain or provide a built-in way how to create one. However, you can also create one by creating a custom step with the following code: ```bash TMP_DIRECTORY=$(mktemp -d) KEYCHAIN_PATH=$TMP_DIRECTORY/keychain.keychain KEYCHAIN_PASSWORD=$(uuidgen) security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security default-keychain -s $KEYCHAIN_PATH security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH ``` `tuist registry login` will then store the credentials in the default keychain. Ensure that your default keychain is created and unlocked _before_ `tuist registry login` is run. Additionally, you need to ensure the `TUIST_CONFIG_TOKEN` environment variable is set. You can create one by following the documentation here. An example workflow for GitHub Actions could then look like this: ```yaml name: Build jobs: build: steps: - # Your set up steps... - name: Create keychain run: | TMP_DIRECTORY=$(mktemp -d) KEYCHAIN_PATH=$TMP_DIRECTORY/keychain.keychain KEYCHAIN_PASSWORD=$(uuidgen) security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security default-keychain -s $KEYCHAIN_PATH security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH - name: Log in to the Tuist Registry env: TUIST_CONFIG_TOKEN: ${{ secrets.TUIST_CONFIG_TOKEN }} run: tuist registry login - # Your build steps ``` ### Incremental resolution across environments {#incremental-resolution-across-environments} Clean/cold resolutions are slightly faster with our registry, and you can experience even greater improvements if you persist the resolved dependencies across CI builds. Note that thanks to the registry, the size of the directory that you need to store and restore is much smaller than without the registry, taking significantly less time. To cache dependencies when using the default Xcode package integration, the best way is to specify a custom `-clonedSourcePackagesDirPath` when resolving dependencies via `xcodebuild`, such as: ```sh xcodebuild -resolvePackageDependencies -clonedSourcePackagesDirPath .build ``` Additionally, you will need to find a path of the `Package.resolved`. You can grab the path by running `ls **/Package.resolved`. The path should look something like `App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved`. For Swift packages and the XcodeProj-based integration, we can use the default `.build` directory located either in the root of the project or in the `Tuist` directory. Make sure the path is correct when setting up your pipeline. Here's an example workflow for GitHub Actions for resolving and caching dependencies when using the default Xcode package integration: ```yaml - name: Restore cache id: cache-restore uses: actions/cache/restore@v4 with: path: .build key: ${{ runner.os }}-${{ hashFiles('App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} restore-keys: .build - name: Resolve dependencies if: steps.cache-restore.outputs.cache-hit != 'true' run: xcodebuild -resolvePackageDependencies -clonedSourcePackagesDirPath .build - name: Save cache id: cache-save uses: actions/cache/save@v4 with: path: .build key: ${{ steps.cache-restore.outputs.cache-primary-key }} ``` --- URL: "/es/guides/develop/registry" LLMS_URL: "/es/guides/develop/registry.md" title: "Registry" titleTemplate: ":title · Develop · Guides · Tuist" description: "Optimize your Swift package resolution times by leveraging the Tuist Registry." --- # Registry {#registry} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project As the number of dependencies grows, so does the time to resolve them. While other package managers like [CocoaPods](https://cocoapods.org/) or [npm](https://www.npmjs.com/) are centralized, Swift Package Manager is not. Because of that, SwiftPM needs to resolve dependencies by doing a deep clone of each repository, which can be time-consuming. To address this, Tuist provides an implementation of the [Package Registry](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/PackageRegistryUsage.md), so you can download only the commit you _actually need_. The packages in the registry are based on the [Swift Package Index](https://swiftpackageindex.com/) – if you can find a package there, the package is also available in the Tuist Registry. Additionally, the packages are distributed across the globe using an edge storage for minimum latency when resolving them. ## Usage {#usage} To set up and log in to the registry, run the following command in your project's directory: ```bash tuist registry setup ``` This command generates a registry configuration files and logs you in to the registry. To ensure the rest of your team can access the registry, ensure the generated files is committed and that your team members run the following command to log in: ```bash tuist registry login ``` Now you can access the registry! To resolve dependencies from the registry instead of from source control, continue reading based on your project setup: - Xcode project - Generated project with the Xcode package integration - Generated project with the XcodeProj-based package integration - Swift package To set up the registry on the CI, follow this guide: Continuous integration. ### Package registry identifiers {#package-registry-identifiers} If you want to ensure that the registry is used every time you resolve dependencies, you will need to update `dependencies` in your `Package.swift` file to use the registry identifier instead of a URL. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `swift-composable-architecture` package, do the following: > [!NOTE] > The identifier can't contain more than one dot. If the repository name contains a dot, it's replaced with an underscore. > For example, the `https://github.com/groue/GRDB.swift` package would have the registry identifier `groue.GRDB_swift`. --- URL: "/es/guides/develop/projects/tma-architecture" LLMS_URL: "/es/guides/develop/projects/tma-architecture.md" title: "The Modular Architecture (TMA)" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about The Modular Architecture (TMA) and how to structure your projects using it." --- # The Modular Architecture (TMA) {#the-modular-architecture-tma} TMA is an architectural approach to structure Apple OS applications to enable scalability, optimize build and test cycles, and ensure good practices in your team. Its core idea is to build your apps by building independent features that are interconnected using clear and concise APIs. These guidelines introduce the principles of the architecture, helping you identify and organize your application features in different layers. It also introduces tips, tools, and advice if you decide to use this architecture. > [!INFO] µFEATURES > This architecture was previously known as µFeatures. We've renamed it to The Modular Architecture (TMA) to better reflect its purpose and the principles behind it. ## Core principle {#core-principle} Developers should be able to **build, test, and try** their features fast, independently of the main app, and while ensuring Xcode features like UI previews, code completion, and debugging work reliably. ## What is a module {#what-is-a-module} A module represents an application feature and is a combination of the following five targets (where target referts to an Xcode target): - **Source:** Contains the feature source code (Swift, Objective-C, C++, JavaScript...) and its resources (images, fonts, storyboards, xibs). - **Interface:** It's a companion target that contains the public interface and models of the feature. - **Tests:** Contains the feature unit and integration tests. - **Testing:** Provides testing data that can be used in tests and the example app. It also provides mocks for module classes and protocols that can be used by other features as we'll see later. - **Example:** Contains an example app that developers can use to try out the feature under certain conditions (different languages, screen sizes, settings). We recommend following a naming convention for targets, something that you can enforce in your project thanks to Tuist's DSL. | Target | Dependencies | Content | | ------------------ | --------------------------- | --------------------------- | | `Feature` | `FeatureInterface` | Source code and resources | | `FeatureInterface` | - | Public interface and models | | `FeatureTests` | `Feature`, `FeatureTesting` | Unit and integration tests | | `FeatureTesting` | `FeatureInterface` | Testing data and mocks | | `FeatureExample` | `FeatureTesting`, `Feature` | Example app | > [!TIP] UI Previews > `Feature` can use `FeatureTesting` as a Development Asset to allow for UI previews > [!IMPORTANT] COMPILER DIRECTIVES INSTEAD OF TESTING TARGETS > Alternatively, you can use compiler directives to include test data and mocks in the `Feature` or `FeatureInterface` targets when compiling for `Debug`. You simplify the graph, but you'll end up compiling code that you won't need for running the app. ## Why a module {#why-a-module} ### Clear and concise APIs {#clear-and-concise-apis} When all the app source code lives in the same target it is very easy to build implicit dependencies in code and end up with the so well-known spaghetti code. Everything is strongly coupled, the state is sometimes unpredictable, and introducing new changes become a nightmare. When we define features in independent targets we need to design public APIs as part of our feature implementation. We need to decide what should be public, how our feature should be consumed, what should remain private. We have more control over how we want our feature clients to use the feature and we can enforce good practices by designing safe APIs. ### Small modules {#small-modules} [Divide and conquer](https://en.wikipedia.org/wiki/Divide_and_conquer). Working in small modules allows you to have more focus and test and try the feature in isolation. Moreover, development cycles are much faster since we have a more selective compilation, compiling only the components that are necessary to get our feature working. The compilation of the whole app is only necessary at the very end of our work, when we need to integrate the feature into the app. ### Reusability {#reusability} Reusing code across apps and other products like extensions is encouraged using frameworks or libraries. By building modules reusing them is pretty straightforward. We can build an iMessage extension, a Today Extension, or a watchOS application by just combining existing modules and adding _(when necessary)_ platform-specific UI layers. ## Dependencies {#dependencies} When a module depends on another module, it declares a dependency against its interface target. The benefit of this is two-fold. It prevents the implementation of a module to be coupled to the implementation of another module, and it speeds up clean builds because they only have to compile the implementation of our feature, and the interfaces of direct and transitive dependencies. This approach is inspired by SwiftRock's idea of [Reducing iOS Build Times by using Interface Modules](https://swiftrocks.com/reducing-ios-build-times-by-using-interface-targets). Depending on interfaces requires apps to build the graph of implementations at runtime, and dependency-inject it into the modules that need it. Although TMA is non-opinionated about how to do this, we recommend using dependency-injection solutions or patterns or solutions that don't add built-time indirections or use platform APIs that were not designed for this purpose. ## Product types {#product-types} When building a module, you can choose between **libraries and frameworks**, and **static and dynamic linking** for the targets. Without Tuist, making this decision is a bit more complex because you need to configure the dependency graph manually. However, thanks to Tuist Projects, this is no longer a problem. We recommend using dynamic libraries or frameworks during development using bundle accessors to decouple the bundle-accessing logic from the library or framework nature of the target. This is key for fast compilation times and to ensure [SwiftUI Previews](https://developer.apple.com/documentation/swiftui/previews-in-xcode) work reliably. And static libraries or frameworks for the release builds to ensure the app boots fast. You can leverage dynamic configuration to change the product type at generation-time: ```bash # You'll have to read the value of the variable from the manifest {#youll-have-to-read-the-value-of-the-variable-from-the-manifest} # and use it to change the linking type {#and-use-it-to-change-the-linking-type} TUIST_PRODUCT_TYPE=static-library tuist generate ``` ```swift // You can place this in your manifest files or helpers // and use the returned value when instantiating targets. func productType() -> Product { if case let .string(productType) = Environment.productType { return productType == "static-library" ? .staticLibrary : .framework } else { return .framework } } ``` > [!IMPORTANT] MERGEABLE LIBRARIES > Apple attempted to alleviate the cumbersomeness of switching between static and dynamic libraries by introducing [mergeable libraries](https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries). However, that introduces build-time non-determinism that makes your build non-reproducible and harder to optimize so we don't recommend using it. ## Code {#code} TMA is non-opinionated about the code architecture and patterns for your modules. However, we'd like to share some tips based on our experience: - **Leveraging the compiler is great.** Over-leveraging the compiler might end up being non-productive and cause some Xcode features like previews to work unreliably. We recommend using the compiler to enforce good practices and catch errors early, but not to the point that it makes the code harder to read and maintain. - **Use Swift Macros sparingly.** They can be very powerful but can also make the code harder to read and maintain. - **Embrace the platform and the language, don't abstract them.** Trying to come up with ellaborated abstraction layers might end up being counterproductive. The platform and the language are powerful enough to build great apps without the need for additional abstraction layers. Use good programming and design patterns as a reference to build your features. ## Resources {#resources} - [Building µFeatures](https://speakerdeck.com/pepibumur/building-ufeatures) - [Framework Oriented Programming](https://speakerdeck.com/pepibumur/framework-oriented-programming-mobilization-dot-pl) - [A Journey into frameworks and Swift](https://speakerdeck.com/pepibumur/a-journey-into-frameworks-and-swift) - [Leveraging frameworks to speed up our development on iOS - Part 1](https://developers.soundcloud.com/blog/leveraging-frameworks-to-speed-up-our-development-on-ios-part-1) - [Library Oriented Programming](https://academy.realm.io/posts/justin-spahr-summers-library-oriented-programming/) - [Building Modern Frameworks](https://developer.apple.com/videos/play/wwdc2014/416/) - [The Unofficial Guide to xcconfig files](https://pewpewthespells.com/blog/xcconfig_guide.html) - [Static and Dynamic Libraries](https://pewpewthespells.com/blog/static_and_dynamic_libraries.html) --- URL: "/es/guides/develop/projects/templates" LLMS_URL: "/es/guides/develop/projects/templates.md" title: "Templates" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to create and use templates in Tuist to generate code in your projects." --- # Templates {#templates} In projects with an established architecture, developers might want to bootstrap new components or features that are consistent with the project. With `tuist scaffold` you can generate files from a template. You can define your own templates or use the ones that are vendored with Tuist. These are some scenarios where scaffolding might be useful: - Create a new feature that follows a given architecture: `tuist scaffold viper --name MyFeature`. - Create new projects: `tuist scaffold feature-project --name Home` > [!NOTE] NON-OPINIONATED > Tuist is not opinionated about the content of your templates, and what you use them for. They are only required to be in a specific directory. ## Defining a template {#defining-a-template} To define templates, you can run `tuist edit` and then create a directory called `name_of_template` under `Tuist/Templates` that represents your template. So if you are creating a template called `framework`, you should create a new directory `framework` at `Tuist/Templates` with a manifest file called `framework.swift` that could look like this: To define templates, you can run `tuist edit` and then create a directory called `name_of_template` under `Tuist/Templates` that represents your template. To define templates, you can run `tuist edit` and then create a directory called `name_of_template` under `Tuist/Templates` that represents your template. ```swift import ProjectDescription let nameAttribute: Template.Attribute = .required("name") let template = Template( description: "Custom template", attributes: [ nameAttribute, .optional("platform", default: "ios"), ], items: [ .string( path: "Project.swift", contents: "My template contents of name \(nameAttribute)" ), .file( path: "generated/Up.swift", templatePath: "generate.stencil" ), .directory( path: "destinationFolder", sourcePath: "sourceFolder" ), ] ) ``` ## Using a template {#using-a-template} After defining the template, we can use it from the `scaffold` command: ```bash tuist scaffold name_of_template --name Name --platform macos ``` > [!NOTE] > Since platform is an optional argument, we can also call the command without the `--platform macos` argument. If `.string` and `.files` don't provide enough flexibility, you can leverage the [Stencil](https://stencil.fuller.li/en/latest/) templating language via the `.file` case. Besides that, you can also use additional filters defined here. Using string interpolation, `\(nameAttribute)` above would resolve to `{{ name }}`. If you'd like to use Stencil filters in the template definition, you can use that interpolation manually and add any filters you like. For example, you might use `{ { name | lowercase } }` instead of `\(nameAttribute)` to get the lowercased value of the name attribute. You can also use `.directory` which gives the possibility to copy entire folders to a given path. > [!TIP] PROJECT DESCRIPTION HELPERS > Templates support the use of project description helpers to reuse code across templates. --- URL: "/es/guides/develop/projects/synthesized-files" LLMS_URL: "/es/guides/develop/projects/synthesized-files.md" title: "Synthesized files" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about synthesized files in Tuist projects." --- # Synthesized files {#synthesized-files} Tuist can generate files and code at generation-time to bring some convenience to managing and working with Xcode projects. In this page you'll learn about this functionality, and how you can use it in your projects. ## Target resources {#target-resources} Xcode projects support adding resources to targets. However, they present teams with a few challenges, specially when working with a modular project where sources and resources are often moved around: - **Inconsistent runtime access**: Where the resources end up in the final product and how you access them depends on the target product. For example, if your target represents an application, the resources are copied to the application bundle. This leads to code accessing the resources that makes assumptions on the bundle structure, which is not ideal because it makes the code harder to reason about and the resources to move around. - **Products that don't support resources**: There are certain products like static libraries that are not bundles and therefore don't support resources. Because of that, you either have to resort to a different product type, for example frameworks, that might add some overhead on your project or app. For example, static frameworks will be linked statically to the final product, and a build phase is required to only copy the resources to the final product. Or dynamic frameworks, where Xcode will copy both the binary and the resources into the final product, but it'll increase the startup time of your app because the framework needs to be loaded dynamically. - **Prone to runtime errors**: Resources are identified by their name and extension (strings). Therefore, a typo in any of those will lead to a runtime error when trying to access the resource. This is not ideal because it's not caught at compile time and might lead to crashes in release. Tuist solves the problems above by **synthesizing a unified interface to access bundles and resources** that abstracts away the implementation details. > [!IMPORTANT] RECOMMENDED > Even though accessing resources through the Tuist-synthesized interface is not mandatory, we recommend it because it makes the code easier to reason about and the resources to move around. ## Resources {#resources} Tuist provides interfaces to declare the content of files such as `Info.plist` or entitlements in Swift. This is useful to ensure consistency across targets and projects, and leverage the compiler to catch issues at compile time. You can also come up with your own abstractions to model the content and share it across targets and projects. When your project is generated, Tuist will synthesize the content of those files and write them into the `Derived` directory relative to the directory containing the project that defines them. > [!TIP] GITIGNORE THE DERIVED DIRECTORY > We recommend adding the `Derived` directory to the `.gitignore` file of your project. ## Bundle accessors {#bundle-accessors} Tuist synthesizes an interface to access the bundle that contains the target resources. ### Swift {#swift} The target will contain an extension of the `Bundle` type that exposes the bundle: ```swift let bundle = Bundle.module ``` ### Objective-C {#objectivec} In Objective-C, you'll get an interface `{Target}Resources` to access the bundle: ```objc NSBundle *bundle = [MyFeatureResources bundle]; ``` > [!TIP] SUPPORTING RESOURCES IN LIBRARIES THROUGH BUNDLES > If a target product, for example a library, doesn't support resources, Tuist will include the resources in a target of product type `bundle` ensuring that it ends up in the final product and that the interface points to the right bundle. ## Resource accessors {#resource-accessors} Resources are identified by their name and extension using strings. This is not ideal because it's not caught at compile time and might lead to crashes in release. To prevent that, Tuist integrates [SwiftGen](https://github.com/SwiftGen/SwiftGen) into the project generation process to synthesize an interface to access the resources. Thanks to that, you can confidently access the resources leveraging the compiler to catch any issues. Tuist includes [templates](https://github.com/tuist/tuist/tree/main/Sources/TuistGenerator/Templates) to synthesize accessors for the following resource types by default: | Resource type | Synthesized file | | ----------------- | ------------------------ | | Images and colors | `Assets+{Target}.swift` | | Strings | `Strings+{Target}.swift` | | Plists | `{NameOfPlist}.swift` | | Fonts | `Fonts+{Target}.swift` | | Files | `Files+{Target}.swift` | > Note: You can disable the synthesizing of resource accessors on a per-project basis by passing the `disableSynthesizedResourceAccessors` option to the project options. #### Custom templates {#custom-templates} If you want to provide your own templates to synthesize accessors to other resource types, which must be supported by [SwiftGen](https://github.com/SwiftGen/SwiftGen), you can create them at `Tuist/ResourceSynthesizers/{name}.stencil`, where the name is the camel-case version of the resource. | Resource | Template name | | ---------------- | -------------------------- | | strings | `Strings.stencil` | | assets | `Assets.stencil` | | plists | `Plists.stencil` | | fonts | `Fonts.stencil` | | coreData | `CoreData.stencil` | | interfaceBuilder | `InterfaceBuilder.stencil` | | json | `JSON.stencil` | | yaml | `YAML.stencil` | | files | `Files.stencil` | If you want to configure the list of resource types to synthesize accessors for, you can use the `Project.resourceSynthesizers` property passing the list of resource synthesizers you want to use: ```swift let project = Project(resourceSynthesizers: [.string(), .fonts()]) ``` > [!NOTE] REFERENCE > You can check out [this fixture](https://github.com/tuist/tuist/tree/main/fixtures/ios_app_with_templates) to see an example of how to use custom templates to synthesize accessors to resources. --- URL: "/es/guides/develop/projects/plugins" LLMS_URL: "/es/guides/develop/projects/plugins.md" title: "Plugins" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to create and use plugins in Tuist to extend its functionality." --- # Plugins {#plugins} Plugins are a tool to share and reuse Tuist artifacts across multiple projects. The following artifacts are supported: - Project description helpers across multiple projects. - Templates across multiple projects. - Tasks across multiple projects. - Resource accessor template across multiple projects Note that plugins are designed to be a simple way to extend Tuist's functionality. Therefore there are **some limitations to consider**: - A plugin cannot depend on another plugin. - A plugin cannot depend on third-party Swift packages - A plugin cannot use project description helpers from the project that uses the plugin. If you need more flexibility, consider suggesting a feature for the tool or building your own solution upon Tuist's generation framework, [`TuistGenerator`](https://github.com/tuist/tuist/tree/main/Sources/TuistGenerator). ## Plugin types {#plugin-types} ### Project description helper plugin {#project-description-helper-plugin} A project description helper plugin is represented by a directory containing a `Plugin.swift` manifest file that declares the plugin's name and a `ProjectDescriptionHelpers` directory containing the helper Swift files. ::: code-group ```bash [Plugin.swift] import ProjectDescription let plugin = Plugin(name: "MyPlugin") ``` ```bash [Directory structure] . ├── ... ├── Plugin.swift ├── ProjectDescriptionHelpers └── ... ``` ::: ### Resource accessor templates plugin {#resource-accessor-templates-plugin} If you need to share synthesized resource accessors you can use this type of plugin. The plugin is represented by a directory containing a `Plugin.swift` manifest file that declares the plugin's name and a `ResourceSynthesizers` directory containing the resource accessor template files. ::: code-group ```bash [Plugin.swift] import ProjectDescription let plugin = Plugin(name: "MyPlugin") ``` ```bash [Directory structure] . ├── ... ├── Plugin.swift ├── ResourceSynthesizers ├───── Strings.stencil ├───── Plists.stencil ├───── CustomTemplate.stencil └── ... ``` ::: The name of the template is the [camel case](https://en.wikipedia.org/wiki/Camel_case) version of the resource type: | Resource type | Template file name | | ----------------- | ---------------------------------------- | | Strings | Strings.stencil | | Assets | Assets.stencil | | Property Lists | Plists.stencil | | Fonts | Fonts.stencil | | Core Data | CoreData.stencil | | Interface Builder | InterfaceBuilder.stencil | | JSON | JSON.stencil | | YAML | YAML.stencil | When defining the resource synthesizers in the project, you can specify the plugin name to use the templates from the plugin: ```swift let project = Project(resourceSynthesizers: [.strings(plugin: "MyPlugin")]) ``` ### Task plugin {#task-plugin-badge-typewarning-textdeprecated-} > [!WARNING] DEPRECATED > Task plugins are deprecated. Check out [this blog post](https://tuist.dev/blog/2025/04/15/automation-in-swift-projects) if you are looking for an automation solution for your project. Tasks are `$PATH`-exposed executables that are invocable through the `tuist` command if they follow the naming convention `tuist-`. In earlier versions, Tuist provided some weak conventions and tools under `tuist plugin` to `build`, `run`, `test` and `archive` tasks represented by executables in Swift Packages, but we have deprecated this feature since it increases the maintenance burden and complexity of the tool. If you were using Tuist for distributing tasks, we recommend building your - You can continue using the `ProjectAutomation.xcframework` distributed with every Tuist release to have access to the project graph from your logic with `let graph = try Tuist.graph()`. The command uses sytem process to run the `tuist` command, and return the in-memory representation of the project graph. - To distribute tasks, we recommend including the a fat binary that supports the `arm64` and `x86_64` in GitHub releases, and using [Mise](https://mise.jdx.dev) as an installation tool. To instruct Mise on how to install your tool, you'll need a plugin repository. You can use [Tuist's](https://github.com/asdf-community/asdf-tuist) as a reference. - If you name your tool `tuist-{xxx}` and users can install it by running `mise install`, they can run it either invoking it directly, or through `tuist xxx`. > [!NOTE] THE FUTURE OF PROJECTAUTOMATION > We plan to consolidate the models of `ProjectAutomation` and `XcodeGraph` into a single backward-compatible framework that exposes the entirity of the project graph to the user. Moreover, we'll extract the generation logic into a new layer, `XcodeGraph` that you can also use from your own CLI. Think of it as building your own Tuist. ## Using plugins {#using-plugins} To use a plugin, you'll have to add it to your project's `Tuist.swift` manifest file: ```swift import ProjectDescription let tuist = Tuist( project: .tuist(plugins: [ .local(path: "/Plugins/MyPlugin") ]) ) ``` If you want to reuse a plugin across projects that live in different repositories, you can push your plugin to a Git repository and reference it in the `Tuist.swift` file: ```swift import ProjectDescription let tuist = Tuist( project: .tuist(plugins: [ .git(url: "https://url/to/plugin.git", tag: "1.0.0"), .git(url: "https://url/to/plugin.git", sha: "e34c5ba") ]) ) ``` After adding the plugins, `tuist install` will fetch the plugins in a global cache directory. > [!NOTE] NO VERSION RESOLUTION > As you might have noted, we don't provide version resolution for plugins. We recommend using Git tags or SHAs to ensure reproducibility. > [!TIP] PROJECT DESCRIPTION HELPERS PLUGINS > When using a project description helpers plugin, the name of the module that contains the helpers is the name of the plugin > > ```swift > import ProjectDescription > import MyTuistPlugin > let project = Project.app(name: "MyCoolApp", platform: .iOS) > ``` --- URL: "/es/guides/develop/projects/manifests" LLMS_URL: "/es/guides/develop/projects/manifests.md" title: "Manifests" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the manifest files that Tuist uses to define projects and workspaces and configure the generation process." --- # Manifests {#manifests} Tuist defaults to Swift files as the primary way to define projects and workspaces and configure the generation process. These files are referred to as **manifest files** throughout the documentation. The decision of using Swift was inspired by the [Swift Package Manager](https://www.swift.org/documentation/package-manager/), which also uses Swift files to define packages. Thanks to the usage of Swift, we can leverage the compiler to validate the correctness of the content and reuse code across different manifest files, and Xcode to provide a first-class editing experience thanks to the syntax highlighting, auto-completion, and validation. > [!NOTE] CACHING > Since manifest files are Swift files that need to be compiled, Tuist caches the compilation results to speed up the parsing process. Therefore, you'll notice that the first time you run Tuist, it might take a bit longer to generate the project. Subsequent runs will be faster. ## Project.swift {#projectswift} The `Project.swift` manifest declares an Xcode project. The project gets generated in the same directory where the manifest file is located with the name indicated in the `name` property. ```swift // Project.swift let project = Project( name: "App", targets: [ // .... ] ) ``` > [!WARNING] ROOT VARIABLES > The only variable that should be at the root of the manifest is `let project = Project(...)`. If you need to reuse code across various parts of the manifest, you can use Swift functions. ## Workspace.swift {#workspaceswift} By default, Tuist generates an [Xcode Workspace](https://developer.apple.com/documentation/xcode/projects-and-workspaces) containing the project being generated and the projects of its dependencies. If for any reason you'd like to customize the workspace to add additional projects or include files and groups, you can do so by defining a `Workspace.swift` manifest. ```swift // Workspace.swift import ProjectDescription let workspace = Workspace( name: "App-Workspace", projects: [ "./App", // Path to directory containing the Project.swift file ] ) ``` > [!NOTE] > Tuist will resolve the dependency graph and include the projects of the dependencies in the workspace. You don't need to include them manually. This is necessary for the build system to resolve the dependencies correctly. ### Multi or mono-project {#multi-or-monoproject} A question that often comes up is whether to use a single project or multiple projects in a workspace. In a world without Tuist where a mono-project setup would lead to frequent Git conflicts the usage of workspaces is encouraged. However, since we don't recommend including the Tuist-generated Xcode projects in the Git repository, Git conflicts are not an issue. Therefore, the decision of using a single project or multiple projects in a workspace is up to you. In the Tuist project we lean on mono-projects because the cold generation time is faster (fewer manifest files to compile) and we leverage project description helpers as a unit of encapsulation. However, you might want to use Xcode projects as a unit of encapsulation to represent different domains of your application, which aligns more closely with the Xcode's recommended project structure. ## Tuist.swift {#tuistswift} Tuist provides sensible defaults to simplify project configuration. However, you can customize the configuration by defining a `Tuist.swift` at the root of the project, which is used by Tuist to determine the root of the project. ```swift import ProjectDescription let tuist = Tuist( project: .tuist(generationOptions: .options(enforceExplicitDependencies: true)) ) ``` --- URL: "/es/guides/develop/projects/inspect/implicit-dependencies" LLMS_URL: "/es/guides/develop/projects/inspect/implicit-dependencies.md" title: "Implicit imports" titleTemplate: ":title · Inspect · Projects · Develop · Guides · Tuist" description: "Learn how to use Tuist to find implicit imports." --- # Implicit imports {#implicit-imports} To alleviate the complexity of maintaining an Xcode project graph with raw Xcode project, Apple designed the build system in a way that allows dependencies to be implicitly defined. This means that a product, for example an app, can depend on a framework, even without declaring the dependency explicitly. At a small scale, this is is fine, but as the project graph grows in complexity, the implicitness might manifest as unreliable incremental builds or editor-based features such as previews or code completion. The problem is that you can't prevent implicit dependencies from happening. Any developer can add an `import` statement to their Swift code, and the implicit dependency will be created. This is where Tuist comes in. Tuist provides a command to inspect the implicit dependencies by statically analyzing the code in your project. The following command will output the implicit dependencies of your project: ```bash tuist inspect implicit-imports ``` If the command detects any implicit imports, it exits with an exit code other than zero. > [!TIP] VALIDATE IN CI > We strongly recommend to run this command as part of your continuous integration command every time new code is pushed upstream. > [!IMPORTANT] NOT ALL IMPLICIT CASES ARE DETECTED > Since Tuist relies on static code analysis to detect implicit dependencies, it might not catch all cases. For example, Tuist is unable to understand conditional imports through compiler directives in code. --- URL: "/es/guides/develop/projects/hashing" LLMS_URL: "/es/guides/develop/projects/hashing.md" title: "Hashing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about Tuist's hashing logic upon which features like binary caching and selective testing are built." --- # Hashing {#hashing} Features like caching or smart test execution require a way to determine whether a target has changed. Tuist calculates a hash for each target in the dependency graph to determine if a target has changed. The hash is calculated based on the following attributes: - The target's attributes (e.g., name, platform, product, etc.) - The target's files - The hash of the target's dependencies ### Cache attributes {#cache-attributes} Additionally, when calculating the hash for caching, we also hash the following attributes. #### Swift version {#swift-version} We hash the Swift version obtained from running the command `/usr/bin/xcrun swift --version` to prevent compilation errors due to Swift version mismatches between the targets and the binaries. > [!NOTE] MODULE STABILITY > Previous versions of binary caching relied on the `BUILD_LIBRARY_FOR_DISTRIBUTION` build setting to enable [module stability](https://www.swift.org/blog/library-evolution#enabling-library-evolution-support) and enable using binaries with any compiler version. However, it caused compilation issues in projects with targets that don't support module stability. Generated binaries are bound to the Swift version used to compile them, and the Swift version must match the one used to compile the project. #### Configuration {#configuration} The idea behind the flag `-configuration` was to ensure debug binaries were not used in release builds and viceversa. However, we are still missing a mechanism to remove the other configurations from the projects to prevent them from being used. ## Debugging {#debugging} If you notice non-deterministic behaviors when using the caching across environments or invocations, it might be related to differences across the environments or a bug in the hashing logic. We recommend following these steps to debug the issue: 1. Ensure the same [configuration](#configuration) and [Swift version](#swift-version) is used across environments. 2. Check if there are differences between the Xcode projects generated by two consecutive invocations of `tuist generate` or across environments. You can use the `diff` command to compare the projects. The generated projects might include **absolute paths** causing the hashing logic to be non-deterministic. > [!NOTE] BETTER DEBUGGING EXPERIENCE PLANNED > Improving our debugging experience is in our roadmap. The print-hashes command, which lacks the context to understand the differences, will be replaced by a more user-friendly command that uses a tree-like structure to show the differences between the hashes. --- URL: "/es/guides/develop/projects/editing" LLMS_URL: "/es/guides/develop/projects/editing.md" title: "Editing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to use Tuist's edit workflow to declare your project leveraging Xcode's build system and editor capabilities." --- # Editing {#editing} Unlike traditional Xcode projects or Swift Packages, where changes are done through Xcode's UI, Tuist-managed projects are defined in Swift code contained in **manifest files**. If you're familiar with Swift Packages and the `Package.swift` file, the approach is very similar. You could edit these files using any text editor, but we recommend to use Tuist-provided workflow for that, `tuist edit`. The workflow creates an Xcode project that contains all manifest files and allows you to edit and compile them. Thanks to using Xcode, you get all the benefits of **code completion, syntax highlighting, and error checking**. ## Edit the project {#edit-the-project} To edit your project, you can run the following command in a Tuist project directory or a sub-directory: ```bash tuist edit ``` The command creates an Xcode project in a global directory and opens it in Xcode. The project includes a `Manifests` directory that you can build to ensure all your manifests are valid. > [!INFO] GLOB-RESOLVED MANIFESTS > `tuist edit` resolves the manifests to be included by using the glob `**/{Manifest}.swift` from the project's root directory (the one containing the `Tuist.swift` file). Make sure there's a valid `Tuist.swift` at the root of the project. ## Edit and generate workflow {#edit-and-generate-workflow} As you might have noticed, the editing can't be done from the generated Xcode project. That's by design to prevent the generated project from having a dependency on Tuist, ensuring you can move from Tuist in the future with little effort. When iterating on a project, we recommend running `tuist edit` from a terminal session to get an Xcode project to edit the project, and use another terminal session to run `tuist generate`. --- URL: "/es/guides/develop/projects/dynamic-configuration" LLMS_URL: "/es/guides/develop/projects/dynamic-configuration.md" title: "Dynamic configuration" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how how to use environment variables to dynamically configure your project." --- # Dynamic configuration {#dynamic-configuration} There are certain scenarios where you might need to dynamically configure your project at generation time. For example, you might want to change the name of the app, the bundle identifier, or the deployment target based on the environment where the project is being generated. Tuist supports that via environment variables, which can be accessed from the manifest files. ## Configuration through environment variables {#configuration-through-environment-variables} Tuist allows passing configuration through environment variables that can be accessed from the manifest files. For example: ```bash TUIST_APP_NAME=MyApp tuist generate ``` If you want to pass multiple environment variables just separate them with a space. For example: ```bash TUIST_APP_NAME=MyApp TUIST_APP_LOCALE=pl tuist generate ``` ## Reading the environment variables from manifests {#reading-the-environment-variables-from-manifests} Variables can be accessed using the `Environment` type. Any variables following the convention `TUIST_XXX` defined in the environment or passed to Tuist when running commands will be accessible using the `Environment` type. The following example shows how we access the `TUIST_APP_NAME` variable: ```swift func appName() -> String { if case let .string(environmentAppName) = Environment.appName { return environmentAppName } else { return "MyApp" } } ``` Accessing variables returns an instance of type `Environment.Value?` which can take any of the following values: | Case | Description | | ----------------- | ----------------------------------------------------------- | | `.string(String)` | Used when the variable represents a string. | You can also retrieve the string or boolean `Environment` variable using either of the helper methods defined below, these methods require a default value to be passed to ensure the user gets consistent results each time. This avoids the need to define the function appName() defined above. ::: code-group ```swift [String] Environment.appName.getString(default: "TuistServer") ``` ```swift [Boolean] Environment.isCI.getBoolean(default: false) ``` ::: --- URL: "/es/guides/develop/projects/directory-structure" LLMS_URL: "/es/guides/develop/projects/directory-structure.md" title: "Directory structure" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the structure of Tuist projects and how to organize them." --- # Directory structure {#directory-structure} Although Tuist projects are commonly used to supersede Xcode projects, they are not limited to this use case. Tuist projects are also used to generate other types of projects, such as SPM packages, templates, plugins, and tasks. This document describes the structure of Tuist projects and how to organize them. In later sections, we'll cover how to define templates, plugins, and tasks. ## Standard Tuist projects {#standard-tuist-projects} Tuist projects are **the most common type of project generated by Tuist.** They are used to build apps, frameworks, and libraries among others. Unlike Xcode projects, Tuist projects are defined in Swift, which makes them more flexible and easier to maintain. Tuist projects are also more declarative, which makes them easier to understand and reason about. The following structure shows a typical Tuist project that generates an Xcode project: ```bash Tuist.swift Tuist/ Package.swift ProjectDescriptionHelpers/ Projects/ App/ Project.swift Feature/ Project.swift Workspace.swift ``` - **Tuist directory:** This directory has two purposes. First, it signals **where the root of the project is**. Second, it's the container for the following files: This allows constructing paths relative to the root of the project, and also running Tuist commands from any directory within the project. Second, it's the container for the following files: This allows constructing paths relative to the root of the project, and also running Tuist commands from any directory within the project. - **ProjectDescriptionHelpers:** This directory contains Swift code that's shared across all the manifest files. Manifest files can `import ProjectDescriptionHelpers` to use the code defined in this directory. Sharing code is useful to avoid duplications and ensure consistency across the projects. - **Package.swift:** This file contains Swift Package dependencies for Tuist to integrate them using Xcode projects and targets (like [CocoaPods](https://cococapods)) that are configurable and optimizable. Learn more here. - **Root directory**: The root directory of your project that also contains the `Tuist` directory. - Tuist.swift: This file contains configuration for Tuist that's shared across all the projects, workspaces, and environments. For example, it can be used to disable automatic generation of schemes, or to define the deployment target of the projects. - Workspace.swift: This manifest represents an Xcode workspace. It's used to group other projects and can also add additional files and schemes. - Project.swift: This manifest represents an Xcode project. It's used to define the targets that are part of the project, and their dependencies. When interacting with the above project, commands expect to find either a `Workspace.swift` or a `Project.swift` file in the working directory or the directory indicated via the `--path` flag. The manifest should be in a directory or subdirectory of a directory containing a `Tuist` directory, which represents the root of the project. > [!TIP] > Xcode workspaces allowed splitting projects into multiple Xcode projects to reduce the likelihood of merge conflicts. If that's what you were using workspaces for, you don't need them in Tuist. Tuist auto-generates a workspace containing a project and its dependencies' projects. ## Swift Package {#swift-package-badge-typewarning-textbeta-} Tuist also supports SPM package projects. If you are working on an SPM package, you shouldn't need to update anything. Tuist automatically picks up on your root `Package.swift` and all the features of Tuist work as if it was a `Project.swift` manifest. To get started, run `tuist install` and `tuist generate` in your SPM package. Your project should now have all the same schemes and files that you would see in the vanilla Xcode SPM integration. However, now you can also run `tuist cache` and have majority of your SPM dependencies and modules precompiled, making subsequent builds extremely fast. --- URL: "/es/guides/develop/projects/dependencies" LLMS_URL: "/es/guides/develop/projects/dependencies.md" title: "Dependencies" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to declare dependencies in your Tuist project." --- # Dependencies {#dependencies} When a project grows, it's common to split it into multiple targets to share code, define boundaries, and improve build times. Multiple targets means defining dependencies between them forming a **dependency graph**, which might include external dependencies as well. ## XcodeProj-codified graphs {#xcodeprojcodified-graphs} Due to Xcode and XcodeProj's design, the maintenance of a dependency graph can be a tedious and error-prone task. Here are some examples of the problems that you might encounter: - Because Xcode's build system outputs all the project's products into the same directory in derived data, targets might be able to import products that they shouldn't. Compilations might fail on CI, where clean builds are more common, or later on when a different configuration is used. - The transitive dynamic dependencies of a target need to be copied into any of the directories that are part of the `LD_RUNPATH_SEARCH_PATHS` build setting. If they aren't, the target won't be able to find them at runtime. This is easy to think about and set up when the graph is small, but it becomes a problem as the graph grows. - When a target links a static [XCFramework](https://developer.apple.com/documentation/xcode/creating-a-multi-platform-binary-framework-bundle), the target needs an additional build phase for Xcode to process the bundle and extract the right binary for the current platform and architecture. This build phase is not added automatically, and it's easy to forget to add it. The above are just a few examples, but there are many more that we've encountered over the years. Imagine if you required a team of engineers to maintain a dependency graph and ensure its validity. Or even worse, that the intricacies were resolved at build-time by a closed-source build system that you can't control or customize. Sounds familiar? This is the approach that Apple took with Xcode and XcodeProj and that the Swift Package Manager has inherited. We strongly believe that the dependency graph should be **explicit** and **static** because only then can it be **validated** and **optimized**. With Tuist, you focus on describing what depends on what, and we take care of the rest. The intricacies and implementation details are abstracted away from you. In the following sections you'll learn how to declare dependencies in your project. > [!TIP] GRAPH VALIDATION > Tuist validates the graph when generating the project to ensure that there are no cycles and that all the dependencies are valid. Thanks to this, any team can take part in evolving the dependency graph without worrying about breaking it. ## Local dependencies {#local-dependencies} Targets can depend on other targets in the same and different projects, and on binaries. When instantiating a `Target`, you can pass the `dependencies` argument with any of the following options: - `Target`: Declares a dependency with a target within the same project. - `Project`: Declares a dependency with a target in a different project. - `Framework`: Declares a dependency with a binary framework. - `Library`: Declares a dependency with a binary library. - `XCFramework`: Declares a dependency with a binary XCFramework. - `SDK`: Declares a dependency with a system SDK. - `XCTest`: Declares a dependency with XCTest. > [!NOTE] DEPENDENCY CONDITIONS > Every dependency type accepts a `condition` option to conditionally link the dependency based on the platform. By default, it links the dependency for all platforms the target supports. ## External dependencies {#external-dependencies} Tuist also allows you to declare external dependencies in your project. ### Swift Packages {#swift-packages} Swift Packages are our recommended way of declaring dependencies in your project. You can integrate them using Xcode's default integration mechanism or using Tuist's XcodeProj-based integration. #### Tuist's XcodeProj-based integration {#tuists-xcodeprojbased-integration} Xcode's default integration while being the most convenient one, lacks flexibility and control that's required for medium and large projects. To overcome this, Tuist offers an XcodeProj-based integration that allows you to integrate Swift Packages in your project using XcodeProj's targets. Thanks to that, we can not only give you more control over the integration but also make it compatible with workflows like caching and smart test runs. XcodeProj's integration is more likely to take more time to support new Swift Package features or handle more package configurations. However, the mapping logic between Swift Packages and XcodeProj targets is open-source and can be contributed to by the community. This is contrary to Xcode's default integration, which is closed-source and maintained by Apple. To add external dependencies, you'll have to create a `Package.swift` either under `Tuist/` or at the root of the project. ::: code-group ```swift [Tuist/Package.swift] // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription import ProjectDescriptionHelpers let packageSettings = PackageSettings( productTypes: [ "Alamofire": .framework, // default is .staticFramework ] ) #endif let package = Package( name: "PackageName", dependencies: [ .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), ], targets: [ .binaryTarget( name: "Sentry", url: "https://github.com/getsentry/sentry-cocoa/releases/download/8.40.1/Sentry.xcframework.zip", checksum: "db928e6fdc30de1aa97200576d86d467880df710cf5eeb76af23997968d7b2c7" ), ] ) ``` ::: > [!TIP] PACKAGE SETTINGS > The `PackageSettings` instance wrapped in a compiler directive allows you to configure how packages are integrated. For example, in the example above it's used to override the default product type used for packages. By default, you shouldn't need it. The `Package.swift` file is just an interface to declare external dependencies, nothing else. That's why you don't define any targets or products in the package. Once you have the dependencies defined, you can run the following command to resolve and pull the dependencies into the `Tuist/Dependencies` directory: ```bash tuist install # Resolving and fetching dependencies. {#resolving-and-fetching-dependencies} # Installing Swift Package Manager dependencies. {#installing-swift-package-manager-dependencies} ``` As you might have noticed, we take an approach similar to [CocoaPods](https://cocoapods.org)', where the resolution of dependencies is its own command. This gives control to the users over when they'd like dependencies to be resolved and updated, and allows opening the Xcode in project and have it ready to compile. This is an area where we believe the developer experience provided by Apple's integration with the Swift Package Manager degrates over time as the project grows. From your project targets you can then reference those dependencies using the `TargetDependency.external` dependency type: ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "App", organizationName: "tuist.io", targets: [ .target( name: "App", destinations: [.iPhone], product: .app, bundleId: "io.tuist.app", deploymentTargets: .iOS("13.0"), infoPlist: .default, sources: ["Targets/App/Sources/**"], dependencies: [ .external(name: "Alamofire"), // [!code ++] ] ), ] ) ``` ::: > [!NOTE] NO SCHEMES GENERATED FOR EXTERNAL PACKAGES > The **schemes** are not automatically created for Swift Package projects to keep the schemes list clean. You can create them via Xcode's UI. #### Xcode's default integration {#xcodes-default-integration} If you want to use Xcode's default integration mechanism, you can pass the list `packages` when instantiating a project: ```swift let project = Project(name: "MyProject", packages: [ .remote(url: "https://github.com/krzyzanowskim/CryptoSwift", requirement: .exact("1.8.0")) ]) ``` And then reference them from your targets: ```swift let target = .target(name: "MyTarget", dependencies: [ .package(product: "CryptoSwift", type: .runtime) ]) ``` For Swift Macros and Build Tool Plugins, you'll need to use the types `.macro` and `.plugin` respectively. > [!WARNING] SPM Build Tool Plugins > SPM build tool plugins must be declared using [Xcode's default integration](#xcode-s-default-integration) mechanism, even when using Tuist's [XcodeProj-based integration](#tuist-s-xcodeproj-based-integration) for your project dependencies. A practical application of an SPM build tool plugin is performing code linting during Xcode's "Run Build Tool Plug-ins" build phase. In a package manifest this is defined as follows: ```swift // swift-tools-version: 5.9 import PackageDescription let package = Package( name: "Framework", products: [ .library(name: "Framework", targets: ["Framework"]), ], dependencies: [ .package(url: "https://github.com/SimplyDanny/SwiftLintPlugins", .upToNextMajor(from: "0.56.1")), ], targets: [ .target( name: "Framework", plugins: [ .plugin(name: "SwiftLint", package: "SwiftLintPlugin"), ] ), ] ) ``` To generate an Xcode project with the build tool plugin intact, you must declare the package in the project manifest's `packages` array, and then include a package with type `.plugin` in a target's dependencies. ```swift import ProjectDescription let project = Project( name: "Framework", packages: [ .remote(url: "https://github.com/SimplyDanny/SwiftLintPlugins", requirement: .upToNextMajor(from: "0.56.1")), ], targets: [ .target( name: "Framework", dependencies: [ .package(product: "SwiftLintBuildToolPlugin", type: .plugin), ] ), ] ) ``` ### Carthage {#carthage} Since [Carthage](https://github.com/carthage/carthage) outputs `frameworks` or `xcframeworks`, you can run `carthage update` to output the dependencies in the `Carthage/Build` directory and then use the `.framework` or `.xcframework` target dependency type to declare the dependency in your target. You can wrap this in a script that you can run before generating the project. ```bash #!/usr/bin/env bash carthage update tuist generate ``` > [!WARNING] BUILD AND TEST > If you build and test your project through `tuist build` and `tuist test`, you will similarly need to ensure that the Carthage-resolved dependencies are present by running the `carthage update` command before `tuist build` or `tuist test` are run. ### CocoaPods {#cocoapods} [CocoaPods](https://cocoapods.org) expects an Xcode project to integrate the dependencies. You can use Tuist to generate the project, and then run `pod install` to integrate the dependencies by creating a workspace that contains your project and the Pods dependencies. You can wrap this in a script that you can run before generating the project. ```bash #!/usr/bin/env bash tuist generate pod install ``` > [!WARNING] > CocoaPods dependencies are not compatible with workflows like `build` or `test` that run `xcodebuild` right after generating the project. They are also incompatible with binary caching and selective testing since the fingerprinting logic doesn't account for the Pods dependencies. ## Static or dynamic {#static-or-dynamic} Frameworks and libraries can be linked either statically or dynamically, **a choice that has significant implications for aspects like app size and boot time**. Despite its importance, this decision is often made without much consideration. The **general rule of thumb** is that you want as many things as possible to be statically linked in release builds to achieve fast boot times, and as many things as possible to be dynamically linked in debug builds to achieve fast iteration times. The challenge with changing between static and dynamic linking in a project graph is that is not trivial in Xcode because a change has cascading effect on the entire graph (e.g. libraries can't contain resources, static frameworks don't need to be embedded). Apple tried to solve the problem with compile time solutions like Swift Package Manager's automatic decision between static and dynamic linking, or [Mergeable Libraries](https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries). However, this adds new dynamic variables to the compilation graph, adding new sources of non-determinism, and potentially causing some features like Swift Previews that rely on the compilation graph to become unreliable. Luckily, Tuist conceptually compresses the complexity associated with changing between static and dynamic and synthesizes bundle accessors that are standard across linking types. In combination with dynamic configurations via environment variables, you can pass the linking type at invocation time, and use the value in your manifests to set the product type of your targets. ```swift // Use the value returned by this function to set the product type of your targets. func productType() -> Product { if case let .string(linking) = Environment.linking { return linking == "static" ? .staticFramework : .framework } else { return .framework } } ``` Note that Tuist does not default to convenience through implicit configuration due to its costs. What this means is that we rely on you setting the linking type and any additional build settings that are sometimes required, like the [`-ObjC` linker flag](https://github.com/pointfreeco/swift-composable-architecture/discussions/1657#discussioncomment-4119184), to ensure the resulting binaries are correct. Therefore, the stance that we take is providing you with the resources, usually in the shape of documentation, to make the right decisions. > [!TIP] EXAMPLE: COMPOSABLE ARCHITECTURE > A Swift Package that many projects integrate is [Composable Architecture](https://github.com/pointfreeco/swift-composable-architecture). As described [here](https://github.com/pointfreeco/swift-composable-architecture/discussions/1657#discussioncomment-4119184) and the [troubleshooting section](#troubleshooting), you'll need to set the `OTHER_LDFLAGS` build setting to `$(inherited) -ObjC` when linking the packages statically, which is Tuist's default linking type. Alternatively, you can override the product type for the package to be dynamic. ### Scenarios {#scenarios} There are some scenarios where setting the linking entirely to static or dynamic is not feasible or a good idea. The following is a non-exhaustive list of scenarios where you might need to mix static and dynamic linking: - **Apps with extensions:** Since apps and their extensions need to share code, you might need to make those targets dynamic. Otherwise, you'll end up with the same code duplicated in both the app and the extension, causing the binary size to increase. - **Pre-compiled external dependencies:** Sometimes you are provided with pre-compiled binaries that are either static or dynamic. Static binaries can be wrapped in dynamic frameworks or libraries to be linked dynamically. When making changes to the graph, Tuist will analyze it and display a warning if it detects a "static side effect". This warning is meant to help you identify issues that might arise from linking a target statically that depends transitively on a static target through dynamic targets. These side effects often manifest as increased binary size or, in the worst cases, runtime crashes. ## Troubleshooting {#troubleshooting} ### Objective-C Dependencies {#objectivec-dependencies} When integrating Objective-C dependencies, the inclusion of certain flags on the consuming target may be necessary to avoid runtime crashes as detailed in [Apple Technical Q&A QA1490](https://developer.apple.com/library/archive/qa/qa1490/_index.html). Since the build system and Tuist have no way of inferring whether the flag is necessary or not, and since the flag comes with potentially undesirable side effects, Tuist will not automatically apply any of these flags, and because Swift Package Manager considers `-ObjC` to be included via an `.unsafeFlag` most packages cannot include it as part of their default linking settings when required. Consumers of Objective-C dependencies (or internal Objective-C targets) should apply `-ObjC` or `-force_load` flags when required by setting `OTHER_LDFLAGS` on consuming targets. ### Firebase & Other Google Libraries {#firebase-other-google-libraries} Google's open source libraries — while powerful — can be difficult to integrate within Tuist as they often use non-standard architecture and techniques in how they are built. Here are a few tips that may be necessary to follow to integrate Firebase and Google's other Apple-platform libraries: #### Ensure `-ObjC` is added to `OTHER_LDFLAGS` {#ensure-objc-is-added-to-other_ldflags} Many of Google's libraries are written in Objective-C. Because of this, any consuming target will need to include the `-ObjC` tag in its `OTHER_LDFLAGS` build setting. This can either be set in an `.xcconfig` file or manually specified in the target's settings within your Tuist manifests. An example: ```swift Target.target( ... settings: .settings( base: ["OTHER_LDFLAGS": "$(inherited) -ObjC"] ) ... ) ``` Refer to the [Objective-C Dependencies](#objective-c-dependencies) section above for more details. #### Set the product type for `FBLPromises` to dynamic framework {#set-the-product-type-for-fblpromises-to-dynamic-framework} Certain Google libraries depend on `FBLPromises`, another of Google's libraries. You may encounter a crash that mentions `FBLPromises`, looking something like this: ``` NSInvalidArgumentException. Reason: -[FBLPromise HTTPBody]: unrecognized selector sent to instance 0x600000cb2640. ``` Explicitly setting the product type of `FBLPromises` to `.framework` in your `Package.swift` file should fix the issue: ```swift [Tuist/Package.swift] // swift-tools-version: 5.10 import PackageDescription #if TUIST import ProjectDescription import ProjectDescriptionHelpers let packageSettings = PackageSettings( productTypes: [ "FPLPromises": .framework, ] ) #endif let package = Package( ... ``` ### Transitive static dependencies leaking through `.swiftmodule` {#transitive-static-dependencies-leaking-through-swiftmodule} When a dynamic framework or library depends on static ones through `import StaticSwiftModule`, the symbols are included in the `.swiftmodule` of the dynamic framework or library, potentially [causing the compilation to fail](https://forums.swift.org/t/compiling-a-dynamic-framework-with-a-statically-linked-library-creates-dependencies-in-swiftmodule-file/22708/1). To prevent that, you'll have to import the static dependency using [`@_implementationOnly`](https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_implementationonly): ```swift @_implementationOnly import StaticModule ``` --- URL: "/es/guides/develop/projects/cost-of-convenience" LLMS_URL: "/es/guides/develop/projects/cost-of-convenience.md" title: "The cost of convenience" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the cost of convenience in Xcode and how Tuist helps you prevent the issues that come with it." --- # The cost of convenience {#the-cost-of-convenience} Designing a code editor that the spectrum **from small to large-scale projects can use** is a challenging task. Many tools approach the problem by layering their solution and providing extensibility. The bottom-most layer is very low-level and close to the underlying build system, and the top-most layer is a high-level abstraction that's convenient to use but less flexible. By doing so, they make the simple things easy, and everything else possible. However, **[Apple](https://www.apple.com) decided to take a different approach with Xcode**. The reason is unknown, but it's likely that optimizing for the challenges of large-scale projects has never been their goal. They overinvested in convenience for small projects, provided little flexibility, and strongly coupled the tools with the underlying build system. To achieve the convenience, they provide sensible defaults, which you can easily replace, and added a lot of implicit build-time-resolved behaviors that are the culprit of many issues at scale. ## Explicitness and scale {#explicitness-and-scale} When working at scale, **explicitness is key**. It allows the build system to analyze and understand the project structure and dependencies ahead of time, and perform optimizations that would be impossible otherwise. The same explicitness is also key in ensuring that editor features such as [SwiftUI previews](https://developer.apple.com/documentation/swiftui/previews-in-xcode) or [Swift Macros](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros/) work reliably and predictably. Because Xcode and Xcode projects embraced implicitness as a valid design choice to achieve convenience, a principle that the Swift Package Manager has inherited, the difficulties of using Xcode are also present in the Swift Package Manager. > [!INFO] THE ROLE OF TUIST > We could summarize Tuist's role as a tool that prevents implicitly-defined projects and leverages explicitness to provide a better developer experience (e.g. validations, optimizations). Tools like [Bazel](https://bazel.build) take it further by bringing it down to the build system level. This is an issue that's barely discussed in the community, but it's a significant one. While working on Tuist, we've noticed many organizations and developers thinking that the current challenges they face will be addressed by the [Swift Package Manager](https://www.swift.org/documentation/package-manager/), but what they don't realize is that because it's building on the same principles, even though it mitigates the so well-known Git conflicts, they degrade the developer experience in other areas and continue to make the projects non-optimizable. In the following sections, we'll discuss some real examples of how implicitness affects the developer experience and the project's health. The list is not exhaustive, but it should give you a good idea of the challenges that you might face when working with Xcode projects or Swift Packages. ## Convenience getting in your way {#convenience-getting-in-your-way} ### Shared built products directory {#shared-built-products-directory} Xcode uses a directory inside the derived data directory for each product. Inside it, it stores the build artifacts, such as the compiled binaries, the dSYM files, and the logs. Because all the products of a project go into the same directory, which is visible by default from other targets to link against, **you might end up with targets that implicitly depend on each other.** While this might not be a problem when having just a few targets, it might manifest as failing builds that are hard to debug when the project grows. The consequence of this design decision is that many projects acidentally compile with a graph that is not well-defined. > [!TIP] TUIST DETECTION OF IMPLICIT DEPENDENCIES > Tuist provides a command to detect implicit dependencies. You can use the command to validate in CI that all your dependencies are explicit. ### Find implicit dependencies in schemes {#find-implicit-dependencies-in-schemes} Defining and maintaining a dependency graph in Xcode gets harder as the project grows. It's hard because they are codified in the `.pbxproj` files as build phases and build settings, there are no tools to visualize and work with the graph, and the changes in the graph (e.g. adding a new dynamic precompiled framework), might require configuration changes upstream (e.g. adding a new build phase to copy the framework into the bundle). Apple decided at some point that instead of evolving the graph model into something more manageable, it'd make more sense to add an option to resolve implicit dependencies at build time. This is once again a questionable design choice because you might end up with slower build times or unpredictable builds. For example, a build might pass locally due to some state in derive data, which acts as a [singleton](https://en.wikipedia.org/wiki/Singleton_pattern), but then fail to compile on CI because the state is different. > [!TIP] > We recommend disabling this in your project schemes, and use like Tuist that eases the management of the dependency graph. ### SwiftUI Previews and static libraries/frameworks {#swiftui-previews-and-static-librariesframeworks} Some editor features like SwiftUI Previews or Swift Macros require the compilation of the dependency graph from the file that's being edited. This integration between the editor requires that the build system resolves any implicitness and output the right artifacts that are necessary for those features to work. As you can imagine, **the more implicit the graph is, the more challenging the task is for the build system**, and therefore it's not surprising that many of these features don't work reliably. We often hear from developers that they stopped using SwiftUI previews long time ago because they were too unreliable. Instead, they are using either example apps, or avoiding certaing things, like the usage of static libraries or script build phases, because they cause the feature to break. ### Mergeable libraries {#mergeable-libraries} Dynamic frameworks, while more flexible and easier to work with, have a negative impact in the launch time of apps. On the other side, static libraries are faster to launch, but impact the compilation time and are a bit harder to work with, specially in complex graph scenarios. _Wouldn't it be great if you could change between one or the other depending on the configuration?_ That's what Apple must have thought when they decided to work on mergeable libraries. But once again, they moved more build-time inference to the build-time. If reasoning about a dependency graph, imagine having to do so when the static or dynamic nature of the target will be resolved at build-time based on some build settings in some targets. Good luck making that work reliably while ensuring features like SwiftUI previews don't break. **Many users come to Tuist wanting to use mergeable libraries and our answer is always the same. You don't need to.** You can control the static or dynamic nature of your targets at generation-time leading to a project whose graph is known ahead of compilation. No variables need to be resolved at build-time. ```bash # The value of TUIST_DYNAMIC can be read from the project {#the-value-of-tuist_dynamic-can-be-read-from-the-project} # to set the product as static or dynamic based on the value. {#to-set-the-product-as-static-or-dynamic-based-on-the-value} TUIST_DYNAMIC=1 tuist generate ``` ## Explicit, explicit, and explicit {#explicit-explicit-and-explicit} If there's an important non-written principle that we recommend every developer or organization that wants their development with Xcode to scale, is that they should embrace explicitness. And if explicitness is hard to manage with raw Xcode projects, they should consider something else, either [Tuist](https://tuist.io) or [Bazel](https://bazel.build). **Only then reliability, predicability, and optimizations will be possible.** ## Future {#future} Whether Apple will do something to prevent all the above issues is unknown. Their continuous decisions embedded into Xcode and the Swift Package Manager don't suggest that they will. Once you allow implicit configuration as a valid state, **it's hard to move from there without introducing breaking changes.** Going back to first principles and rethinking the design of the tools might lead to breaking many Xcode projects that accidentally compiled for years. Imagine the community uproar if that happened. Apple finds itself in a bit of a chicken-and-egg problem. Convenience is what helps developers get started quickly and build more apps for their ecosystem. But their decisions to make the experience convenience at that scale, is making it hard for them to ensure some of the Xcode features work reliably. Because the future is unknown, we try to **be as close as possible to the industry standards and Xcode projects**. We prevent the above issues, and leverage the knowledge that we have to provide a better developer experience. Ideally we wouldn't have to resort to project generation for that, but the lack of extensibility of Xcode and the Swift Package Manager make it the only viable option. And it's also a safe option because they'll have to break the Xcode projects to break Tuist projects. Ideally, **the build system was more extensible**, but wouldn't it be a bad idea to have plugins/extensions that contract with a world of implicitness? It doesn't seem like a good idea. So it seems like we'll need external tools like Tuist or [Bazel](https://bazel.build) to provide a better developer experience. Or maybe Apple will surprise us all and make Xcode more extensible and explicit... Until that happens, you have to choose whether you want to embrace the convencience of Xcode and take on the debt that comes with it, or trust us on this journey to provide a better developer experience. We won't disappoint you. --- URL: "/es/guides/develop/projects/code-sharing" LLMS_URL: "/es/guides/develop/projects/code-sharing.md" title: "Code sharing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to share code across manifest files to reduce duplications and ensure consistency" --- # Code sharing {#code-sharing} One of the inconveniences of Xcode when we use it with large projects is that it doesn't allow reusing elements of the projects other than the build settings through `.xcconfig` files. Being able to reuse project definitions is useful for the following reasons: - It eases the **maintenance** because changes can be applied in one place and all the projects get the changes automatically. - It makes it possible to define **conventions** that new projects can conform to. - Projects are more **consistent** and therefore the likelihood of broken builds due inconsistencies is significantly less. - Adding a new projects becomes an easy task because we can reuse the existing logic. Reusing code across manifest files is possible in Tuist thanks to the concept of **project description helpers**. > [!TIP] A TUIST UNIQUE ASSET > Many organizations like Tuist because they see in project description helpers a platform for platform teams to codify their own conventions and come up with their own language for describing their projects. For example, YAML-based project generators have to come up with their own YAML-based propietary templating solution, or force organizations onto building their tools upon. ## Project description helpers {#project-description-helpers} Project description helpers are Swift files that get compiled into a module, `ProjectDescriptionHelpers`, that manifest files can import. The module is compiled by gathering all the files in the `Tuist/ProjectDescriptionHelpers` directory. You can import them into your manifest file by adding an import statement at the top of the file: ```swift // Project.swift import ProjectDescription import ProjectDescriptionHelpers ``` `ProjectDescriptionHelpers` are available in the following manifests: - `Project.swift` - `Package.swift` (only behind the `#TUIST` compiler flag) - `Workspace.swift` ## Example {#example} The snippets below contain an example of how we extend the `Project` model to add static constructors and how we use them from a `Project.swift` file: ::: code-group ```swift [Tuist/Project+Templates.swift] import ProjectDescription extension Project { public static func featureFramework(name: String, dependencies: [TargetDependency] = []) -> Project { return Project( name: name, targets: [ .target( name: name, destinations: .iOS, product: .framework, bundleId: "io.tuist.\(name)", infoPlist: "\(name).plist", sources: ["Sources/\(name)/**"], resources: ["Resources/\(name)/**",], dependencies: dependencies ), .target( name: "\(name)Tests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.\(name)Tests", infoPlist: "\(name)Tests.plist", sources: ["Sources/\(name)Tests/**"], resources: ["Resources/\(name)Tests/**",], dependencies: [.target(name: name)] ) ] ) } } ``` ```swift {2} [Project.swift] import ProjectDescription import ProjectDescriptionHelpers let project = Project.featureFramework(name: "MyFeature") ``` ::: > [!TIP] A TOOL TO ESTABLISH CONVENTIONS > Note how through the function we are defining conventions about the name of the targets, the bundle identifier, and the folders structure. --- URL: "/es/guides/develop/projects/best-practices" LLMS_URL: "/es/guides/develop/projects/best-practices.md" title: "Best practices" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the best practices for working with Tuist and Xcode projects." --- # Best practices {#best-practices} Over the years working with different teams and projects, we've identified a set of best practices that we recommend following when working with Tuist and Xcode projects. These practices are not mandatory, but they can help you structure your projects in a way that makes them easier to maintain and scale. ## Xcode {#xcode} ### Discouraged patterns {#discouraged-patterns} #### Configurations to model remote environments {#configurations-to-model-remote-environments} Many organizations use build configurations to model different remote environments (e.g., `Debug-Production` or `Release-Canary`), but this approach has some downsides: - **Inconsistencies:** If there are configuration inconsistencies throughout the graph, the build system might end up using the wrong configuration for some targets. - **Complexity:** Projects can end up with a long list of local configurations and remote environments that are hard to reason about and maintain. Build configurations were designed to embody different build settings, and projects rarely need more than just `Debug` and `Release`. The need to model different environments can be achieved by using schemes: - **In Debug builds:** You can include all the configurations that should be accessible in development in the app (e.g. endpoints), and switch them at runtime. The switch can happen either using scheme launch environment variables, or with a UI within the app. - **In Release builds:** In case of release, you can only include the configuration that the release build is bound to, and not include the runtime logic for switching configurations by using compiler directives. --- URL: "/es/guides/develop/projects/adoption/swift-package" LLMS_URL: "/es/guides/develop/projects/adoption/swift-package.md" title: "Use Tuist with a Swift Package" titleTemplate: ":title · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to use Tuist with a Swift Package." --- # Using Tuist with a Swift Package {#using-tuist-with-a-swift-package-badge-typewarning-textbeta-} Tuist supports using `Package.swift` as a DSL for your projects and it converts your package targets into a native Xcode project and targets. > [!WARNING] > The aim of this feature is to provide an easy way for developers to assess the impact of adopting Tuist in their Swift Packages. Therefore, we don't plan to support the full range of Swift Package Manager features nor to bring every Tuist's unique features like project description helpers to the packages world. > [!NOTE] ROOT DIRECTORY > Tuist commands expect a certain directory structure whose root is identified by a `Tuist` or a `.git` directory. ## Using Tuist with a Swift Package {#using-tuist-with-a-swift-package} We are going to use Tuist with the [TootSDK Package](https://github.com/TootSDK/TootSDK) repository, which contains a Swift Package. The first thing that we need to do is to clone the repository: ```bash git clone https://github.com/TootSDK/TootSDK cd TootSDK ``` Once in the repository's directory, we need to install the Swift Package Manager dependencies: ```bash tuist install ``` Under the hood `tuist install` uses the Swift Package Manager to resolve and pull the dependencies of the package. After the resolution completes, you can then generate the project: ```bash tuist generate ``` Voilà! You have a native Xcode project that you can open and start working on. --- URL: "/es/guides/develop/projects/adoption/new-project" LLMS_URL: "/es/guides/develop/projects/adoption/new-project.md" title: "Create a new project" titleTemplate: ":title · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to create a new project with Tuist." --- # Create a new project {#create-a-new-project} The most straightforward way to start a new project with Tuist is to use the `tuist init` command. This command launches an interactive CLI that guides you through setting up your project. When prompted, make sure to select the option to create a "generated project". You can then edit the project running `tuist edit`, and Xcode will open a project where you can edit the project. One of the files that are generated is the `Project.swift`, which contains the definition of your project. If you are familiar with the Swift Package Manager, think of it as the `Package.swift` but with the lingo of Xcode projects. ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "MyApp", targets: [ .target( name: "MyApp", destinations: .iOS, product: .app, bundleId: "io.tuist.MyApp", infoPlist: .extendingDefault( with: [ "UILaunchScreen": [ "UIColorName": "", "UIImageName": "", ], ] ), sources: ["MyApp/Sources/**"], resources: ["MyApp/Resources/**"], dependencies: [] ), .target( name: "MyAppTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyAppTests", infoPlist: .default, sources: ["MyApp/Tests/**"], resources: [], dependencies: [.target(name: "MyApp")] ), ] ) ``` ::: > [!NOTE] > We intentionally keep the list of available templates short to minimize maintenance overhead. If you want to create a project that doesn't represent an application, for example a framework, you can use `tuist init` as a starting point and then modify the generated project to suit your needs. ## Manually creating a project {#manually-creating-a-project} Alternatively, you can create the project manually. We recommend doing this only if you're already familiar with Tuist and its concepts. The first thing that you'll need to do is to create additional directories for the project structure: ```bash mkdir MyFramework cd MyFramework ``` Then create a `Tuist.swift` file, which will configure Tuist and is used by Tuist to determine the root directory of the project, and a `Project.swift`, where your project will be declared: ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "MyFramework", targets: [ .target( name: "MyFramework", destinations: .macOS, product: .framework, bundleId: "io.tuist.MyFramework", sources: ["MyFramework/Sources/**"], dependencies: [] ) ] ) ``` ```swift [Tuist.swift] import ProjectDescription let tuist = Tuist() ``` ::: > [!IMPORTANT] > Tuist uses the `Tuist/` directory to determine the root of your project, and from there it looks for other manifest files globbing the directories. We recommend creating those files with your editor of choice, and from that point on, you can use `tuist edit` to edit the project with Xcode. --- URL: "/es/guides/develop/projects/adoption/migrate/xcodegen-project" LLMS_URL: "/es/guides/develop/projects/adoption/migrate/xcodegen-project.md" title: "Migrate an XcodeGen project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate your projects from XcodeGen to Tuist." --- # Migrate an XcodeGen project {#migrate-an-xcodegen-project} [XcodeGen](https://github.com/yonaskolb/XcodeGen) is a project-generation tool that uses YAML as [a configuration format](https://github.com/yonaskolb/XcodeGen/blob/master/Docs/ProjectSpec.md) to define Xcode projects. Many organizations **adopted it trying to escape from the frequent Git conflicts that arise when working with Xcode projects.** However, frequent Git conflicts is just one of the many problems that organizations experience. Xcode exposes developers with a lot of intricacies and implicit configurations that make it hard to maintain and optimize projects at scale. XcodeGen falls short there by design because it's a tool that generates Xcode projects, not a project manager. If you need a tool that helps you beyond generating Xcode projects, you might want to consider Tuist. > [!TIP] SWIFT OVER YAML > Many organizations prefer Tuist as a project generation tool too because it uses Swift as a configuration format. Swift is a programming language that developers are familiar with, and that provides them with the convenience of using Xcode's autocompletion, type-checking, and validation features. What follows are some considerations and guidelines to help you migrate your projects from XcodeGen to Tuist. ## Project generation {#project-generation} Both Tuist and XcodeGen provide a `generate` command that turns your project declaration into Xcode projects and workspaces. ::: code-group ```bash [XcodeGen] xcodegen generate ``` ```bash [Tuist] tuist generate ``` ::: The difference lays in the editing experience. With Tuist, you can run the `tuist edit` command, which generates an Xcode project on the fly that you can open and start working on. This is particularly useful when you want to make quick changes to your project. ## `project.yaml` {#projectyaml} XcodeGen's `project.yaml` description file becomes `Project.swift`. Moreover, you can have `Workspace.swift` as a way to customize how projects are grouped in workspaces. You can also have a project `Project.swift` with targets that reference targets from other projects. In those cases, Tuist will generate an Xcode Workspace including all the projects. ::: code-group ```bash [XcodeGen directory structure] / project.yaml ``` ```bash [Tuist directory structure] / Tuist.swift Project.swift Workspace.swift ``` ::: > [!TIP] XCODE'S LANGUAGE > Both XcodeGen and Tuist embrace Xcode's language and concepts. However, Tuist's Swift-based configuration provides you with the convenience of using Xcode's autocompletion, type-checking, and validation features. ## Spec templates {#spec-templates} One of the disadvantages of YAML as a language for project configuration is that it doesn't support reusability across YAML files out of the box. This is a common need when describing projects, which XcodeGen had to solve with their own propietary solution named _"templates"_. With Tuist's re-usability is built into the language itself, Swift, and through a Swift module named project description helpers, which allow reusing code across all your manifest files. ::: code-group ```swift [Tuist/ProjectDescriptionHelpers/Target+Features.swift] import ProjectDescription extension Target { /** This function is a factory of targets that together represent a feature. */ static func featureTargets(name: String) -> [Target] { // ... } } ``` ```swift [Project.swift] import ProjectDescription import ProjectDescriptionHelpers // [!code highlight] let project = Project(name: "MyProject", targets: Target.featureTargets(name: "MyFeature")) // [!code highlight] ``` --- URL: "/es/guides/develop/projects/adoption/migrate/xcode-project" LLMS_URL: "/es/guides/develop/projects/adoption/migrate/xcode-project.md" title: "Migrate an Xcode project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate an Xcode project to a Tuist project." --- # Migrate an Xcode project {#migrate-an-xcode-project} Unless you create a new project using Tuist, in which case you get everything configured automatically, you'll have to define your Xcode projects using Tuist's primitives. How tedious this process is, depends on how complex your projects are. As you probably know, Xcode projects can become messy and complex over time: groups that don't match the directory structure, files that are shared across targets, or file references that point to nonexisting files (to mention some). All that accumulated complexity makes it hard for us to provide a command that reliably migrates project. Moreover, manual migration is an excellent exercise to clean up and simplify your projects. Not only the developers in your project will be thankful for that, but Xcode, who will be faster processing and indexing them. Once you have fully adopted Tuist, it will make sure that projects are consistently defined and that they remain simple. In the aim of easing that work, we are giving you some guidelines based on the feedback that we have received from the users. ## Create project scaffold {#create-project-scaffold} First of all, create a scaffold for your project with the following Tuist files: ::: code-group ```js [Tuist.swift] import ProjectDescription let tuist = Tuist() ``` ```js [Project.swift] import ProjectDescription let project = Project( name: "MyApp-Tuist", targets: [ /** Targets will go here **/ ] ) ``` ```js [Tuist/Package.swift] // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies ] ) ``` ::: `Project.swift` is the manifest file where you'll define your project, and `Package.swift` is the manifest file where you'll define your dependencies. The `Tuist.swift` file is where you can define project-scoped Tuist settings for your project. > [!TIP] PROJECT NAME WITH -TUIST SUFFIX > To prevent conflicts with the existing Xcode project, we recommend adding the `-Tuist` suffix to the project name. You can drop it once you've fully migrated your project to Tuist. ## Build and test the Tuist project in CI {#build-and-test-the-tuist-project-in-ci} To ensure the migration of each change is valid, we recommend extending your continuous integration to build and test the project generated by Tuist from your manifest file: ```bash tuist install tuist generate tuist build -- ...{xcodebuild flags} # or tuist test ``` ## Extract the project build settings into `.xcconfig` files {#extract-the-project-build-settings-into-xcconfig-files} Extract the build settings from the project into an `.xcconfig` file to make the project leaner and easier to migrate. You can use the following command to extract the build settings from the project into an `.xcconfig` file: ```bash mkdir -p xcconfigs/ tuist migration settings-to-xcconfig -p MyApp.xcodeproj -x xcconfigs/MyApp-Project.xcconfig ``` Then update your `Project.swift` file to point to the `.xcconfig` file you've just created: ```swift import ProjectDescription let project = Project( name: "MyApp", settings: .settings(configurations: [ .debug(name: "Debug", xcconfig: "./xcconfigs/MyApp-Project.xcconfig"), // [!code ++] .release(name: "Release", xcconfig: "./xcconfigs/MyApp-Project.xcconfig"), // [!code ++] ]), targets: [ /** Targets will go here **/ ] ) ``` Then extend your continuous integration pipeline to run the following command to ensure that changes to build settings are made directly to the `.xcconfig` files: ```bash tuist migration check-empty-settings -p Project.xcodeproj ``` ## Extract package dependencies {#extract-package-dependencies} Extract all your project's dependencies into the `Tuist/Package.swift` file: ```swift // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies .package(url: "https://github.com/onevcat/Kingfisher", .upToNextMajor(from: "7.12.0")) // [!code ++] ] ) ``` > [!TIP] PRODUCT TYPES > You can override the product type for a specific package by adding it to the `productTypes` dictionary in the `PackageSettings` struct. By default, Tuist assumes that all packages are static frameworks. ## Determine the migration order {#determine-the-migration-order} We recommend migrating the targets from the one that is the most dependent upon to the least. You can use the following command to list the targets of a project, sorted by the number of dependencies: ```bash tuist migration list-targets -p Project.xcodeproj ``` Start migrating the targets from the top of the list, as they are the ones that are the most depended upon. ## Migrate targets {#migrate-targets} Migrate the targets one by one. We recommend doing a pull request for each target to ensure that the changes are reviewed and tested before merging them. ### Extract the target build settings into `.xcconfig` files {#extract-the-target-build-settings-into-xcconfig-files} Like you did with the project build settings, extract the target build settings into an `.xcconfig` file to make the target leaner and easier to migrate. You can use the following command to extract the build settings from the target into an `.xcconfig` file: ```bash tuist migration settings-to-xcconfig -p MyApp.xcodeproj -t TargetX -x xcconfigs/TargetX.xcconfig ``` ### Define the target in the `Project.swift` file {#define-the-target-in-the-projectswift-file} Define the target in `Project.targets`: ```swift import ProjectDescription let project = Project( name: "MyApp", settings: .settings(configurations: [ .debug(name: "Debug", xcconfig: "./xcconfigs/Project.xcconfig"), .release(name: "Release", xcconfig: "./xcconfigs/Project.xcconfig"), ]), targets: [ .target( // [!code ++] name: "TargetX", // [!code ++] destinations: .iOS, // [!code ++] product: .framework, // [!code ++] // or .staticFramework, .staticLibrary... bundleId: "io.tuist.targetX", // [!code ++] sources: ["Sources/TargetX/**"], // [!code ++] dependencies: [ // [!code ++] /** Dependencies go here **/ // [!code ++] /** .external(name: "Kingfisher") **/ // [!code ++] /** .target(name: "OtherProjectTarget") **/ // [!code ++] ], // [!code ++] settings: .settings(configurations: [ // [!code ++] .debug(name: "Debug", xcconfig: "./xcconfigs/TargetX.xcconfig"), // [!code ++] .debug(name: "Release", xcconfig: "./xcconfigs/TargetX.xcconfig"), // [!code ++] ]) // [!code ++] ), // [!code ++] ] ) ``` > [!NOTE] TEST TARGETS > If the target has an associated test target, you should define it in the `Project.swift` file as well repeating the same steps. ### Validate the target migration {#validate-the-target-migration} Run `tuist build` and `tuist test` to ensure the project builds and tests pass. Additionally, you can use [xcdiff](https://github.com/bloomberg/xcdiff) to compare the generated Xcode project with the existing one to ensure that the changes are correct. ### Repeat {#repeat} Repeat until all the targets are fully migrated. Once you are done, we recommend updating your CI and CD pipelines to build and test the project using `tuist build` and `tuist test` commands to benefit from the speed and reliability that Tuist provides. ## Troubleshooting {#troubleshooting} ### Compilation errors due to missing files. {#compilation-errors-due-to-missing-files} If the files associated to your Xcode project targets were not all contained in a file-system directory representing the target, you might end up with a project that doesn't compile. Make sure the list of files after generating the project with Tuist matches the list of files in the Xcode project, and take the opportunity to align the file structure with the target structure. --- URL: "/es/guides/develop/projects/adoption/migrate/swift-package" LLMS_URL: "/es/guides/develop/projects/adoption/migrate/swift-package.md" title: "Migrate a Swift Package" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate from Swift Package Manager as a solution for managing your projects to Tuist projects." --- # Migrate a Swift Package {#migrate-a-swift-package} Swift Package Manager emerged as a dependency manager for Swift code that uninentionally found itself solving the problem of managing projects and supporting other programming languages like Objective-C. Because the tool was designed with a different purpose in mind, it can be challenging to use it to manage projects at scale because it lacks flexibility, performance, and power that Tuist provides. This is well captured in the [Scaling iOS at Bumble](https://medium.com/bumble-tech/scaling-ios-at-bumble-239e0fa009f2) article, which includes the following table comparing the performance of Swift Package Manager and native Xcode projects: A table that compares the regression in performance when using SPM over native Xcode projects We often come across developers and organizations that challenge the need for Tuist considering that Swift Package Manager can take a similar project management role. Some venture into a migration to later on realize that their developer experience has degraded signicantly. For instance, the rename of a file might take up to 15 seconds to re-index. 15 seconds! **Whether Apple will make Swift Package Manager a built-for-scale project manager is uncertain.** However, we are not seeing any signs that it's happening. In fact, we are seeing quite the opposite. They are making Xcode-inspired decisions, like achieving convenience through implicit configurations, which as you might know, is the source of complications at scale. We believe it'd take Apple to go to first principles and revisit some decisions that made sense as a dependency manager but not as a project manager, for example the usage of a compiled language as an interface to define projects. > [!TIP] SPM AS JUST A DEPENDENCY MANAGER > Tuist treats Swift Package Manager as a dependency manager, and it's a great one. We use it to resolve dependencies and to build them. We don't use it to define projects because it's not designed for that. ## Migrating from Swift Package Manager to Tuist {#migrating-from-swift-package-manager-to-tuist} The similarities between Swift Package Manager and Tuist make the migration process straightforward. The main difference is that you'll be defining your projects using Tuist's DSL instead of `Package.swift`. First, create a `Project.swift` file next to your `Package.swift` file. The `Project.swift` file will contain the definition of your project. Here's an example of a `Project.swift` file that defines a project with a single target: ```swift import ProjectDescription let project = Project( name: "App", targets: [ .target( name: "App", destinations: .iOS, product: .app, bundleId: "io.tuist.App", sources: ["Sources/**/*.swift"]* ), ] ) ``` Some things to note: - **ProjectDescription**: Instead of using `PackageDescription`, you'll be using `ProjectDescription`. - **Project:** Instead of exporting a `package` instance, you'll be exporting a `project` instance. - **Xcode language:** The primitives that you use to define your project mimic Xcode's language, so you'll find schemes, targets, and build phases among others. Then create a `Tuist.swift` file with the following content: ```swift import ProjectDescription let tuist = Tuist() ``` The `Tuist.swift` contains the configuration for your project and its path serves as a reference to determine the root of your project. You can check out the directory structure document to learn more about the structure of Tuist projects. ## Editing the project {#editing-the-project} You can use `tuist edit` to edit the project in Xcode. The command will generate an Xcode project that you can open and start working on. ```bash tuist edit ``` Depending on the size of the project, you might consider using it in one shot or incrementally. We recommend starting with a small project to get familiar with the DSL and the workflow. Our advise is always to start from the most depended upon target and work all the way up to the top-level target. --- URL: "/es/guides/develop/projects/adoption/migrate/bazel-project" LLMS_URL: "/es/guides/develop/projects/adoption/migrate/bazel-project.md" title: "Migrate a Bazel project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate your projects from Bazel to Tuist." --- # Migrate a Bazel project {#migrate-a-bazel-project} [Bazel](https://bazel.build) is a build system that Google open-sourced in 2015. It's a powerful tool that allows you to build and test software of any size, quickly and reliably. Some large organizations like [Spotify](https://engineering.atspotify.com/2023/10/switching-build-systems-seamlessly/), [Tinder](https://medium.com/tinder/bazel-hermetic-toolchain-and-tooling-migration-c244dc0d3ae), or [Lyft](https://semaphoreci.com/blog/keith-smiley-bazel) use it, however, it requires an upfront (i.e., learning the technology) and ongoing investment (i.e., keeping up with Xcode updates) to introduce and maintain. While this works for some organizations that treat it as a cross-cutting concern, it might not be the best fit for others that want to focus on their product development. For instance, we've seen organizations whose iOS platform team introduced Bazel and had to drop it after the engineers that led the effort left the company. Apple's stance on the strong coupling between Xcode and the build system is another factor that makes it hard to maintain Bazel projects over time. > [!TIP] TUIST UNIQUENESS LIES IN ITS FINESSE > Instead of fighting Xcode and Xcode projects, Tuist embraces it. It's the same concepts (e.g., targets, schemes, build settings), a familiar language (i.e., Swift), and a simple and enjoyable experience that makes maintaining and scaling projects everyone's job and not just the iOS platform team's. ## Rules {#rules} Bazel uses rules to define how to build and test software. The rules are written in [Starlark](https://github.com/bazelbuild/starlark), a Python-like language. Tuist uses Swift as a configuration language, which provides developers with the convenience of using Xcode's autocompletion, type-checking, and validation features. For example, the following rule describes how to build a Swift library in Bazel: ::: code-group ```txt [BUILD (Bazel)] swift_library( name = "MyLibrary.library", srcs = glob(["**/*.swift"]), module_name = "MyLibrary" ) ``` ```swift [Project.swift (Tuist)] let project = Project( // ... targets: [ .target(name: "MyLibrary", product: .staticLibrary, sources: ["**/*.swift"]) ] ) ``` ::: Here's another example but compating how to define unit tests in Bazel and Tuist: :::code-group ```txt [BUILD (Bazel)] ios_unit_test( name = "MyLibraryTests", bundle_id = "io.tuist.MyLibraryTests", minimum_os_version = "16.0", test_host = "//MyApp:MyLibrary", deps = [":MyLibraryTests.library"], ) ``` ```swift [Project.swift (Tuist)] let project = Project( // ... targets: [ .target( name: "MyLibraryTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyLibraryTests", sources: "Tests/MyLibraryTests/**", dependencies: [ .target(name: "MyLibrary"), ] ) ] ) ``` ::: ## Swift Package Manager dependencies {#swift-package-manager-dependencies} In Bazel, you can use the [`rules_swift_package_manager`](https://github.com/cgrindel/rules_swift_package_manager) [Gazelle](https://github.com/bazelbuild/bazel-gazelle/blob/master/extend.md) plugin to use Swift Packages as dependencies. The plugin requires a `Package.swift` as a source of truth for the dependencies. Tuist's interface is similar to Bazel's in that sense. You can use the `tuist install` command to resolve and pull the dependencies of the package. After the resolution completes, you can then generate the project with the `tuist generate` command. ```bash tuist install # Fetch dependencies defined in Tuist/Package.swift tuist generate # Generate an Xcode project ``` ## Project generation {#project-generation} The community provides a set of rules, [rules_xcodeproj](https://github.com/MobileNativeFoundation/rules_xcodeproj), to generate Xcode projects off Bazel-declared projects. Unlike Bazel, where you need to add some configuration to your `BUILD` file, Tuist doesn't require any configuration at all. You can run `tuist generate` in the root directory of your project, and Tuist will generate an Xcode project for you. --- URL: "/es/guides/develop/projects" LLMS_URL: "/es/guides/develop/projects.md" title: "Projects" titleTemplate: ":title · Develop · Guides · Tuist" description: "Learn about Tuist's DSL for defining Xcode projects." --- # Projects {#projects} Generated is a viable alternative that helps to overcome these challenges while keeping complexity and costs at an acceptable level. It considers Xcode projects as a fundamental element, ensuring resilience against future Xcode updates, and leverages Xcode project generation to provide teams with a modularization-focused declarative API. Tuist uses the project declaration to simplify the complexities of modularization\*\*, optimize workflows like build or test across various environments, and facilitate and democratize the evolution of Xcode projects. ## How does it work? {#how-does-it-work} To get started with generated projects, all you need is to define your project using **Tuist's Domain Specific Language (DSL)**. This entails using manifest files such as `Workspace.swift` or `Project.swift`. If you've worked with the Swift Package Manager before, the approach is very similar. Once you've defined your project, Tuist offers various workflows to manage and interact with it: - **Generate:** This is a foundational workflow. Use it to create an Xcode project that's compatible with Xcode. - **Build:** This workflow not only generates the Xcode project but also employs `xcodebuild` to compile it. - **Test:** Operating much like the build workflow, this not only generates the Xcode project but utilizes `xcodebuild` to test it. ## Challenges with Xcode projects {#challenges-with-xcode-projects} As Xcode projects grow, **organizations may face a decline in productivity** due to several factors, including unreliable incremental builds, frequent clearing of Xcode's global cache by developers encountering issues, and fragile project configurations. To maintain rapid feature development, organizations typically explore various strategies. Some organizations choose to bypass the compiler by abstracting the platform using JavaScript-based dynamic runtimes, such as [React Native](https://reactnative.dev/). While this approach may be effective, it [complicates access to the platform's native features](https://shopify.engineering/building-app-clip-react-native). Other organizations opt for **modularizing the codebase**, which helps establish clear boundaries, making the codebase easier to work with and improving the reliability of build times. However, the Xcode project format is not designed for modularity and results in implicit configurations that few understand and frequent conflicts. This leads to a bad bus factor, and although incremental builds may improve, developers might still frequently clear Xcode's build cache (i.e., derived data) when builds fail. To address this, some organizations choose to **abandon Xcode's build system** and adopt alternatives like [Buck](https://buck.build/) or [Bazel](https://bazel.build/). However, this comes with a [high complexity and maintenance burden](https://bazel.build/migrate/xcode). ## Alternatives {#alternatives} ### Swift Package Manager {#swift-package-manager} While the Swift Package Manager (SPM) primarily focuses on dependencies, Tuist offers a different approach. With Tuist, you don't just define packages for SPM integration; you shape your projects using familiar concepts like projects, workspaces, targets, and schemes. ### XcodeGen {#xcodegen} [XcodeGen](https://github.com/yonaskolb/XcodeGen) is a dedicated project generator designed to reduce conflicts in collaborative Xcode projects and simplify some complexities of Xcode's internal workings. However, projects are defined using serializable formats like [YAML](https://yaml.org/). Unlike Swift, this doesn't allow developers to build upon abstractions or checks without incorporating additional tools. While XcodeGen does offer a way to map dependencies to an internal representation for validation and optimization, it still exposes developers to the nuances of Xcode. This might make XcodeGen a suitable foundation for [building tools](https://github.com/MobileNativeFoundation/rules_xcodeproj), as seen in the Bazel community, but it's not optimal for inclusive project evolution that aims to maintain a healthy and productive environment. ### Bazel {#bazel} [Bazel](https://bazel.build) is an advanced build system renowned for its remote caching features, gaining popularity within the Swift community primarily for this capability. However, given the limited extensibility of Xcode and its build system, substituting it with Bazel's system demands significant effort and maintenance. Only a few companies with abundant resources can bear this overhead, as evident from the select list of firms investing heavily to integrate Bazel with Xcode. Interestingly, the community created a [tool](https://github.com/MobileNativeFoundation/rules_xcodeproj) that employs Bazel's XcodeGen to generate an Xcode project. This results in a convoluted chain of conversions: from Bazel files to XcodeGen YAML and finally to Xcode Projects. Such layered indirection often complicates troubleshooting, making issues more challenging to diagnose and resolve. --- URL: "/es/guides/develop/insights" LLMS_URL: "/es/guides/develop/insights.md" title: "Insights" titleTemplate: ":title · Develop · Guides · Tuist" description: "Get insights into your projects to maintain a product developer environment." --- # Insights {#insights} > [!IMPORTANT] REQUIREMENTS > > - A Tuist account and project Working on large projects shouldn't feel like a chore. In fact, it should be as enjoyable as working on a project you started just two weeks ago. One of the reasons it is not is because as the project grows, the developer experience suffers. The build times increase and tests become slow and flaky. It's often easy to overlook these issues until it gets to a point where they become unbearable – however, at that point, it's difficult to address them. Tuist Insights provides you with the tools to monitor the health of your project and maintain a productive developer environment as your project scales. In other words, Tuist Insights helps you to anwer questions such as: - Has the build time significantly increased in the last week? - Have my tests become slower? Which ones? > [!NOTE] > Tuist Insights are in early development. ## Builds {#builds} While you probably have some metrics for the performance of CI workflows, you might not have the same visibility into the local development environment. However, local build times are one of the most important factors that contribute to the developer experience. To start tracking local build times, you can leverage the `tuist inspect build` command by adding it to your scheme's post-action: ![Post-action for inspecting builds](/images/guides/develop/insights/inspect-build-scheme-post-action.png) In case you're using [Mise](https://mise.jdx.dev/), your script will need to activate `tuist` in the post-action environment: ```sh # -C ensures that Mise loads the configuration from the Mise configuration # file in the project's root directory. eval "$($HOME/.local/bin/mise activate -C $SRCROOT bash --shims)" tuist inspect build ``` Your local builds are now tracked as long as you are logged in to your Tuist account. You can now access your build times in the Tuist dashboard and see how they evolve over time: > [!TIP] > To quickly access the dashboard, run `tuist project show --web` from the CLI. ![Dashboard with build insights](/images/guides/develop/insights/builds-dashboard.png) ## Projects {#projects} > [!NOTE] > Auto-generated schemes automatically include the `tuist inspect build` post-action. > > If you are not interested in tracking build insights in your auto-generated schemes, disable them using the buildInsightsDisabled generation option. If you are using generated projects, you can set up a custom build post-action using a custom scheme, such as: ```swift let project = Project( name: "MyProject", targets: [ // Your targets ], schemes: [ .scheme( name: "MyApp", shared: true, buildAction: .buildAction(targets: ["MyApp"]), testAction: .testAction(targets: ["MyAppTests"]), runAction: .runAction(configuration: "Debug"), postActions: [ .postAction( name: "Inspect Build", scriptText: """ eval \"$($HOME/.local/bin/mise activate -C $SRCROOT bash --shims)\" tuist inspect build """ ) ] ) ] ) ``` If you're not using Mise, your script can be simplified to just: ```swift .postAction( name: "Inspect Build", script: "tuist inspect build", execution: .always ) ``` ## Continuous integration {#continuous-integration} To track build times also on the CI, you will need to ensure that your CI is authenticated. Additionally, you will either need to: - Use the `tuist xcodebuild` command when invoking `xcodebuild` actions. - Add `-resultBundlePath` to your `xcodebuild` invocation. When `xcodebuild` builds your project without `-resultBundlePath`, the `.xcactivitylog` file is not generated. But the `tuist inspect build` post-action requires that file to be generated to analyze your build. --- URL: "/es/guides/develop/cache" LLMS_URL: "/es/guides/develop/cache.md" title: "Cache" titleTemplate: ":title · Develop · Guides · Tuist" description: "Optimize your build times by caching compiled binaries and sharing them across different environments." --- # Cache {#cache} > [!IMPORTANT] REQUIREMENTS > > - A generated project > - A Tuist account and project Xcode's build system provides [incremental builds](https://en.wikipedia.org/wiki/Incremental_build_model), enhancing efficiency under normal circumstances. However, this feature falls short in [Continuous Integration (CI) environments](https://en.wikipedia.org/wiki/Continuous_integration), where data essential for incremental builds is not shared across different builds. Additionally, **developers often reset this data locally to troubleshoot complex compilation problems**, leading to more frequent clean builds. This results in teams spending excessive time waiting for local builds to finish or for Continuous Integration pipelines to provide feedback on pull requests. Furthermore, the frequent context switching in such an environment compounds this unproductiveness. Tuist addresses these challenges effectively with its caching feature. This tool optimizes the build process by caching compiled binaries, significantly reducing build times both in local development and CI environments. This approach not only accelerates feedback loops but also minimizes the need for context switching, ultimately boosting productivity. ## Warming {#warming} Tuist efficiently utilizes hashes for each target in the dependency graph to detect changes. Utilizing this data, it builds and assigns unique identifiers to binaries derived from these targets. At the time of graph generation, Tuist then seamlessly substitutes the original targets with their corresponding binary versions. This operation, known as _"warming,"_ produces binaries for local use or for sharing with teammates and CI environments via Tuist. The process of warming the cache is straightforward and can be initiated with a simple command: ```bash tuist cache ``` The command re-uses binaries to speed up the process. ## Usage {#usage} By default, when Tuist commands necessitate project generation, they automatically substitute dependencies with their binary equivalents from the cache, if available. Additionally, if you specify a list of targets to focus on, Tuist will also replace any dependent targets with their cached binaries, provided they are available. For those who prefer a different approach, there is an option to opt out of this behavior entirely by using a specific flag: ::: code-group ```bash [Project generation] tuist generate # Only dependencies tuist generate Search # Dependencies + Search dependencies tuist generate Search Settings # Dependencies, and Search and Settings dependencies tuist generate --no-binary-cache # No cache at all ``` ```bash [Testing] tuist test ``` ::: > [!WARNING] > Binary caching is a feature designed for development workflows such as running the app on a simulator or device, or running tests. It is not intended for release builds. When archiving the app, generate a project with the sources by using the `--no-binary-cache` flag. ## Supported products {#supported-products} Only the following target products are cacheable by Tuist: - Frameworks (static and dynamic) that don't depend on [XCTest](https://developer.apple.com/documentation/xctest) - Bundles - Swift Macros We are working on supporting libraries and targets that depend on XCTest. > [!NOTE] UPSTREAM DEPENDENCIES > When a target is non-cacheable it makes the upstream targets non-cacheable too. For example, if you have the dependency graph `A > B`, where A depends on B, if B is non-cacheable, A will also be non-cacheable. ## Efficiency {#efficiency} The level of efficiency that can be achieved with binary caching depends strongly on the graph structure. To achieve the best results, we recommend the following: 1. Avoid very nested dependency graphs. The shallower the graph, the better. 2. Define dependencies with protocol/interface targets instead of implementation ones, and dependency-inject implementations from the top-most targets. 3. Split frequently-modified targets into smaller ones whose likelihood of change is lower. The above suggestions are part of the The Modular Architecture, which we propose as a way to structure your projects to maximize the benefits not only of binary caching but also of Xcode's capabilities. ## Recommended setup {#recommended-setup} We recommend having a CI job that **runs in every commit in the main branch** to warm the cache. This will ensure the cache always contains binaries for the changes in `main` so local and CI branch build incrementally upon them. > [!TIP] CACHE WARMING USES BINARIES > The `tuist cache` command also makes use of the binary cache to speed up the warming. The following are some examples of common workflows: ### A developer starts to work on a new feature {#a-developer-starts-to-work-on-a-new-feature} 1. They create a new branch from `main`. 2. They run `tuist generate`. 3. Tuist pulls the most recent binaries from `main` and generates the project with them. ### A developer pushes changes upstream {#a-developer-pushes-changes-upstream} 1. The CI pipeline will run `tuist build` or `tuist test` to build or test the project. 2. The workflow will pull the most recent binaries from `main` and generate the project with them. 3. It will then build or test the project incrementally. ## Troubleshooting {#troubleshooting} ### It doesn't use binaries for my targets {#it-doesnt-use-binaries-for-my-targets} Ensure that the hashes are deterministic across environments and runs. This might happen if the project has references to the environment, for example through absolute paths. You can use the `diff` command to compare the projects generated by two consecutive invocations of `tuist generate` or across environments or runs. Also make sure that the target doesn't depend either directly or indirectly on a non-cacheable target. --- URL: "/es/guides/develop/build" LLMS_URL: "/es/guides/develop/build.md" title: "Build" titleTemplate: ":title · Develop · Guides · Tuist" description: "Learn how to use Tuist to build your projects efficiently." --- # Build {#build} Projects are usually built through a build-system-provided CLI (e.g. `xcodebuild`). Tuist wraps them to improve the user experience and integrate the workflows with the platform to provide optimizations and analytics. You might wonder what's the value of using `tuist build` over generating the project with `tuist generate` (if needed) and building it with the platform-specific CLI. Here are some reasons: - **Single command:** `tuist build` ensures the project is generated if needed before compiling the project. - **Beautified output:** Tuist enriches the output using tools like [xcbeautify](https://github.com/cpisciotta/xcbeautify) that make the output more user-friendly. - Cache: It optimizes the build by deterministically reusing the build artifacts from a remote cache. - **Analytics:** It collects and reports metrics that are correlated with other data points to provide you with actionable information to make informed decisions. ## Usage {#usage} `tuist build` generates the project if needed, and then build it using the platform-specific build tool. We support the use of the `--` terminator to forward all subsequent arguments directly to the underlying build tool. This is useful when you need to pass arguments that are not supported by `tuist build` but are supported by the underlying build tool. ::: code-group ```bash [Build a scheme] tuist build MyScheme ``` ```bash [Build a specific configuration] tuist build MyScheme -- -configuration Debug ``` ```bash [Build all schemes without binary cache] tuist build --no-binary-cache ``` ::: --- URL: "/es/guides/automate/continuous-integration" LLMS_URL: "/es/guides/automate/continuous-integration.md" title: "Continuous Integration (CI)" titleTemplate: ":title · Automate · Guides · Tuist" description: "Learn how to use Tuist in your CI workflows." --- # Continuous Integration (CI) {#continuous-integration-ci} You can use Tuist in [continuous integration](https://en.wikipedia.org/wiki/Continuous_integration) environments. The following sections provide examples of how to do this on different CI platforms. ## Examples {#examples} To run Tuist commands in your CI workflows, you’ll need to install it in your CI environment. ### Xcode Cloud {#xcode-cloud} In [Xcode Cloud](https://developer.apple.com/xcode-cloud/), which uses Xcode projects as the source of truth, you'll need to add a [post-clone](https://developer.apple.com/documentation/xcode/writing-custom-build-scripts#Create-a-custom-build-script) script to install Tuist and run the commands you need, for example `tuist generate`: :::code-group ```bash [Mise] #!/bin/sh curl https://mise.jdx.dev/install.sh | sh mise install # Installs the version from .mise.toml # Runs the version of Tuist indicated in the .mise.toml file {#runs-the-version-of-tuist-indicated-in-the-misetoml-file} mise exec -- tuist generate ``` ```bash [Homebrew] #!/bin/sh brew install --formula tuist@x.y.z tuist generate ``` ::: ### Codemagic {#codemagic} In [Codemagic](https://codemagic.io), you can add an additional step to your workflow to install Tuist: ::: code-group ```yaml [Mise] workflows: lint: name: Build max_build_duration: 30 environment: xcode: 15.0.1 scripts: - name: Install Mise script: | curl https://mise.jdx.dev/install.sh | sh mise install # Installs the version from .mise.toml - name: Build script: mise exec -- tuist build ``` ```yaml [Homebrew] workflows: lint: name: Build max_build_duration: 30 environment: xcode: 15.0.1 scripts: - name: Install Tuist script: | brew install --formula tuist@x.y.z - name: Build script: tuist build ``` ::: ### GitHub Actions {#github-actions} On [GitHub Actions](https://docs.github.com/en/actions) you can add an additional step to install Tuist, and in the case of managing the installation of Mise, you can use the [mise-action](https://github.com/jdx/mise-action), which abstracts the installation of Mise and Tuist: ::: code-group ```yaml [Mise] name: Build Application on: pull_request: branches: - main push: branches: - main jobs: build: runs-on: macos-latest steps: - uses: actions/checkout@v3 - uses: jdx/mise-action@v2 - run: tuist build ``` ```yaml [Homebrew] name: test on: pull_request: branches: - main push: branches: - main jobs: lint: runs-on: macos-latest steps: - uses: actions/checkout@v3 - run: brew install --formula tuist@x.y.z - run: tuist build ``` ::: :::tip We recommend using `mise use --pin` in your Tuist projects to pin the version of Tuist across environments. The command will create a `.tool-versions` file containing the version of Tuist. ::: ## Authentication {#authentication} When using server-side features such as cache, you'll need a way to authenticate requests going from your CI workflows to the server. For that, you can generate a project-scoped token by running the following command: ```bash tuist project tokens create my-handle/MyApp ``` The command will generate a token for the project with full handle `my-account/my-project`. Set the value to the environment variable `TUIST_CONFIG_TOKEN` in your CI environment ensuring it's configured as a secret so it's not exposed. > [!IMPORTANT] CI ENVIRONMENT DETECTION > Tuist only uses the token when it detects it's running on a CI environment. If your CI environment is not detected, you can force the token usage by setting the environment variable `CI` to `1`. --- URL: "/es/guides/ai/mcp" LLMS_URL: "/es/guides/ai/mcp.md" title: "Model Context Protocol (MCP)" titleTemplate: ":title · AI · Guides · Tuist" description: "Learn how to use Tuist's MCP server to have a language-based interface for your app development environment." --- # Model Context Protocol (MCP) [Model Context Protocol (MCP)](https://www.claudemcp.com) is a standard proposed by [Claude](https://claude.ai) for LLMs to interact with development environments. You can think of it as the USB-C of LLMs. Like shipping containers, which made cargo and transportation more interoperable, or protocols like TCP, which decoupled the application layer from the transport layer, MCP makes LLM-powered applications such as [Claude](https://claude.ai/) and editors like [Zed](https://zed.dev) or [Cursor](https://www.cursor.com) interoperable with other domains. Tuist provides a local server through its CLI so that you can interact with your **app development environment**. By connecting your client apps to it, you can use language to interact with your projects. In this page you'll learn about how to set it up and its capabilities. > [!NOTE] > Tuist MCP server uses Xcode's most-recent projects as the source of truth for projects you want to interact with. ## Set it up ### [Claude](https://claude.ai) If you are using [Claude desktop](https://claude.ai/download), you can run the tuist mcp setup claude command to configure your Claude environment. Alternatively, can manually edit the file at `~/Library/Application\ Support/Claude/claude_desktop_config.json`, and add the Tuist MCP server: :::code-group ```json [Global Tuist installation (e.g. Homebrew)] { "mcpServers": { "tuist": { "command": "tuist", "args": ["mcp", "start"] } } } ``` ```json [Mise installation] { "mcpServers": { "tuist": { "command": "mise", "args": ["x", "tuist@latest", "--", "tuist", "mcp", "start"] // Or tuist@x.y.z to fix the version } } } ``` ::: ## Capabilities In the following sections you'll learn about the capabilities of the Tuist MCP server. ### Recursos #### Recent projects and workspaces Tuist keeps a record of the Xcode projects and workspaces you’ve recently worked with, giving your application access to their dependency graphs for powerful insights. You can query this data to uncover details about your project structure and relationships, such as: - What are the direct and transitive dependencies of a specific target? - Which target has the most source files, and how many does it include? - What are all the static products (e.g., static libraries or frameworks) in the graph? - Can you list all targets, sorted alphabetically, along with their names and product types (e.g., app, framework, unit test)? - Which targets depend on a particular framework or external dependency? - What’s the total number of source files across all targets in the project? - Are there any circular dependencies between targets, and if so, where? - Which targets use a specific resource (e.g., an image or plist file)? - What’s the deepest dependency chain in the graph, and which targets are involved? - Can you show me all the test targets and their associated app or framework targets? - Which targets have the longest build times based on recent interactions? - What are the differences in dependencies between two specific targets? - Are there any unused source files or resources in the project? - Which targets share common dependencies, and what are they? With Tuist, you can dig into your Xcode projects like never before, making it easier to understand, optimize, and manage even the most complex setups! --- URL: "/es/contributors/translate" LLMS_URL: "/es/contributors/translate.md" title: "Translate" titleTemplate: ":title · Contributors · Tuist" description: "This document describes the principles that guide the development of Tuist." --- # Translate {#translate} Languages can be barriers to understanding. We want to make sure that Tuist is accessible to as many people as possible. If you speak a language that Tuist doesn't support, you can help us by translating the various surfaces of Tuist. Since maintaining translations is a continuous effort, we add languages as we see contributors willing to help us maintain them. The following languages are currently supported: - English - Korean - Japanese - Russian > [!TIP] REQUEST A NEW LANGUAGE > If you believe Tuist would benefit from supporting a new language, please create a new [topic in the community forum](https://community.tuist.io/c/general/4) to discuss it with the community. ## How to translate We use [Crowdin](https://crowdin.com/) to manage the translations. First, go to the project that you want to contribute to: - [Documentation](https://crowdin.com/project/tuist-documentation) - [Website](https://crowdin.com/project/tuist-documentation) You'll need an account to start translating. You can sign in with GitHub. Once you have access, you can start translating. You'll see the list of resources that are available for translation. When you click on a resource, the editor will open, and you'll see a split view with the resource in the source language on the left and the translation on the right. Translate the text on the right and save your changes. As translations are updated, Crowdin will push them automatically to the right repository opening a pull request, which the maintainers will review and merge. > [!IMPORTANT] DON'T MODIFY THE RESOURCES IN THE TARGET LANGUAGE > Crowdin segments the files to bind source and target languages. If you modify the source language, you'll break the binding, and the reconciliation might yield unexpected results. ## Guidelines The following are the guidelines we follow when translating. ### Custom containers and GitHub alerts When translating [custom containers](https://vitepress.dev/guide/markdown#custom-containers) or [GitHub Alerts](https://docs.github.com/es/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts), only translate the title and the content **but not the type of alert**. :::details Example with GitHub Alert ````markdown > [!WARNING] 루트 변수 > 매니페스트의 루트에 있어야 하는 변수는... // Instead of > [!주의] 루트 변수 > 매니페스트의 루트에 있어야 하는 변수는... ``` ::: ::: details Example with custom container ```markdown ::: warning 루트 변수\ 매니페스트의 루트에 있어야 하는 변수는... ::: # Instead of ::: 주의 루트 변수\ 매니페스트의 루트에 있어야 하는 변수는... ::: ```` ::: ### Heading titles When translating headings, only translate tht title but not the id. For example, when translating the following heading: ```markdown # Add dependencies {#add-dependencies} ``` It should be translated as (note the id is not translated): ```markdown # 의존성 추가하기 {#add-dependencies} ``` --- URL: "/es/contributors/principles" LLMS_URL: "/es/contributors/principles.md" title: "Principles" titleTemplate: ":title · Contributors · Tuist" description: "This document describes the principles that guide the development of Tuist." --- # Principles {#principles} This page describes principles that are pillars to the design and development of Tuist. They evolve with the project and are meant to ensure a sustainable growth that is well-aligned with the project foundation. ## Default to conventions {#default-to-conventions} One of the reasons why Tuist exists is because Xcode is weak in conventions and that leads to complex projects that are hard to scale up and maintain. For that reason, Tuist takes a different approach by defaulting to simple and thoroughly designed conventions. **Developers can opt-out from the conventions, but that’s a conscious decision that doesn’t feel natural.** For example, there’s a convention for defining dependencies between targets by using the provided public interface. By doing that, Tuist ensures that the projects are generated with the right configurations for the linking to work. Developers have the option to define the dependencies through build settings, but they’d be doing it implicitly and therefore breaking Tuist features such as `tuist graph` or `tuist cache` that rely on some conventions being followed. The reason why we default to conventions is that the more decision we can make on behalf of the developers, the more focus they’ll have crafting features for their apps. When we are left with no conventions like it’s the case in many projects, we have to make decisions that will end up not being consistent with other decisions and as a consequence, there’ll be an accidental complexity that will be hard to manage. ## Manifests are the source of truth {#manifests-are-the-source-of-truth} Having many layers of configurations and contracts between them results in a project setup that is hard to reason about and maintain. Think for a second on an average project. The definition of the project lives in the `.xcodeproj` directories, the CLI in scripts (e.g `Fastfiles`), and the CI logic in pipelines. Those are three layers with contracts between them that we need to maintain. _How often have you been in a situation where you changed something in your projects, and then a week later you realized that the release scripts broke?_ We can simplify this by having a single source of truth, the manifest files. Those files provide Tuist with the information that it needs to generate Xcode projects that developers can use to edit their files. Moreover, it allows having standard commands for building projects from a local or CI environment. **Tuist should own the complexity and expose a simple, safe, and enjoyable interface to describe their projects as explicitly as possible.** ## Make the implicit explicit {#make-the-implicit-explicit} Xcode supports implicit configurations. A good example of that is inferring the implicitly defined dependencies. While implicitness is fine for small projects, where configurations are simple, as projects get larger it might cause slowness or odd behaviors. Tuist should provide explicit APIs for implicit Xcode behaviors. It should also support defining Xcode implicitness but implemented in such a way that encourages developers to opt for the explicit approach. Supporting Xcode implicitness and intricacies facilitates the adoption of Tuist, after which teams can take some time to get rid of the implicitness. The definition of dependencies is a good example of that. While developers can define dependencies through build settings and phases, Tuist provides a beautiful API that encourages its adoption. **Designing the API to be explicit allows Tuist to run some checks and optimizations on the projects that otherwise wouldn’t be possible.** Moreover, it enables features like `tuist graph`, which exports a representation of the dependency graph, or `tuist cache`, which caches all the targets as binaries. > [!TIP] > We should treat each request to port features from Xcode as an opportunity to simplify concepts with simple and explicit APIs. ## Keep it simple {#keep-it-simple} One of the main challenges when scaling Xcode projects comes from the fact that **Xcode exposes a lot of complexity to the users.** Due to that, teams have a high bus factor and only a few people in the team understand the project and the errors that the build system throws. That’s a bad situation to be in because the team relies on a few people. Xcode is a great tool, but so many years of improvements, new platforms, and programming languages, are reflected on their surface, which struggled to remain simple. Tuist should take the opportunity to keep things simple because working on simple things is fun and motivates us. No one wants to spend time trying to debug an error that happens at the very end of the compilation process, or understanding why they are not able to run the app on their devices. Xcode delegates the tasks to its underlying build system and in some cases it does a very poor job translating errors into actionable items. Have you ever got a _“framework X not found”_ error and you didn’t know what to do? Imagine if we got a list of potential root causes for the bug. ## Start from the developer’s experience {#start-from-the-developers-experience} Part of the reason why there is a lack of innovation around Xcode, or put differently, not as much as in other programming environments, is because **we often start analyzing problems from existing solutions.** As a consequence, most of the solutions that we find nowadays revolve around the same ideas and workflows. While it’s good to include existing solutions in the equations, we should not let them constrain our creativity. We like to think as [Tom Preston](https://tom.preston-werner.com/) puts it in [this podcast](https://tom.preston-werner.com/): _“Most things can be achieved, whatever you have in your head you can probably pull off with code as long as is possible within the constrains of the universe”._ If **we imagine how we’d like the developer experience to be**, it’s just a matter of time to pull it off — by starting to analyze the problems from the developer experience gives us a unique point of view that will lead us to solutions that users will love to use. We might feel tempted to follow what everyone is doing, even if that means sticking with the inconveniences that everyone continues to complain about. Let’s not do that. How do I imagine archiving my app? How would I love code signing to be? What processes can I help streamline with Tuist? For example, adding support for [Fastlane](https://fastlane.tools/) is a solution to a problem that we need to understand first. We can get to the root of the problem by asking “why” questions. Once we narrow down where the motivation comes from, we can think of how Tuist can help them best. Maybe the solution is integrating with Fastlane, but it’s important we don’t disregard other equally valid solutions that we can put on the table before making trade-offs. ## Errors can and will happen {#errors-can-and-will-happen} We, developers, have an inherent temptation to disregard that errors can happen. As a result, we design and test software only considering the ideal scenario. Swift, its type system, and a well-architected code might help prevent some errors, but not all of them because some are out of our control. We can’t assume the user will always have an internet connection, or that the system commands will return successfully. The environments in which Tuist runs are not sandboxes that we control, and hence we need to make an effort to understand how they might change and impact Tuist. Poorly handled errors result in bad user experience, and users might lose trust in the project. We want users to enjoy every single piece of Tuist, even the way we present errors to them. We should put ourselves in the shoes of users and imagine what we’d expect the error to tell us. If the programming language is the communication channel through which errors propagate, and the users are the destination of the errors, they should be written in the same language that the target (users) speak. They should include enough information to know what happened and hide the information that is not relevant. Also, they should be actionable by telling users what steps they can take to recover from them. And last but not least, our test cases should contemplate failing scenarios. Not only they ensure that we are handling errors as we are supposed to, but prevent future developers from breaking that logic. --- URL: "/es/contributors/issue-reporting" LLMS_URL: "/es/contributors/issue-reporting.md" title: "Issue reporting" titleTemplate: ":title · Contributors · Tuist" description: "Learn how to contribute to Tuist by reporting bugs" --- # Issue reporting {#issue-reporting} As user of Tuist, you might come across bugs or unexpected behaviors. If you do, we encourage you to report them so that we can fix them. ## GitHub issues is our ticketing platform {#github-issues-is-our-ticketing-platform} Issues should be reported on GitHub as [GitHub issues](https://github.com/tuist/tuist/issues) and not on Slack or other platforms. GitHub is better for tracing and managing issues, is closer to the codebase, and allows us to track the progress of the issue. Moreover, it encourages a long-form description of the problem, which forces the reporter to think about the problem and provide more context. ## Context is crucial {#context-is-crucial} An issue without enough context will be deemed incomplete and the author will be asked for additional context. If not provided, the issue will be closed. Think about it this way: the more context you provide, the easier it is for us to understand the problem and fix it. So if you want your issue to be fixed, provide as much context as possible. Try to answer the following questions: - What were you trying to do? - How does your graph look? - What version of Tuist are you using? - Is this blocking you? We also require you to provide a minimal **reproducible project**. ## Reproducible project {#reproducible-project} ### What is a reproducible project? {#what-is-a-reproducible-project} A reproducible project is a small Tuist project to demonstrate a problem - often this problem is caused by a bug in Tuist. Your reproducible project should contain the bare minimum features needed to clearly demonstrate the bug. ### Why should you create a reproducible test case? {#why-should-you-create-a-reproducible-test-case} A reproducible projects lets us isolate the cause of a problem, which is the first step towards fixing it! The most important part of any bug report is to describe the exact steps needed to reproduce the bug. A reproducible project is a great way to share a specific environment that causes a bug. Your reproducible project is the best way to help people that want to help you. ### Steps to create a reproducible project {#steps-to-create-a-reproducible-project} - Create a new git repository. - Initialize a project using `tuist init` in the repository directory. - Add the code needed to recreate the error you’ve seen. - Publish the code (your GitHub account is a good place to do this) and then link to it when creating an issue. ### Benefits of reproducible projects {#benefits-of-reproducible-projects} - **Smaller surface area:** By removing everything but the error, you don’t have to dig to find the bug. - **No need to publish secret code:** You might not be able to publish your main site (for many reasons). Remaking a small part of it as a reproducible test case allows you to publicly demonstrate a problem without exposing any secret code. - **Proof of the bug:** Sometimes a bug is caused by some combination of settings on your machine. A reproducible test case allows contributors to pull down your build and test it on their machines as well. This helps verify and narrow down the cause of a problem. - **Get help with fixing your bug:** If someone else can reproduce your problem, they often have a good chance of fixing the problem. It’s almost impossible to fix a bug without first being able to reproduce it. --- URL: "/es/contributors/get-started" LLMS_URL: "/es/contributors/get-started.md" title: "Get started" titleTemplate: ":title · Contributors · Tuist" description: "Get started contributing to Tuist by following this guide." --- # Get started {#get-started} If you have experience building apps for Apple platforms, like iOS, adding code to Tuist shouldn’t be much different. There are two differences compared to developing apps that are worth mentioning: - **The interactions with CLIs happen through the terminal.** The user executes Tuist, which performs the desired task, and then returns successfully or with a status code. During the execution, the user can be notified by sending output information to the standard output and standard error. There are no gestures, or graphical interactions, just the user intent. - **There’s no runloop that keeps the process alive waiting for input**, like it happens in an iOS app when the app receives system or user events. CLIs run in its process and finishes when the work is done. Asynchronous work can be done using system APIs like [DispatchQueue](https://developer.apple.com/documentation/dispatch/dispatchqueue) or [structured concurrency](https://developer.apple.com/tutorials/app-dev-training/managing-structured-concurrency), but need to make sure the process is running while the asynchronous work is being executed. Otherwise, the process will terminate the asynchronous work. If you don’t have any experience with Swift, we recommend [Apple’s official book](https://docs.swift.org/swift-book/) to get familiar with the language and the most used elements from the Foundation’s API. ## Minimum requirements {#minimum-requirements} To contribute to Tuist, minimum requirements are: - macOS 14.0+ - Xcode 16.3+ ## Set up the project locally {#set-up-the-project-locally} To start working on the project, we can follow the steps below: - Clone the repository by running: `git clone git@github.com:tuist/tuist.git` - [Install](https://mise.jdx.dev/getting-started.html) Mise to provision the development environment. - Run `mise install` to install the system dependencies needed by Tuist - Run `tuist install` to install the external dependencies needed by Tuist - (Optional) Run `tuist auth login` to get access to the Tuist Cache - Run `tuist generate` to generate the Tuist Xcode project using Tuist itself **The generated project opens automatically**. If you need to open again without generating it, run open `Tuist.xcworkspace` (or use Finder). > [!NOTE] XED . > If you try to open the project using `xed .`, it will open the package, and not the project generated by Tuist. We recommend using the Tuist-generated project to dog-food the tool. ## Edit the project {#edit-the-project} If you needed to edit the project, for example to add dependencies or adjust targets, you can use the `tuist edit` command. This is barely used, but it's good to know that it exists. ## Run Tuist {#run-tuist} ### From Xcode {#from-xcode} To run `tuist` from the generated Xcode project, edit the `tuist` scheme, and set the arguments that you'd like to pass to the command. For example, to run the `tuist generate` command, you can set the arguments to `generate --no-open` to prevent the project from opening after the generation. ![An example of a scheme configuration to run the generate command with Tuist](/images/contributors/scheme-arguments.png) You'll also have to set the working directory to the root of the project being generated. You can do that either by using the `--path` argument, which all the commands accept, or configuring the working directory in the scheme as shown below: ![An example of how to set the working directory to run Tuist](/images/contributors/scheme-working-directory.png) > [!WARNING] PROJECTDESCRIPTION COMPILATION > The `tuist` CLI depends on the `ProjectDescription` framework's presence in the built products directory. If `tuist` fails to run because it can't find the `ProjectDescription` framework, build the `Tuist-Workspace` scheme first. ### From the terminal {#from-the-terminal} You can run `tuist` using Tuist itself through its `run` command: ```bash tuist run tuist generate --path /path/to/project --no-open ``` Alternatively, you can also run it through the Swift Package Manager directly: ```bash swift build --product ProjectDescription swift run tuist generate --path /path/to/project --no-open ``` --- URL: "/es/contributors/code-reviews" LLMS_URL: "/es/contributors/code-reviews.md" title: "Code reviews" titleTemplate: ":title · Contributors · Tuist" description: "Learn how to contribute to Tuist by reviewing code" --- # Code reviews {#code-reviews} La revisión de PRs es una forma típica de contribuir con el proyecto. A pesar de la integración continua (CI) validando que el código hace lo que se supone que tiene que hacer, no es suficiente. Hay rasgos de contribución que no se pueden automatizar: diseño, estructura de código y arquitectura, calidad de las pruebas, o errores tipográficos. Las siguientes secciones representan diferentes aspectos del proceso de revisión de código. ## Legibilidad {#readability} ¿Expresa el código claramente su intención? **Si necesitas dedicar un montón de tiempo a averiguar lo que hace el código, es necesario mejorar la implementación de código.** Sugiere dividir el código en abstracciones más pequeñas que son más fáciles de entender. De forma alternativa y como último recurso, pueden añadir un comentario explicando el razonamiento detrás de él. Pregúntate si serías capaz de entender el código en un futuro próximo, sin ningún contexto circundante como la descripción del pull request. ## Small pull requests {#small-pull-requests} PRs grandes son difíciles de revisar y es más probable que se escapen detalles. Si un PR acaba siendo muy grande e inmanejable, sugiere al autor que lo divida en PRs más pequeños. > [!NOTE] EXCEPCIONES > Hay pocos escenarios donde no es posible dividir el PR, como cuando los cambios están fuertemente acoplados y no pueden ser divididos. En esos casos, el autor debe dar una explicación clara de los cambios y del razonamiento que se esconden detrás de ellos. ## Consistencia {#consistency} It’s important that the changes are consistent with the rest of the project. Inconsistencies complicate maintenance, and therefore we should avoid them. If there’s an approach to output messages to the user, or report errors, we should stick to that. If the author disagrees with the project’s standards, suggest them to open an issue where we can discuss them further. ## Tests {#tests} Tests allow changing code with confidence. The code on pull requests should be tested, and all tests should pass. A good test is a test that consistently produces the same result and that it’s easy to understand and maintain. Reviewers spend most of the review time in the implementation code, but tests are equally important because they are code too. ## Breaking changes {#breaking-changes} Breaking changes are a bad user experience for users of Tuist. Contributions should avoid introducing breaking changes unless it’s strictly necessary. There are many language features that we can leverage to evolve the interface of Tuist without resorting to a breaking change. Whether a change is breaking or not might not be obvious. A method to verify whether the change is breaking is running Tuist against the fixture projects in the fixtures directory. It requires putting ourselves in the user’s shoes and imagine how the changes would impact them. --- URL: "/es/contributors/cli/logging" LLMS_URL: "/es/contributors/cli/logging.md" title: "Logging" titleTemplate: ":title · CLI · Contributors · Tuist" description: "Learn how to contribute to Tuist by reviewing code" --- # Logging {#logging} The CLI embraces the [swift-log](https://github.com/apple/swift-log) interface for logging. The package abstracts away the implementation details of logging, allowing the CLI to be agnostic to the logging backend. The logger is dependency-injected using [swift-service-context](https://github.com/apple/swift-service-context) and can be accessed anywhere using: ```bash ServiceContext.current?.logger ``` > [!NOTE] > `swift-service-context` passes the instance using [task locals](https://developer.apple.com/documentation/swift/tasklocal) which don't propagate the value when using `Dispatch`, so if you run asynchronous code using `Dispatch`, you'll to get the instance from the context and pass it to the asynchronous operation. ## What to log {#what-to-log} Logs are not the CLI's UI. They are a tool to diagnose issues when they arise. Therefore, the more information you provide, the better. When building new features, put yourself in the shoes of a developer coming across unexpected behavior, and think about what information would be helpful to them. Ensure you you use the right [log level](https://www.swift.org/documentation/server/guides/libraries/log-levels.html). Otherwise developers won't be able to filter out the noise. --- URL: "/es/cli/shell-completions" LLMS_URL: "/es/cli/shell-completions.md" title: "Shell completions" titleTemplate: ":title · CLI · Tuist" description: "Learn how to configure your shell to auto-complete Tuist commands." --- # Shell completions If you have Tuist **globally installed** (e.g., via Homebrew), you can install shell completions for Bash and Zsh to autocomplete commands and options. :::warning WHAT IS A GLOBAL INSTALLATION A global installation is an installation that's available in your shell's `$PATH` environment variable. This means you can run `tuist` from any directory in your terminal. This is the default installation method for Homebrew. ::: #### Zsh {#zsh} If you have [oh-my-zsh](https://ohmyz.sh/) installed, you already have a directory of automatically loading completion scripts — `.oh-my-zsh/completions`. Copy your new completion script to a new file in that directory called `_tuist`: ```bash tuist --generate-completion-script > ~/.oh-my-zsh/completions/_tuist ``` Without `oh-my-zsh`, you'll need to add a path for completion scripts to your function path, and turn on completion script autoloading. First, add these lines to `~/.zshrc`: ```bash fpath=(~/.zsh/completion $fpath) autoload -U compinit compinit ``` Next, create a directory at `~/.zsh/completion` and copy the completion script to the new directory, again into a file called `_tuist`. ```bash tuist --generate-completion-script > ~/.zsh/completion/_tuist ``` #### Bash {#bash} If you have [bash-completion](https://github.com/scop/bash-completion) installed, you can just copy your new completion script to file `/usr/local/etc/bash_completion.d/_tuist`: ```bash tuist --generate-completion-script > /usr/local/etc/bash_completion.d/_tuist ``` Without bash-completion, you'll need to source the completion script directly. Copy it to a directory such as `~/.bash_completions/`, and then add the following line to `~/.bash_profile` or `~/.bashrc`: ```bash source ~/.bash_completions/example.bash ``` #### Fish {#fish} If you use [fish shell](https://fishshell.com), you can copy your new completion script to `~/.config/fish/completions/tuist.fish`: ```bash mkdir -p ~/.config/fish/completions tuist --generate-completion-script > ~/.config/fish/completions/tuist.fish ``` --- URL: "/es/cli/logging" LLMS_URL: "/es/cli/logging.md" title: "Logging" titleTemplate: ":title · CLI · Tuist" description: "Learn how to enable and configure logging in Tuist." --- # Logging {#logging} The CLI logs messages internally to help you diagnose issues. ## Diagnose issues using logs {#diagnose-issues-using-logs} If a command invocation doesn't yield the intended results, you can diagnose the issue by inspecting the logs. The CLI forwards the logs to [OSLog](https://developer.apple.com/documentation/os/oslog) and the file-system. In every run, it creates a log file at `$XDG_STATE_HOME/tuist/logs/{uuid}.log` where `$XDG_STATE_HOME` takes the value `~/.local/state` if the environment variable is not set. By default, the CLI outputs the logs path when the execution exits unexpectedly. If it doesn't, you can find the logs in the path mentioned above (i.e., the most recent log file). > [!IMPORTANT] > Sensitive information is not redacted, so be cautious when sharing logs. ### Continuous integration {#diagnose-issues-using-logs-ci} In CI, where environments are disposable, you might want to configure your CI pipeline to export Tuist logs. Exporting artifacts is a common capability across CI services, and the configuration depends on the service you use. For example, in GitHub Actions, you can use the `actions/upload-artifact` action to upload the logs as an artifact: ```yaml name: Node CI on: [push] env: XDG_STATE_HOME: /tmp jobs: build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 # ... other steps - run: tuist generate # ... do something with the project - name: Export Tuist logs uses: actions/upload-artifact@v4 with: name: tuist-logs path: /tmp/tuist/logs/*.log ``` --- URL: "/es/cli/[command]" LLMS_URL: "/es/cli/[command].md" editLink: false titleTemplate: ":title · CLI · Tuist" --- --- URL: "/en/server/on-premise/metrics" LLMS_URL: "/en/server/on-premise/metrics.md" title: "Metrics" titleTemplate: ":title | On-premise | Server | Tuist" description: "Optimize your build times by caching compiled binaries and sharing them across different environments." --- # Metrics {#metrics} You can ingest metrics gathered by the Tuist server using [Prometheus](https://prometheus.io/) and a visualization tool such as [Grafana](https://grafana.com/) to create a custom dashboard tailored to your needs. The Prometheus metrics are served via the `/metrics` endpoint on port 9091. The Prometheus' [scrape_interval](https://prometheus.io/docs/introduction/first_steps/#configuring-prometheus) should be set as less than 10_000 seconds (we recommend keeping the default of 15 seconds). ## Elixir metrics {#elixir-metrics} By default we include metrics of the Elixir runtime, BEAM, Elixir, and some of the libraries we use. The following are some of the metrics you can expect to see: - [Application](https://hexdocs.pm/prom_ex/PromEx.Plugins.Application.html) - [BEAM](https://hexdocs.pm/prom_ex/PromEx.Plugins.Beam.html) - [Phoenix](https://hexdocs.pm/prom_ex/PromEx.Plugins.Phoenix.html) - [Phoenix LiveView](https://hexdocs.pm/prom_ex/PromEx.Plugins.PhoenixLiveView.html) - [Ecto](https://hexdocs.pm/prom_ex/PromEx.Plugins.Ecto.html) - [Oban](https://hexdocs.pm/prom_ex/PromEx.Plugins.Oban.html) We recommend checking those pages to know which metrics are available and how to use them. ## Runs metrics {#runs-metrics} A set of metrics related to Tuist Runs. ### `tuist_runs_total` (counter) {#tuist_runs_total-counter} The total number of Tuist Runs. #### Tags {#tuist-runs-total-tags} | Tag | Description | |--- | ---- | | `name` | The name of the `tuist` command that was run, such as `build`, `test`, etc. | | `is_ci` | A boolean indicating if the executor was a CI or a developer's machine. | | `status` | `0` in case of `success`, `1` in case of `failure`. | ### `tuist_runs_duration_milliseconds` (histogram) {#tuist_runs_duration_milliseconds-histogram} The total duration of each tuist run in milliseconds. #### Tags {#tuist-runs-duration-miliseconds-tags} | Tag | Description | |--- | ---- | | `name` | The name of the `tuist` command that was run, such as `build`, `test`, etc. | | `is_ci` | A boolean indicating if the executor was a CI or a developer's machine. | | `status` | `0` in case of `success`, `1` in case of `failure`. | ## Cache metrics {#cache-metrics} A set of metrics related to the Tuist Cache. ### `tuist_cache_events_total` (counter) {#tuist_cache_events_total-counter} The total number of binary cache events. #### Tags {#tuist-cache-events-total-tags} | Tag | Description | |--- | ---- | | `event_type` | Can be either of `local_hit`, `remote_hit`, or `miss`. | ### `tuist_cache_uploads_total` (counter) {#tuist_cache_uploads_total-counter} The number of uploads to the binary cache. ### `tuist_cache_uploaded_bytes` (sum) {#tuist_cache_uploaded_bytes-sum} The number of bytes uploaded to the binary cache. ### `tuist_cache_downloads_total` (counter) {#tuist_cache_downloads_total-counter} The number of downloads to the binary cache. ### `tuist_cache_downloaded_bytes` (sum) {#tuist_cache_downloaded_bytes-sum} The number of bytes downloaded from the binary cache. --- ## Previews metrics {#previews-metrics} A set of metrics related to the previews feature. ### `tuist_previews_uploads_total` (sum) {#tuist_previews_uploads_total-counter} The total number of previews uploaded. ### `tuist_previews_downloads_total` (sum) {#tuist_previews_downloads_total-counter} The total number of previews downloaded. --- ## Storage metrics {#storage-metrics} A set of metrics related to the storage of artifacts in a remote storage (e.g. s3). > [!TIP] > These metrics are useful to understand the performance of the storage operations and to identify potential bottlenecks. ### `tuist_storage_get_object_size_size_bytes` (histogram) {#tuist_storage_get_object_size_size_bytes-histogram} The size (in bytes) of an object fetched from the remote storage. #### Tags {#tuist-storage-get-object-size-size-bytes-tags} | Tag | Description | |--- | ---- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_size_duration_miliseconds` (histogram) {#tuist_storage_get_object_size_duration_miliseconds-histogram} The duration (in milliseconds) of fetching an object size from the remote storage. #### Tags {#tuist-storage-get-object-size-duration-miliseconds-tags} | Tag | Description | |--- | ---- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_size_count` (counter) {#tuist_storage_get_object_size_count-counter} The number of times an object size was fetched from the remote storage. #### Tags {#tuist-storage-get-object-size-count-tags} | Tag | Description | |--- | ---- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_delete_all_objects_duration_milliseconds` (histogram) {#tuist_storage_delete_all_objects_duration_milliseconds-histogram} The duration (in milliseconds) of deleting all objects from the remote storage. #### Tags {#tuist-storage-delete-all-objects-duration-milliseconds-tags} | Tag | Description | |--- | ---- | | `project_slug` | The project slug of the project whose objects are being deleted. | ### `tuist_storage_delete_all_objects_count` (counter) {#tuist_storage_delete_all_objects_count-counter} The number of times all project objects were deleted from the remote storage. #### Tags {#tuist-storage-delete-all-objects-count-tags} | Tag | Description | |--- | ---- | | `project_slug` | The project slug of the project whose objects are being deleted. | ### `tuist_storage_multipart_start_upload_duration_milliseconds` (histogram) {#tuist_storage_multipart_start_upload_duration_milliseconds-histogram} The duration (in milliseconds) of starting an upload to the remote storage. #### Tags {#tuist-storage-multipart-start-upload-duration-milliseconds-tags} | Tag | Description | |--- | ---- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_multipart_start_upload_duration_count` (counter) {#tuist_storage_multipart_start_upload_duration_count-counter} The number of times an upload was started to the remote storage. #### Tags {#tuist-storage-multipart-start-upload-duration-count-tags} | Tag | Description | |--- | ---- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_as_string_duration_milliseconds` (histogram) {#tuist_storage_get_object_as_string_duration_milliseconds-histogram} The duration (in milliseconds) of fetching an object as a string from the remote storage. #### Tags {#tuist-storage-get-object-as-string-duration-milliseconds-tags} | Tag | Description | |--- | ---- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_get_object_as_string_count` (count) {#tuist_storage_get_object_as_string_count-count} The number of times an object was fetched as a string from the remote storage. #### Tags {#tuist-storage-get-object-as-string-count-tags} | Tag | Description | |--- | ---- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_check_object_existence_duration_milliseconds` (histogram) {#tuist_storage_check_object_existence_duration_milliseconds-histogram} The duration (in milliseconds) of checking the existence of an object in the remote storage. #### Tags {#tuist-storage-check-object-existence-duration-milliseconds-tags} | Tag | Description | |--- | ---- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_check_object_existence_count` (count) {#tuist_storage_check_object_existence_count-count} The number of times the existence of an object was checked in the remote storage. #### Tags {#tuist-storage-check-object-existence-count-tags} | Tag | Description | |--- | ---- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_generate_download_presigned_url_duration_milliseconds` (histogram) {#tuist_storage_generate_download_presigned_url_duration_milliseconds-histogram} The duration (in milliseconds) of generating a download presigned URL for an object in the remote storage. #### Tags {#tuist-storage-generate-download-presigned-url-duration-milliseconds-tags} | Tag | Description | |--- | ---- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_generate_download_presigned_url_count` (count) {#tuist_storage_generate_download_presigned_url_count-count} The number of times a download presigned URL was generated for an object in the remote storage. #### Tags {#tuist-storage-generate-download-presigned-url-count-tags} | Tag | Description | |--- | ---- | | `object_key` | The lookup key of the object in the remote storage. | ### `tuist_storage_multipart_generate_upload_part_presigned_url_duration_milliseconds` (histogram) {#tuist_storage_multipart_generate_upload_part_presigned_url_duration_milliseconds-histogram} The duration (in milliseconds) of generating a part upload presigned URL for an object in the remote storage. #### Tags {#tuist-storage-multipart-generate-upload-part-presigned-url-duration-milliseconds-tags} | Tag | Description | |--- | ---- | | `object_key` | The lookup key of the object in the remote storage. | | `part_number` | The part number of the object being uploaded. | | `upload_id` | The upload ID of the multipart upload. | ### `tuist_storage_multipart_generate_upload_part_presigned_url_count` (count) {#tuist_storage_multipart_generate_upload_part_presigned_url_count-count} The number of times a part upload presigned URL was generated for an object in the remote storage. #### Tags {#tuist-storage-multipart-generate-upload-part-presigned-url-count-tags} | Tag | Description | |--- | ---- | | `object_key` | The lookup key of the object in the remote storage. | | `part_number` | The part number of the object being uploaded. | | `upload_id` | The upload ID of the multipart upload. | ### `tuist_storage_multipart_complete_upload_duration_milliseconds` (histogram) {#tuist_storage_multipart_complete_upload_duration_milliseconds-histogram} The duration (in milliseconds) of completing an upload to the remote storage. #### Tags {#tuist-storage-multipart-complete-upload-duration-milliseconds-tags} | Tag | Description | |--- | ---- | | `object_key` | The lookup key of the object in the remote storage. | | `upload_id` | The upload ID of the multipart upload. | ### `tuist_storage_multipart_complete_upload_count` (count) {#tuist_storage_multipart_complete_upload_count-count} The total number of times an upload was completed to the remote storage. #### Tags {#tuist-storage-multipart-complete-upload-count-tags} | Tag | Description | |--- | ---- | | `object_key` | The lookup key of the object in the remote storage. | | `upload_id` | The upload ID of the multipart upload. | --- ## Projects metrics {#projects-metrics} A set of metrics related to the projects. ### `tuist_projects_total` (last_value) {#tuist_projects_total-last_value} The total number of projects. --- ## Accounts metrics {#accounts-metrics} A set of metrics related to accounts (users and organizations). ### `tuist_accounts_organizations_total` (last_value) {#tuist_accounts_organizations_total-last_value} The total number of organizations. ### `tuist_accounts_users_total` (last_value) {#tuist_accounts_users_total-last_value} The total number of users. ## Database metrics {#database-metrics} A set of metrics related to the database connection. ### `tuist_repo_pool_checkout_queue_length` (last_value) {#tuist_repo_pool_checkout_queue_length-last_value} The number of database queries that are sitting in a queue waiting to be assigned to a database connection. ### `tuist_repo_pool_ready_conn_count` (last_value) {#tuist_repo_pool_ready_conn_count-last_value} The number of database connections that are ready to be assigned to a database query. ### `tuist_repo_pool_db_connection_connected` (counter) {#tuist_repo_pool_db_connection_connected-counter} The number of connections that have been established to the database. ### `tuist_repo_pool_db_connection_disconnected` (counter) {#tuist_repo_pool_db_connection_disconnected-counter} The number of connections that have been disconnected from the database. ## HTTP metrics {#http-metrics} A set of metrics related to Tuist's interactions with other services via HTTP. ### `tuist_http_request_count` (counter) {#tuist_http_request_count-last_value} The number of outgoing HTTP requests. ### `tuist_http_request_duration_nanosecond_sum` (sum) {#tuist_http_request_duration_nanosecond_sum-last_value} The sum of the duration of the outgoing requests (including the time that they spent waiting to be assigned to a connection). ### `tuist_http_request_duration_nanosecond_bucket` (distribution) {#tuist_http_request_duration_nanosecond_bucket-distribution} The distribution of the duration of outgoing requests (including the time that they spent waiting to be assigned to a connection). ### `tuist_http_queue_count` (counter) {#tuist_http_queue_count-counter} The number of requests that have been retrieved from the pool. ### `tuist_http_queue_duration_nanoseconds_sum` (sum) {#tuist_http_queue_duration_nanoseconds_sum-sum} The time it takes to retrieve a connection from the pool. ### `tuist_http_queue_idle_time_nanoseconds_sum` (sum) {#tuist_http_queue_idle_time_nanoseconds_sum-sum} The time a connection has been idle waiting to be retrieved. ### `tuist_http_queue_duration_nanoseconds_bucket` (distribution) {#tuist_http_queue_duration_nanoseconds_bucket-distribution} The time it takes to retrieve a connection from the pool. ### `tuist_http_queue_idle_time_nanoseconds_bucket` (distribution) {#tuist_http_queue_idle_time_nanoseconds_bucket-distribution} The time a connection has been idle waiting to be retrieved. ### `tuist_http_connection_count` (counter) {#tuist_http_connection_count-counter} The number of connections that have been established. ### `tuist_http_connection_duration_nanoseconds_sum` (sum) {#tuist_http_connection_duration_nanoseconds_sum-sum} The time it takes to establish a connection against a host. ### `tuist_http_connection_duration_nanoseconds_bucket` (distribution) {#tuist_http_connection_duration_nanoseconds_bucket-distribution} The distribution of the time it takes to establish a connection against a host. ### `tuist_http_send_count` (counter) {#tuist_http_send_count-counter} The number of requests that have been sent once assigned to a connection from the pool. ### `tuist_http_send_duration_nanoseconds_sum` (sum) {#tuist_http_send_duration_nanoseconds_sum-sum} The time that it takes for requests to complete once assigned to a connection from the pool. ### `tuist_http_send_duration_nanoseconds_bucket` (distribution) {#tuist_http_send_duration_nanoseconds_bucket-distribution} The distribution of the time that it takes for requests to complete once assigned to a connection from the pool. ### `tuist_http_receive_count` (counter) {#tuist_http_receive_count-counter} The number of responses that have been received from sent requests. ### `tuist_http_receive_duration_nanoseconds_sum` (sum) {#tuist_http_receive_duration_nanoseconds_sum-sum} The time spent receiving responses. ### `tuist_http_receive_duration_nanoseconds_bucket` (distribution) {#tuist_http_receive_duration_nanoseconds_bucket-distribution} The distribution of the time spent receiving responses. ### `tuist_http_queue_available_connections` (last_value) {#tuist_http_queue_available_connections-last_value} The number of connections available in the queue. ### `tuist_http_queue_in_use_connections` (last_value) {#tuist_http_queue_in_use_connections-last_value} The number of queue connections that are in use. --- URL: "/en/server/on-premise/install" LLMS_URL: "/en/server/on-premise/install.md" title: "Installation" titleTemplate: ":title | On-premise | Server | Tuist" description: "Learn how to install Tuist on your infrastructure." --- # On-premise installation {#onpremise-installation} We offer a self-hosted version of the Tuist server for organizations that require more control over their infrastructure. This version allows you to host Tuist on your own infrastructure, ensuring that your data remains secure and private. > [!IMPORTANT] ENTERPRISE CUSTOMERS ONLY > The on-premise version of Tuist is available only for organizations on the Enterprise plan. If you are interested in this version, please reach out to [contact@tuist.dev](mailto:contact@tuist.dev). ## Release cadence {#release-cadence} The Tuist server is **released every Monday** and the version name follows the convention name `{MAJOR}.YY.MM.DD`. The date component is used to warn the CLI user if their hosted version is 60 days older than the release date of the CLI. It's crucial that on-premise organizations keep up with Tuist updates to ensure their developers benefit from the most recent improvements and that we can drop deprecated features with the confidence that we are not breaking any of the on-premise setups. The major component of the CLI is used to flag breaking changes in the Tuist server that will require coordination with the on-premise users. You should not expect us to use it, and in case we needed, rest asure we'll work with you in making the transition smooth. > [!NOTE] RELEASE NOTES > You'll be given access to a `tuist/registry` repository associated with the registry where images are published. Every new released will be published in that repository as a GitHub release and will contain release notes to inform you about what changes come with it. ## Runtime requirements {#runtime-requirements} This section outlines the requirements for hosting the Tuist server on your infrastructure. ### Running Docker-virtualized images {#running-dockervirtualized-images} We distribute the server as a [Docker](https://www.docker.com/) image via [GitHub’s Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry). To run it, your infrastructure must support running Docker images. Note that most infrastructure providers support it because it’s become the standard container for distributing and running software in production environments. ### Postgres database {#postgres-database} In addition to running the Docker images, you’ll need a [Postgres database](https://www.postgresql.org/) to store relational data. Most infrastructure providers include Posgres databases in their offering (e.g., [AWS](https://aws.amazon.com/rds/postgresql/) & [Google Cloud](https://cloud.google.com/sql/docs/postgres)). For performant analytics, we use a [Timescale Postgres extension](https://www.timescale.com/). You need to make sure that TimescaleDB is installed on the machine running the Postgres database. Follow the installation instructions [here](https://docs.timescale.com/self-hosted/latest/install/) to learn more. If you are unable to install the Timescale extension, you can set up your own dashboard using the Prometheus metrics. > [!INFO] MIGRATIONS > The Docker image's entrypoint automatically runs any pending schema migrations before starting the service. ### Storage {#storage} You’ll also need a solution to store files (e.g. framework and library binaries). Currently we support any storage that's S3-compliant. ## Configuration {#configuration} The configuration of the service is done at runtime through environment variables. Given the sensitive nature of these variables, we advise encrypting and storing them in secure password management solutions. Rest assured, Tuist handles these variables with utmost care, ensuring they are never displayed in logs. > [!NOTE] LAUNCH CHECKS > The necessary variables are verified at startup. If any are missing, the launch will fail and the error message will detail the absent variables. ### License configuration {#license-configuration} As an on-premise user, you'll receive a license key that you'll need to expose as an environment variable. This key is used to validate the license and ensure that the service is running within the terms of the agreement. | Environment variable | Description | Required | Default | Example | | --- | --- | --- | --- | --- | | `TUIST_LICENSE` | The license provided after signing the service level agreement | Yes | | `******` | > [!IMPORTANT] EXPIRATION DATE > Licenses have an expiration date. Users will receive a warning while using Tuist commands that interact with the server if the license expires in less than 30 days. If you are interested in renewing your license, please reach out to [contact@tuist.dev](mailto:contact@tuist.dev). ### Base environment configuration {#base-environment-configuration} | Environment variable | Description | Required | Default | Example | | --- | --- | --- | --- | --- | | `TUIST_APP_URL` | The base URL to access the instance from the Internet | Yes | | https://cloud.tuist.io | | `TUIST_SECRET_KEY_BASE` | The key to use to encrypt information (e.g. sessions in a cookie) | Yes | | | `c5786d9f869239cbddeca645575349a570ffebb332b64400c37256e1c9cb7ec831345d03dc0188edd129d09580d8cbf3ceaf17768e2048c037d9c31da5dcacfa` | | `TUIST_SECRET_KEY_PASSWORD` | Pepper to generate hashed passwords | No | `$TUIST_SECRET_KEY_BASE` | | | `TUIST_SECRET_KEY_TOKENS` | Secret key to generate random tokens | No | `$TUIST_SECRET_KEY_BASE` | | | `TUIST_USE_IPV6` | When `1` it configures the app to use IPv6 addresses | No | `0` | `1`| | `TUIST_LOG_LEVEL` | The log level to use for the app | No | `info` | [Log levels](https://hexdocs.pm/logger/1.12.3/Logger.html#module-levels) | | `TUIST_GITHUB_APP_PRIVATE_KEY` | The private key used for the GitHub app to unlock extra functionality such as posting automatic PR comments | No | `-----BEGIN RSA...` | | | `TUIST_OPS_USER_HANDLES` | A comma-separated list of user handles that have access to the operations URLs | No | | `user1,user2` | ### Database configuration {#database-configuration} The following environment variables are used to configure the database connection: | Environment variable | Description | Required | Default | Example | | --- | --- | --- | --- | --- | | `DATABASE_URL` | The URL to access the Postgres database. Note that the URL should contain the authentication information | Yes | | `postgres://username:password@cloud.us-east-2.aws.test.com/production` | | `TUIST_USE_SSL_FOR_DATABASE` | When true, it uses [SSL](https://en.wikipedia.org/wiki/Transport_Layer_Security) to connect to the database | No | `1` | `1` | | `TUIST_DATABASE_POOL_SIZE` | The number of connections to keep open in the connection pool | No | `10` | `10` | | `TUIST_DATABASE_QUEUE_TARGET` | The interval (in miliseconds) for checking if all the connections checked out from the pool took more than the queue interval [(More information)](https://hexdocs.pm/db_connection/DBConnection.html#start_link/2-queue-config) | No | `300` | `300` | | `TUIST_DATABASE_QUEUE_INTERVAL` | The threshold time (in miliseconds) in the queue that the pool uses to determine if it should start dropping new connections [(More information)](https://hexdocs.pm/db_connection/DBConnection.html#start_link/2-queue-config) | No | `1000` | `1000` | ### Authentication environment configuration {#authentication-environment-configuration} We facilitate authentication through [identity providers (IdP)](https://en.wikipedia.org/wiki/Identity_provider). To utilize this, ensure all necessary environment variables for the chosen provider are present in the server's environment. **Missing variables** will result in Tuist bypassing that provider. #### GitHub {#github} We recommend authenticating using a [GitHub App](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps) but you can also use the [OAuth App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app). Make sure to include all essential environment variables specified by GitHub in the server environment. Absent variables will cause Tuist to overlook the GitHub authentication. To properly set up the GitHub app: - In the GitHub app's general settings: - Copy the `Client ID` and set it as `TUIST_GITHUB_APP_CLIENT_ID` - Create and copy a new `client secret` and set it as `TUIST_GITHUB_APP_CLIENT_SECRET` - Set the `Callback URL` as `http://YOUR_APP_URL/users/auth/github/callback`. `YOUR_APP_URL` can also be your server's IP address. - In the `Permissions and events`'s `Account permissions` section, set the `Email addresses` permission to `Read-only`. You'll then need to expose the following environment variables in the environment where the Tuist server runs: | Environment variable | Description | Required | Default | Example | | --- | --- | --- | --- | --- | | `TUIST_GITHUB_APP_CLIENT_ID` | The client ID of the GitHub application | Yes | | `Iv1.a629723000043722` | | `TUIST_GITHUB_APP_CLIENT_SECRET` | The client secret of the application | Yes | | `232f972951033b89799b0fd24566a04d83f44ccc` | #### Google {#google} You can set up authentication with Google using [OAuth 2](https://developers.google.com/identity/protocols/oauth2). For that, you'll need to create a new credential of type OAuth client ID. When creating the credentials, select "Web Application" as application type, name it `Tuist`, and set the redirect URI to `{base_url}/users/auth/google/callback` where `base_url` is the URL your hosted-service is running at. Once you create the app, copy the client ID and secret and set them as environment variables `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` respectively. > [!NOTE] CONSENT SCREEN SCOPES > You might need to create a consent screen. When you do so, make sure to add the `userinfo.email` and `openid` scopes and mark the app as internal. #### Okta {#okta} You can enable authentication with Okta through the [OAuth 2.0](https://oauth.net/2/) protocol. You'll have to [create an app](https://developer.okta.com/docs/en/guides/implement-oauth-for-okta/main/#create-an-oauth-2-0-app-in-okta) on Okta with the following configuration: - **App integration name:** `Tuist` - **Grant type:** Enable *Authorization Code* for *Client acting on behalf of a user* - **Sign-in redirect URL:** `{url}/users/auth/okta/callback` where `url` is the public URL your service is accessed through. - **Assignments:** This configuration will depend on your security team requirements. Once the app is created you'll need to set the following environment variables: | Environment variable | Description | Required | Default | Example | | --- | --- | --- | --- | --- | | `TUIST_OKTA_SITE` | The URL of your Okta organization | Yes | | `https://your-org.okta.com` | | `TUIST_OKTA_CLIENT_ID` | The client ID to authenticate against Okta | Yes | | | | `TUIST_OKTA_CLIENT_SECRET` | The client secret to authenticate against Okta | Yes | | | ### Storage environment configuration {#storage-environment-configuration} Tuist needs storage to house artifacts uploaded through the API. It's **essential to configure one of the supported storage solutions** for Tuist to operate effectively. #### S3-compliant storages {#s3compliant-storages} You can use any S3-compliant storage provider to store artifacts. The following environment variables are required to authenticate and configure the integration with the storage provider: | Environment variable | Description | Required | Default | Example | | --- | --- | --- | --- | --- | | `TUIST_ACCESS_KEY_ID` or `AWS_ACCESS_KEY_ID` | The access key ID to authenticate against the storage provider | Yes | | `AKIAIOSFOD` | | `TUIST_SECRET_ACCESS_KEY` or `AWS_SECRET_ACCESS_KEY` | The secret access key to authenticate against the storage provider | Yes | | `wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY` | | `TUIST_S3_REGION` or `AWS_REGION` | The region where the bucket is located | Yes | | `us-west-2` | | `TUIST_S3_ENDPOINT` or `AWS_ENDPOINT` | The endpoint of the storage provider | Yes | | `https://s3.us-west-2.amazonaws.com` | | `TUIST_S3_BUCKET_NAME` | The name of the bucket where the artifacts will be stored | Yes | | `tuist-artifacts` | | `TUIST_S3_REQUEST_TIMEOUT` | The timeout (in seconds) for requests to the storage provider | No | `30` | `30` | | `TUIST_S3_POOL_TIMEOUT` | The timeout (in seconds) for the connection pool to the storage provider | No | `5` | `5` | | `TUIST_S3_POOL_COUNT` | The number of pools to use for connections to the storage provider | No | `1` | `1` | | `TUIST_S3_PROTOCOL` | The protocol to use when connecting to the storage provider (`http1` or `http2`) | No | `http2` | `http2` | | `TUIST_S3_VIRTUAL_HOST` | Whether the URL should be constructed with the bucket name as a sub-domain (virtual host). | No | No | `1` | > [!NOTE] AWS authentication with Web Identity Token from environment variables > If your storage provider is AWS and you'd like to authenticate using a web identity token, you can set the environment variable `TUIST_S3_AUTHENTICATION_METHOD` to `aws_web_identity_token_from_env_vars`, and Tuist will use that method using the conventional AWS environment variables. #### Google Cloud Storage {#google-cloud-storage} For Google Cloud Storage, follow [these docs](https://cloud.google.com/storage/docs/authentication/managing-hmackeys) to get the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` pair. The `AWS_ENDPOINT` should be set to `https://storage.googleapis.com`. Other environment variables are the same as for any other S3-compliant storage. ### Git platform configuration {#git-platform-configuration} Tuist can integrate with Git platforms to provide extra features such as automatically posting comments in your pull requests. #### GitHub {#platform-github} You will need to [create a GitHub app](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps). You can reuse the one you created for authentication, unless you created an OAuth GitHub app. In the `Permissions and events`'s `Repository permissions` section, you will need to additionally set the `Pull requests` permission to `Read and write`. On top of the `TUIST_GITHUB_APP_CLIENT_ID` and `TUIST_GITHUB_APP_CLIENT_SECRET`, you will need the following environment variables: | Environment variable | Description | Required | Default | Example | | --- | --- | --- | --- | --- | | `TUIST_GITHUB_APP_PRIVATE_KEY` | The private key of the GitHub application | Yes | | `-----BEGIN RSA PRIVATE KEY-----...` | ## Deployment {#deployment} On-premise users are granted access to the repository located at [tuist/registry](https://github.com/cloud/registry) which has a linked container registry for pulling images. Currently, the container registry allows authentication only as an individual user. Therefore, users with repository access must generate a **personal access token** within the Tuist organization, ensuring they have the necessary permissions to read packages. After submission, we will promptly approve this token. > [!IMPORTANT] USER VS ORGANIZATION-SCOPED TOKENS > Using a personal access token presents a challenge because it's associated with an individual who might eventually depart from the enterprise organization. GitHub recognizes this limitation and is actively developing a solution to allow GitHub apps to authenticate with app-generated tokens. ### Pulling the Docker image {#pulling-the-docker-image} After generating the token, you can retrieve the image by executing the following command: ```bash echo $TOKEN | docker login ghcr.io -u USERNAME --password-stdin docker pull ghcr.io/tuist/tuist:latest ``` ### Deploying the Docker image {#deploying-the-docker-image} The deployment process for the Docker image will differ based on your chosen cloud provider and your organization's continuous deployment approach. Since most cloud solutions and tools, like [Kubernetes](https://kubernetes.io/), utilize Docker images as fundamental units, the examples in this section should align well with your existing setup. We recommend establishing a deployment pipeline that that runs **every Tuesday**, pulling and deploying fresh images. This ensures you consistently benefit from the latest improvements. > [!IMPORTANT] > If your deployment pipeline needs to validate that the server is up and running, you can send a `GET` HTTP request to `/ready` and assert a `200` status code in the response. #### Fly {#fly} To deploy the app on [Fly](https://fly.io/), you'll require a `fly.toml` configuration file. Consider generating it dynamically within your Continuous Deployment (CD) pipeline. Below is a reference example for your use: ```toml app = "tuist" primary_region = "fra" kill_signal = "SIGINT" kill_timeout = "5s" [experimental] auto_rollback = true [env] # Your environment configuration goes here # Or exposed through Fly secrets [processes] app = "/usr/local/bin/hivemind /app/Procfile" [[services]] protocol = "tcp" internal_port = 8080 auto_stop_machines = false auto_start_machines = false processes = ["app"] http_options = { h2_backend = true } [[services.ports]] port = 80 handlers = ["http"] force_https = true [[services.ports]] port = 443 handlers = ["tls", "http"] [services.concurrency] type = "connections" hard_limit = 100 soft_limit = 80 [[services.http_checks]] interval = 10000 grace_period = "10s" method = "get" path = "/ready" protocol = "http" timeout = 2000 tls_skip_verify = false [services.http_checks.headers] [[statics]] guest_path = "/app/public" url_prefix = "/" ``` Then you can run `fly launch --local-only --no-deploy` to launch the app. On subsequent deploys, instead of running `fly launch --local-only`, you will need to run `fly deploy --local-only`. Fly.io doesn't allow to pull private Docker images, which is why we need to use the `--local-only` flag. ### Docker Compose {#docker-compose} Below is an example of a `docker-compose.yml` file that you can use as a reference to deploy the service: ```yaml version: '3.8' services: db: image: timescale/timescaledb-ha:pg16 restart: always environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - PGDATA=/var/lib/postgresql/data/pgdata ports: - '5432:5432' volumes: - db:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 pgweb: container_name: pgweb restart: always image: sosedoff/pgweb ports: - "8081:8081" links: - db:db environment: PGWEB_DATABASE_URL: postgres://postgres:postgres@db:5432/postgres?sslmode=disable depends_on: - db tuist: image: ghcr.io/tuist/tuist:latest container_name: tuist depends_on: - db ports: - "80:80" - "8080:8080" - "443:443" expose: - "80" - "8080" - "443:443" environment: # Base Tuist Env - https://docs.tuist.io/en/guides/dashboard/on-premise/install#base-environment-configuration TUIST_USE_SSL_FOR_DATABASE: "0" TUIST_LICENSE: # ... DATABASE_URL: postgres://postgres:postgres@db:5432/postgres?sslmode=disable TUIST_APP_URL: https://localhost:8080 TUIST_SECRET_KEY_BASE: # ... WEB_CONCURRENCY: 80 # Auth - one method # GitHub Auth - https://docs.tuist.io/en/guides/dashboard/on-premise/install#github TUIST_GITHUB_OAUTH_ID: TUIST_GITHUB_APP_CLIENT_SECRET: # Okta Auth - https://docs.tuist.io/en/guides/dashboard/on-premise/install#okta TUIST_OKTA_SITE: TUIST_OKTA_CLIENT_ID: TUIST_OKTA_CLIENT_SECRET: TUIST_OKTA_AUTHORIZE_URL: # Optional TUIST_OKTA_TOKEN_URL: # Optional TUIST_OKTA_USER_INFO_URL: # Optional TUIST_OKTA_EVENT_HOOK_SECRET: # Optional # Storage AWS_ACCESS_KEY_ID: # ... AWS_SECRET_ACCESS_KEY: # ... AWS_S3_REGION: # ... AWS_ENDPOINT: # https://amazonaws.com TUIST_S3_BUCKET_NAME: # ... # Other volumes: db: driver: local ``` ## Operations {#operations} Tuist provides a set of utilities under `/ops/` that you can use to manage your instance. > [!IMPORTANT] Authorization > Only people whose handles are listed in the `TUIST_OPS_USER_HANDLES` environment variable can access the `/ops/` endpoints. - **Errors (`/ops/errors`):** You can view unexpected errors that ocurred in the application. This is useful for debugging and understanding what went wrong and we might ask you to share this information with us if you're facing issues. - **Dashboard (`/ops/dashboard`):** You can view a dashboard that provides insights into the application's performance and health (e.g. memory consumption, processes running, number of requests). This dashboard can be quite useful to understand if the hardware you're using is enough to handle the load. --- URL: "/en/server/introduction/why-a-server" LLMS_URL: "/en/server/introduction/why-a-server.md" title: "Why a server?" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn why Tuist has a server and how it can help scale your app development." --- # Why a server? {#why-a-server} At a certain scale, optimizing a project and developers' interactions with them require access to data that changes over time, and integrations with other internet services where teams collaborate. This is only possible with **a server that can store data in a database, process it asynchonously, and integrate it with other services.** While the role of a server is common in other ecosystems, it's not that common in app development. Teams leaned heavily on open source solutions that leveraged the capabilities of CI services to approximate the capabilities of a server. However, as the complexity of the projects and the number of developers working on them increased, the limitations of these solutions became more evident. We believe teams shouldn't have to worry about setting up and maintaining a server to scale their projects. That's why we built a server that Tuist and [Xcode projects](https://developer.apple.com/documentation/xcode/creating-an-xcode-project-for-an-app) can integrate with to scale their projects and teams. > [!TIP] GIVING YOUR PROJECTS AND WORKFLOWS SUPERPOWERS > A way of thinking about the server is as a superpower that you can give to your projects and workflows. Some superpowers like binary caching require you to have a Tuist project but others just work with vanilla Xcode projects. --- URL: "/en/server/introduction/integrations" LLMS_URL: "/en/server/introduction/integrations.md" title: "Integrations" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn how to connect Tuist to other tools and services." --- # Integrations {#integrations} We strongly believe we should meet developers where they are, and let's be honest, developers spend time outside of their coding environments, such as reviewing pull request on [GitHub](https://github.com) or communicating with their team on [Slack](https://slack.com). That's why we've built integrations with popular tools and services to make it easier for you to use Tuist in your workflows. This page lists the integrations we currently support. ## Git platforms {#git-platforms} Git repositories are the centerpiece of the vast majority of software projects out there. We integrate with your Git platform to provide Tuist insights right in your pull requests or to save you some configuration such as syncing your default branch. ### GitHub {#github} Install the [Tuist GitHub app](https://github.com/marketplace/tuist). Once installed, you will need to tell Tuist the URL of your repository, such as: ```sh tuist project update tuist/tuist --repository-url https://github.com/tuist/tuist ``` --- URL: "/en/server/introduction/authentication" LLMS_URL: "/en/server/introduction/authentication.md" title: "Authentication" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn how to authenticate with the Tuist server from the CLI." --- # Authentication {#authentication} To interact with the server, the CLI needs to authenticate the requests using [bearer authentication](https://swagger.io/docs/specification/authentication/bearer-authentication/). The CLI supports authenticating as a user or as a project. ## As a user {#as-a-user} When using the CLI locally on your machine, we recommend authenticating as a user. To authenticate as a user, you need to run the following command: ```bash tuist auth login ``` The command will take you through a web-based authentication flow. Once you authenticate, the CLI will store a long-lived refresh token and a short-lived access token under `~/.config/tuist/credentials`. Each file in the directory represents the domain you authenticated against, which by default should be `cloud.tuist.io.json`. The information stored in that directory is sensitive, so **make sure to keep it safe**. The CLI will automatically look up the credentials when making requests to the server. If the access token is expired, the CLI will use the refresh token to get a new access token. ### Organization SSO {#organization-sso} If you have a Google Workspace organization and you want any developer who signs in with the same Google hosted domain to be added to your Tuist organization, you can set it up with: ```bash tuist organization update sso my-organization --provider google --organization-id my-google-domain.com ``` For on-premise customers that have Okta set up, you can get the same behavior as for Google by running: ```bash tuist organization update sso my-organization --provider okta --organization-id my-okta-domain.com ``` > [!IMPORTANT] > You must be authenticated with Google using an email tied to the organization whose domain you are setting up. ## As a project {#as-a-project} In non-interactive environments like continuous integrations', you can't authenticate through an interactive flow. For those environments, we recommend authenticating as a project by using a project-scoped token: ```bash tuist project tokens create ``` The CLI expects the token to be defined as the environment variable `TUIST_CONFIG_TOKEN`, and the `CI=1` environment variable to be set. The CLI will use the token to authenticate the requests. > [!IMPORTANT] LIMITED SCOPE > The permissions of the project-scoped token are limited to the actions that we consider safe for projects to perform from a CI environment. We plan to document the permissions that the token has in the future. --- URL: "/en/server/introduction/accounts-and-projects" LLMS_URL: "/en/server/introduction/accounts-and-projects.md" title: "Accounts and projects" titleTemplate: ":title | Introduction | Server | Tuist" description: "Learn how to create and manage accounts and projects in Tuist." --- # Accounts and projects {#accounts-and-projects} ## Accounts {#accounts} To use the server, you'll need an account. There are two types of accounts: - **Personal account:** Those accounts are created automaticaly when you sign up and are identified by a handle that's obtained either from the identity provider (e.g. GitHub) or the first part of the email address. - **Organization account:** Those accounts are manually created and are identified by a handle that's defined by the developer. Organizations allow inviting other members to collaborate on projects. If you are familiar with [GitHub](https://github.com), the concept is similar to theirs, where you can have personal and organization accounts, and they are identified by a *handle* that's used when constructing URLs. > [!NOTE] CLI-FIRST > Most operations to manage accounts and projects are done through the CLI. We are working on a web interface that will make it easier to manage accounts and projects. You can manage the organization through the subcommands under `tuist organization`. To create a new organization account, run: ```bash tuist organization create {account-handle} ``` ## Projects {#projects} Your projects, either Tuist's or raw Xcode's, need to be integrated with your account through a remote project. Continuing with the comparison with GitHub, it's like having a local and a remote repository where you push your changes. You can use the `tuist project` to create and manage projects. Projects are identified by a full handle, which is the result of concatenating the organization handle and the project handle. For example, if you have an organization with the handle `tuist`, and a project with the handle `tuist`, the full handle would be `tuist/tuist`. The binding between the local and the remote project is done through the configuration file. If you don't have any, create it at `Tuist.swift` and add the following content: ```swift let tuist = Tuist(fullHandle: "{account-handle}/{project-handle}") // e.g. tuist/tuist ``` > [!IMPORTANT] TUIST PROJECT-ONLY FEATURES > Note that there are some features like binary caching that require you having a Tuist project. If you are using raw Xcode projects, you won't be able to use those features. Your project's URL is constructed by using the full handle. For example, Tuist's dashboard, which is public, is accessible at [cloud.tuist.io/tuist/tuist](https://cloud.tuist.io/tuist/tuist), where `tuist/tuist` is the project's full handle. --- URL: "/en/references/project-description/[identifier]" LLMS_URL: "/en/references/project-description/[identifier].md" editLink: false titleTemplate: ":title · Project Description · References · Tuist" --- --- URL: "/en/references/migrations/from-v3-to-v4" LLMS_URL: "/en/references/migrations/from-v3-to-v4.md" title: "From v3 to v4" titleTemplate: ":title · Migrations · References · Tuist" description: "This page documents how to migrate the Tuist CLI from the version 3 to version 4." --- # From Tuist v3 to v4 {#from-tuist-v3-to-v4} With the release of [Tuist 4](https://github.com/tuist/tuist/releases/tag/4.0.0), we took the opportunity to introduce some breaking changes to the project, which we believed would make the project easier to use and maintain in the long run. This document outlines the changes you will need to make to your project to upgrade from Tuist 3 to Tuist 4. ### Dropped version management through `tuistenv` {#dropped-version-management-through-tuistenv} Prior to Tuist 4, the installation script installed a tool, `tuistenv`, that would get renamed to `tuist` at installation time. The tool would take care of installing and activating versions of Tuist ensuring determinism across environments. With the aim of reducing the feature surface of Tuist, we decided to drop `tuistenv` in favor of [Mise](https://mise.jdx.dev/), a tool that does the same job but is more flexible and can be used across different tools. If you were using `tuistenv`, you'll have to uninstall the current version of Tuist by running `curl -Ls https://uninstall.tuist.io | bash` and then install it using the installation method of your choice. We strongly recommend the usage of Mise because it's able to install and activate versions deterministically across environments. ::: code-group ```bash [Uninstall tuistenv] curl -Ls https://uninstall.tuist.io | bash ``` ::: > [!IMPORTANT] MISE IN CI ENVIRONMENTS AND XCODE PROJECTS > If you decide to embrace the determinism that Mise brings across the board, we recommend checking out the documentation for how to use Mise in [CI environments](https://mise.jdx.dev/continuous-integration.html) and [Xcode projects](https://mise.jdx.dev/ide-integration.html#xcode). > [!NOTE] HOMEBREW IS SUPPORTED > Note that you can still install Tuist using Homebrew, which is a popular package manager for macOS. You can find the instructions on how to install Tuist using Homebrew in the installation guide. ### Dropped `init` constructors from `ProjectDescription` models {#dropped-init-constructors-from-projectdescription-models} With the aim of improving the readability and expressiveness of the APIs, we decided to remove the `init` constructors from all the `ProjectDescription` models. Every model now provides a static constructor that you can use to create instances of the models. If you were using the `init` constructors, you'll have to update your project to use the static constructors instead. > [!TIP] NAMING CONVENTION > The naming convention that we follow is to use the name of the model as the name of the static constructor. For example, the static constructor for the `Target` model is `Target.target`. ### Renamed `--no-cache` to `--no-binary-cache` {#renamed-nocache-to-nobinarycache} Because the `--no-cache` flag was ambiguous, we decided to rename it to `--no-binary-cache` to make it clear that it refers to the binary cache. If you were using the `--no-cache` flag, you'll have to update your project to use the `--no-binary-cache` flag instead. ### Renamed `tuist fetch` to `tuist install` {#renamed-tuist-fetch-to-tuist-install} We renamed the `tuist fetch` command to `tuist install` to align with the industry convention. If you were using the `tuist fetch` command, you'll have to update your project to use the `tuist install` command instead. ### [Adopt `Package.swift` as the DSL for dependencies](https://github.com/tuist/tuist/pull/5862) {#adopt-packageswift-as-the-dsl-for-dependencieshttpsgithubcomtuisttuistpull5862} Before Tuist 4, you could define dependencies in a `Dependencies.swift` file. This proprietary format broke the support in tools like [Dependabot](https://github.com/dependabot) or [Renovatebot](https://github.com/renovatebot/renovate) to automatically update dependencies. Moreover, it introduced unnecessary indirections for users. Therefore, we decided to embrace `Package.swift` as the only way to define dependencies in Tuist. If you were using the `Dependencies.swift` file, you'll have to move the content from your `Tuist/Dependencies.swift` to a `Package.swift` at the root, and use the `#if TUIST` directive to configure the integration. You can read more about how to integrate Swift Package dependencies here ### Renamed `tuist cache warm` to `tuist cache` {#renamed-tuist-cache-warm-to-tuist-cache} For brevity, we decided to rename the `tuist cache warm` command to `tuist cache`. If you were using the `tuist cache warm` command, you'll have to update your project to use the `tuist cache` command instead. ### Renamed `tuist cache print-hashes` to `tuist cache --print-hashes` {#renamed-tuist-cache-printhashes-to-tuist-cache-printhashes} We decided to rename the `tuist cache print-hashes` command to `tuist cache --print-hashes` to make it clear that it's a flag of the `tuist cache` command. If you were using the `tuist cache print-hashes` command, you'll have to update your project to use the `tuist cache --print-hashes` flag instead. ### Removed caching profiles {#removed-caching-profiles} Before Tuist 4, you could define caching profiles in `Tuist/Config.swift` which contained a configuration for the cache. We decided to remove this feature because it could lead to confusion when using it in the generation process with a profile other than the one that was used to generate the project. Moreover, it could lead to users using a debug profile to build a release version of the app, which could lead to unexpected results. In its place, we introduced the `--configuration` option, which you can use to specify the configuration you want to use when generating the project. If you were using caching profiles, you'll have to update your project to use the `--configuration` option instead. ### Removed `--skip-cache` in favor of arguments {#removed-skipcache-in-favor-of-arguments} We removed the flag `--skip-cache` from the `generate` command in favor of controlling for which targets the binary cache should be skipped by using the arguments. If you were using the `--skip-cache` flag, you'll have to update your project to use the arguments instead. ::: code-group ```bash [Before] tuist generate --skip-cache Foo ``` ```bash [After] tuist generate Foo ``` ::: ### [Dropped signing capabilities](https://github.com/tuist/tuist/pull/5716) {#dropped-signing-capabilitieshttpsgithubcomtuisttuistpull5716} Signing is already solved by community tooling like [Fastlane](https://fastlane.tools/) and Xcode itself, which do a much better job at that. We felt that signing was an stretch goal for Tuist, and that it was better to focus on the core features of the project. If you were using Tuist signing capabilities, which consisted of encrypting the certificates and profiles in the repository and installing them in the right places at generation time, you might want to replicate that logic in your own scripts that run before project generation. In particular: - A script that decrypts the certificates and profiles using a key either stored in the file-system or in an environment variable, and installs certificates in the keychain, and the provisioning profiles in the directory `~/Library/MobileDevice/Provisioning\ Profiles`. - A script that can take an existing profiles and certificates and encrypt them. > [!TIP] SIGNING REQUIREMENTS > Signing requires the right certificates to be present in the keychain and the provisioning profiles to be present in the directory `~/Library/MobileDevice/Provisioning\ Profiles`. You can use the `security` command-line tool to install certificates in the keychain and the `cp` command to copy the provisioning profiles to the right directory. ### Dropped Carthage integration via `Dependencies.swift` {#dropped-carthage-integration-via-dependenciesswift} Before Tuist 4, Carthage dependencies could be defined in a `Dependencies.swift` file, which users could then fetch by running `tuist fetch`. We also felt that this was a stretch goal for Tuist, specially considering a future where Swift Package Manager would be the preferred way to manage dependencies. If you were using Carthage dependencies, you'll have to use `Carthage` directly to pull the pre-compiled frameworks and XCFrameworks into Carthage's standard directory, and then reference those binaries from your tagets using the `TargetDependency.xcframework` and `TargetDependency.framework` cases. > [!NOTE] CARTHAGE IS STILL SUPPORTED > Some users understood that we dropped Carthage support. We didn't. The contract between Tuist and Carthage's output is to system-stored frameworks and XCFrameworks. The only thing that changed is who is responsible for fetching the dependencies. It used to be Tuist through Carthage, now it's Carthage. ### Dropped the `TargetDependency.packagePlugin` API {#dropped-the-targetdependencypackageplugin-api} Before Tuist 4, you could define a package plugin dependency using the `TargetDependency.packagePlugin` case. After seeing the Swift Package Manager introducing new package types, we decided to iterate on the API towards something that would be more flexible and future-proof. If you were using `TargetDependency.packagePlugin`, you'll have to use `TargetDependency.package` instead, and pass the type of package you want to use as an argument. ### [Dropped deprecated APIs](https://github.com/tuist/tuist/pull/5560) {#dropped-deprecated-apishttpsgithubcomtuisttuistpull5560} We removed the APIs that were marked as deprecated in Tuist 3. If you were using any of the deprecated APIs, you'll have to update your project to use the new APIs. --- URL: "/en/references/examples/[example]" LLMS_URL: "/en/references/examples/[example].md" editLink: false titleTemplate: ":title · Examples · References · Tuist" --- Check out example --- URL: "/en" LLMS_URL: "/en.md" title: "What is Tuist?" description: "Extend your Apple native tooling to better apps at scale." --- # From idea to the store We are the only **integrated extension of Apple's native toolchain to build better apps faster.**
## Installation Install Tuist and run `tuist init` to get started: ::: code-group ```bash [Homebrew] brew tap tuist/tuist brew install --formula tuist tuist init ``` ```bash [Mise] mise x tuist@latest -- tuist init ``` ::: Check out our installation guide for more details. ## Discover more Try out Tuist in minutes and learn how to get the most out of Tuist. ## Watch our latest talks Explore our team's presentations. Stay informed and gain expertise. ## Join the community See the source code, connect with others, and get connected. --- URL: "/en/guides/tuist/about" LLMS_URL: "/en/guides/tuist/about.md" title: "About Tuist" titleTemplate: ":title · Guides · Tuist" description: "Extend your Apple native tooling to better apps at scale." --- # About Tuist {#about-tuist} In the world of app development, particularly for platforms like Apple's, organizations often encounter **productivity roadblocks.** These can include sluggish compilation times, unreliable tests, and intricate automation workflows that drain resources. Traditionally, companies address these issues by forming dedicated platform teams. These specialists maintain codebase health and integrity, freeing other developers to focus on feature creation. However, this approach can be expensive and risky, as the departure of key team members can severely impact productivity. ## What {#what} **Tuist is a toolchain designed to accelerate and enhance app development.** We integrate seamlessly with official tools and systems, meeting developers in familiar territory. By shouldering the burden of tool and system integration, we enable teams to channel their energy into feature development and improving the overall developer experience. In essence, Tuist serves as your virtual platform team. We're with you every step of the way - from the spark of an app idea to its user launch - tackling challenges as they arise. Tuist is comprised of a [CLI](https://github.com/tuist/tuist), which is the main entry point for developers, and a server that the CLI integrates with to persist state and integrate with other publicly available services. ## Why {#why} Why choose Tuist? Here are compelling reasons: ### Simplify 🌱 {#simplify} As projects grow and span multiple platforms, modularization becomes crucial. Tuist streamlines this complexity, offering tools to optimize and better understand your project's structure. **Further reading:** Projects ### Optimize workflows 🚀 {#optimize-workflows} Leveraging project information, Tuist enhances efficiency through selective test execution and deterministic binary reuse across builds. **Further reading:** Cache, Selective testing, Registry, and Previews ### Foster healthy project evolution 📈 {#foster-healthy-project-evolution} We provide insights into your project's dynamics and expert guidance for informed decision-making. This approach prevents the frustration and productivity loss associated with unhealthy projects, which can lead to developer attrition and missed business goals. **Further reading:** Server ### Break down silos 💜 {#break-down-silos} Unlike platform-specific ecosystems (e.g., Xcode's contained environment), Tuist offers web-centric experiences and integrates seamlessly with popular tools like Slack, Prometheus, and GitHub, enhancing cross-tool collaboration. **Further reading:** Projects --- If you want to know more about Tuist, the project, and the company, you can check out our [handbook](https://handbook.tuist.io/), which contains detailed information about our vision, values, and the team behind Tuist. --- URL: "/en/guides/share/previews" LLMS_URL: "/en/guides/share/previews.md" title: "Previews" titleTemplate: ":title · Share · Guides · Tuist" description: "Learn how to generate and share previews of your apps with anyone." --- # Previews {#previews} > [!IMPORTANT] REQUIREMENTS > - A Tuist account and project When building an app, you may want to share it with others to get feedback. Traditionally, this is something that teams do by building, signing, and pushing their apps to platforms like Apple's [TestFlight](https://developer.apple.com/testflight/). However, this process can be cumbersome and slow, especially when you're just looking for quick feedback from a colleague or a friend. To make this process more streamlined, Tuist provides a way to generate and share previews of your apps with anyone. > [!IMPORTANT] DEVICE BUILDS NEED TO BE SIGNED > When building for device, it is currently your responsibility to ensure the app is signed correctly. We plan to streamline this in the future. :::code-group ```bash [Tuist Project] tuist build App # Build the app for the simulator tuist build App -- -destination 'generic/platform=iOS' # Build the app for the device tuist share App ``` ```bash [Xcode Project] xcodebuild -scheme App -project App.xcodeproj -configuration Debug # Build the app for the simulator xcodebuild -scheme App -project App.xcodeproj -configuration Debug -destination 'generic/platform=iOS' # Build the app for the device tuist share App --configuration Debug --platforms iOS tuist share App.ipa # Share an existing .ipa file ``` ::: The command will generate a link that you can share with anyone to run the app – either on a simulator or an actual device. All they'll need to do is to run the command below: ```bash tuist run {url} tuist run --device "My iPhone" {url} # Run the app on a specific device ``` When sharing an `.ipa` file, you can download the app directly from the mobile device using the Preview link. The links to `.ipa` previews are by default _public_. In the future, you will have an option to make them private, so that the recipient of the link would need to authenticate with their Tuist account to download the app. `tuist run` also enables you to run a latest preview based on a specifier such as `latest`, branch name, or a specific commit hash: ```bash tuist run App@latest # Runs latest App preview associated with the project's default branch tuist run App@my-feature-branch # Runs latest App preview associated with a given branch tuist run App@00dde7f56b1b8795a26b8085a781fb3715e834be # Runs latest App preview associated with a given git commit sha ``` > [!IMPORTANT] PREVIEWS' VISIBILITY > Only people with access to the organization the project belongs to can access the previews. We plan to add support for expiring links. ## Tuist macOS app {#tuist-macos-app}

Tuist

Download
To make running Tuist Previews even easier, we developed a Tuist macOS menu bar app. Instead of running Previews via the Tuist CLI, you can [download](https://tuist.dev/download) the macOS app. You can also install the app by running `brew install --cask tuist/tuist/tuist`. When you now click on "Run" in the Preview page, the macOS app will automatically launch it on your currently selected device. > [!IMPORTANT] REQUIREMENTS > To download Previews, you need to first authenticate with the `tuist auth login` command. > In the future, you will be able to authenticate directly in the app. > > Additionally, you need to have Xcode locally installed. ## Pull/merge request comments {#pullmerge-request-comments} > [!IMPORTANT] INTEGRATION WITH GIT PLATFORM REQUIRED > To get automatic pull/merge request comments, integrate your remote project with a Git platform. Testing new functionality should be a part of any code review. But having to build an app locally adds unnecessary friction, often leading to developers skipping testing functionality on their device at all. But *what if each pull request contained a link to the build that would automatically run the app on a device you selected in the Tuist macOS app?* Once your Tuist project is connected with your Git platform such as [GitHub](https://github.com), add a `tuist share MyApp` to your CI workflow. Tuist will then post a Preview link directly in your pull requests: ![GitHub app comment with a Tuist Preview link](/images/guides/share/github-app-with-preview.png) ## README badge {#readme-badge} To make Tuist Previews more visible in your repository, you can add a badge to your `README` file that points to the latest Tuist Preview: [![Tuist Preview](https://tuist.dev/Dimillian/IcySky/previews/latest/badge.svg)](https://tuist.dev/Dimillian/IcySky/previews/latest) To add the badge to your `README`, use the following markdown and replace the account and project handles with your own: ``` [![Tuist Preview](https://tuist.dev/{account-handle}/{project-handle}/previews/latest/badge.svg)](https://tuist.dev/{account-handle}/{project-handle}/previews/latest) ``` ## Automations {#automations} You can use the `--json` flag to get a JSON output from the `tuist share` command: ``` tuist share --json ``` The JSON output is useful to create custom automations, such as posting a Slack message using your CI provider. The JSON contains a `url` key with the full preview link and a `qrCodeURL` key with the URL to the QR code image to make it easier to download previews from a real device. An example of a JSON output is below: ```json { "id": 1234567890, "url": "https://cloud.tuist.io/preview/1234567890", "qrCodeURL": "https://cloud.tuist.io/preview/1234567890/qr-code.svg" } ``` --- URL: "/en/guides/quick-start/install-tuist" LLMS_URL: "/en/guides/quick-start/install-tuist.md" title: "Install Tuist" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "Learn how to install Tuist in your environment." --- # Install Tuist {#install-tuist} The Tuist CLI consists of an executable, dynamic frameworks, and a set of resources (for example, templates). Although you could manually build Tuist from [the sources](https://github.com/tuist/tuist), **we recommend using one of the following installation methods to ensure a valid installation.** ### Mise {#recommended-mise} ::: info Mise is a recommended alternative to [Homebrew](https://brew.sh) if you are a team or organization that needs to ensure deterministic versions of tools across different environments. ::: You can install Tuist through any of the following commands: ```bash mise install tuist # Install the current version specified in .tool-versions/.mise.toml mise install tuist@x.y.z # Install a specific version number mise install tuist@3 # Install a fuzzy version number ``` Note that unlike tools like Homebrew, which install and activate a single version of the tool globally, **Mise requires the activation of a version** either globally or scoped to a project. This is done by running `mise use`: ```bash mise use tuist@x.y.z # Use tuist-x.y.z in the current project mise use tuist@latest # Use the latest tuist in the current directory mise use -g tuist@x.y.z # Use tuist-x.y.z as the global default mise use -g tuist@system # Use the system's tuist as the global default ``` ### Homebrew {#recommended-homebrew} You can install Tuist using [Homebrew](https://brew.sh) and [our formulas](https://github.com/tuist/homebrew-tuist): ```bash brew tap tuist/tuist brew install --formula tuist brew install --formula tuist@x.y.z ``` ::: tip VERIFYING THE AUTHENTICITY OF THE BINARIES You can verify that your installation's binaries have been built by us by running the following command, which checks if the certificate's team is `U6LC622NKF`: ```bash curl -fsSL "https://docs.tuist.dev/verify.sh" | bash ``` ::: --- URL: "/en/guides/quick-start/get-started" LLMS_URL: "/en/guides/quick-start/get-started.md" title: "Get started" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "Learn how to install Tuist in your environment." --- # Get started {#get-started} The easiest way to get started with Tuist in any directory or in the directory of your Xcode project or workspace: ::: code-group ```bash [Mise] mise x tuist@latest -- tuist init ``` ```bash [Global Tuist (Homebrew)] tuist init ``` ::: The command will walk you through the steps to create a generated project or integrate an existing Xcode project or workspace. It helps you connect your setup to the remote server, giving you access to features like selective testing, previews, and the registry. > [!NOTE] MIGRATE AN EXISTING PROJECT > If you want to migrate an existing project to generated projects to improve the developer experience and take advantage of our cache, check out our migration guide. --- URL: "/en/guides/quick-start/gather-insights" LLMS_URL: "/en/guides/quick-start/gather-insights.md" title: "Gather insights" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "Learn how to gather insights about your project." --- # Gather insights {#gather-insights} Tuist can integrate with a server to extend its capabilities. One of those capabilities is gathering insights about your project and builds. All you need is to have an account with a project in the server. First of all, you'll need to authenticate by running: ```bash tuist auth login ``` ## Create a project {#create-a-project} You can then create a project by running: ```bash tuist project create my-handle/MyApp # Tuist project my-handle/MyApp was successfully created 🎉 {#tuist-project-myhandlemyapp-was-successfully-created-} ``` Copy `my-handle/MyApp`, which represents the full handle of the project. ## Connect projects {#connect-projects} After creating the project on the server, you'll have to connect it to your local project. Run `tuist edit` and edit the `Tuist.swift` file to include the full handle of the project: ```swift import ProjectDescription let tuist = Tuist(fullHandle: "my-handle/MyApp") ``` Voilà! You're now ready to gather insights about your project and builds. Run `tuist test` to run the tests reporting the results to the server. > [!NOTE] > Tuist enqueues the results locally and tries to send them without blocking the command. Therefore, they might not be sent immediately after the command finishes. In CI, the results are sent immediately. ![An image that shows a list of runs in the server](/images/guides/quick-start/runs.png) Having data from your projects and builds is crucial in making informed decisions. Tuist will continue to extend its capabilities, and you'll benefit from them without having to change your project configuration. Magic, right? 🪄 --- URL: "/en/guides/quick-start/add-dependencies" LLMS_URL: "/en/guides/quick-start/add-dependencies.md" title: "Add dependencies" titleTemplate: ":title · Quick-start · Guides · Tuist" description: "Learn how to add dependencies to your first Swift project" --- # Add dependencies {#add-dependencies} It's common for projects to depend on third-party libraries to provide additional functionality. To do so, run the following command to have the best experience editing your project: ```bash tuist edit ``` An Xcode project will open containing your project files. Edit the `Package.swift` and add the ```swift // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies .package(url: "https://github.com/onevcat/Kingfisher", .upToNextMajor(from: "7.12.0")) // [!code ++] ] ) ``` Then edit the application target in your project to declare `Kingfisher` as a dependency: ```swift import ProjectDescription let project = Project( name: "MyApp", targets: [ .target( name: "MyApp", destinations: .iOS, product: .app, bundleId: "io.tuist.MyApp", infoPlist: .extendingDefault( with: [ "UILaunchStoryboardName": "LaunchScreen.storyboard", ] ), sources: ["MyApp/Sources/**"], resources: ["MyApp/Resources/**"], dependencies: [ .external(name: "Kingfisher") // [!code ++] ] ), .target( name: "MyAppTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyAppTests", infoPlist: .default, sources: ["MyApp/Tests/**"], resources: [], dependencies: [.target(name: "MyApp")] ), ] ) ``` Then run `tuist install` to resolve and pull the dependencies using the [Swift Package Manager](https://www.swift.org/documentation/package-manager/). > [!NOTE] SPM AS A DEPENDENCY RESOLVER > Tuist recommended approach to dependencies uses the Swift Package Manager (SPM) only to resolve dependencies. Tuist then converts them into Xcode projects and targets for maximum configurability and control. ## Visualize the project {#visualize-the-project} You can visualize the project structure by running: ```bash tuist graph ``` The command will output and open a `graph.png` file in the project's directory: ![Project graph](/images/guides/quick-start/graph.png) ## Use the dependency {#use-the-dependency} Run `tuist generate` to open the project in Xcode, and make the following changes to the `ContentView.swift` file: ```swift import SwiftUI import Kingfisher // [!code ++] public struct ContentView: View { public init() {} public var body: some View { Text("Hello, World!") // [!code --] .padding() // [!code --] KFImage(URL(string: "https://cloud.tuist.io/images/tuist_logo_32x32@2x.png")!) // [!code ++] } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } ``` Run the app from Xcode, and you should see the image loaded from the URL. --- URL: "/en/guides/develop/test" LLMS_URL: "/en/guides/develop/test.md" title: "tuist test" titleTemplate: ":title · Develop · Guides · Tuist" description: "Learn how to run tests efficiently with Tuist." --- # Test {#test} Tuist provides a command, `tuist test` to generate the project if needed, and then run the tests with the the platform-specific build tool (e.g. `xcodebuild` for Apple platforms). You might wonder what's the value of using `tuist test` over generating the project with `tuist generate` and running the tests with the platform-specific build tool. - **Single command:** `tuist test` ensures the project is generated if needed before compiling the project. - **Beautified output:** Tuist enriches the output using tools like [xcbeautify](https://github.com/cpisciotta/xcbeautify) that make the output more user-friendly. - Cache: It optimizes the build by deterministically reusing the build artifacts from a remote cache. - Smart runner: It runs only the tests that need to be run, saving time and resources. - Flakiness: Prevent, detect, and fix flaky tests. ## Usage {#usage} To run the tests of a project, you can use the `tuist test` command. This command will generate the project if needed, and then run the tests using the platform-specific build tool. We support the use of the `--` terminator to forward all subsequent arguments directly to the build tool. ::: code-group ```bash [Running scheme tests] tuist test MyScheme ``` ```bash [Running all tests without binary cache] tuist test --no-binary-cache ``` ```bash [Running all tests without selective testing] tuist test --no-selective-testing ``` ::: ## Pull/merge request comments {#pullmerge-request-comments} > [!IMPORTANT] REQUIREMENTS > To get automatic pull/merge request comments, integrate your remote project with a Git platform. When running tests in your CI environments we can correlate the test results with the pull/merge request that triggered the CI build. This allows us to post a comment on the pull/merge request with the test results. ![GitHub App example](/images/contributors/scheme-arguments.png) --- URL: "/en/guides/develop/selective-testing/xcodebuild" LLMS_URL: "/en/guides/develop/selective-testing/xcodebuild.md" title: "xcodebuild" titleTemplate: ":title · Selective testing · Develop · Guides · Tuist" description: "Learn how to leverage selective testing with `xcodebuild`." --- # xcodebuild {#xcodebuild} > [!IMPORTANT] REQUIREMENTS > - A Tuist account and project To run tests selectively using `xcodebuild`, you can prepend your `xcodebuild` command with `tuist` – for example, `tuist xcodebuild test -scheme App`. The command hashes your project and on success, it persists the hashes to determine what has changed in future runs. In future runs `tuist xcodebuild test` transparently uses the hashes to filter down the tests to run only the ones that have changed since the last successful test run. For example, assuming the following dependency graph: - `FeatureA` has tests `FeatureATests`, and depends on `Core` - `FeatureB` has tests `FeatureBTests`, and depends on `Core` - `Core` has tests `CoreTests` `tuist xcodebuild test` will behave as such: | Action | Description | Internal state | | ---- | --- | ---- | | `tuist xcodebuild test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The hashes of `FeatureATests`, `FeatureBTests` and `CoreTests` are persisted | | `FeatureA` is updated | The developer modifies the code of a target | Same as before | | `tuist xcodebuild test` invocation | Runs the tests in `FeatureATests` because it hash has changed | The new hash of `FeatureATests` is persisted | | `Core` is updated | The developer modifies the code of a target | Same as before | | `tuist xcodebuild test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The new hash of `FeatureATests` `FeatureBTests`, and `CoreTests` are persisted | To use `tuist xcodebuild test` on your CI, follow the instructions in the Continuous integration guide. --- URL: "/en/guides/develop/selective-testing/generated-project" LLMS_URL: "/en/guides/develop/selective-testing/generated-project.md" title: "Generated project" titleTemplate: ":title · Selective testing · Develop · Guides · Tuist" description: "Learn how to leverage selective testing with a generated project." --- # Generated project {#generated-project} > [!IMPORTANT] REQUIREMENTS > - A generated project > - A Tuist account and project To run tests selectively with your generated project, use the `tuist test` command. The command hashes your Xcode project the same way it does for warming the cache, and on success, it persists the hashes on to determine what has changed in future runs. In future runs `tuist test` transparently uses the hashes to filter down the tests to run only the ones that have changed since the last successful test run. For example, assuming the following dependency graph: - `FeatureA` has tests `FeatureATests`, and depends on `Core` - `FeatureB` has tests `FeatureBTests`, and depends on `Core` - `Core` has tests `CoreTests` `tuist test` will behave as such: | Action | Description | Internal state | | ---- | --- | ---- | | `tuist test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The hashes of `FeatureATests`, `FeatureBTests` and `CoreTests` are persisted | | `FeatureA` is updated | The developer modifies the code of a target | Same as before | | `tuist test` invocation | Runs the tests in `FeatureATests` because it hash has changed | The new hash of `FeatureATests` is persisted | | `Core` is updated | The developer modifies the code of a target | Same as before | | `tuist test` invocation | Runs the tests in `CoreTests`, `FeatureATests`, and `FeatureBTests` | The new hash of `FeatureATests` `FeatureBTests`, and `CoreTests` are persisted | `tuist test` integrates directly with binary caching to use as many binaries from your local or remote storage to improve the build time when running your test suite. The combination of selective testing with binary caching can dramatically reduce the time it takes to run tests on your CI. ## UI Tests {#ui-tests} Tuist supports selective testing of UI tests. However, Tuist needs to know the destination in advance. Only if you specify the `destination` parameter, Tuist will run the UI tests selectively, such as: ```sh tuist test --device 'iPhone 14 Pro' # or tuist test -- -destination 'name=iPhone 14 Pro' # or tuist test -- -destination 'id=SIMULATOR_ID' ``` --- URL: "/en/guides/develop/selective-testing" LLMS_URL: "/en/guides/develop/selective-testing.md" title: "Selective testing" titleTemplate: ":title · Develop · Guides · Tuist" description: "Use selective testing to run only the tests that have changed since the last successful test run." --- # Selective testing {#selective-testing} As your project grows, so does the amount of your tests. For a long time, running all tests on every PR or push to `main` takes tens of seconds. But this solution does not scale to thousands of tests your team might have. On every test run on the CI, you most likely re-run all the tests, regardless of the changes. Tuist's selective testing helps you to drastically speed up running the tests themselves by running only the tests that have changed since the last successful test run based on our hashing algorithm. Selective testing works with `xcodebuild`, which supports any Xcode project, or if you generate your projects with Tuist, you can use the `tuist test` command instead that provides some extra convenience such as integration with the binary cache. To get started with selective testing, follow the instructions based on your project setup: - xcodebuild - Generated project > [!WARNING] MODULE VS FILE-LEVEL GRANULARITY > Due to the impossibility of detecting the in-code dependencies between tests and sources, the maximum granularity of selective testing is at the target level. Therefore, we recommend keeping your targets small and focused to maximize the benefits of selective testing. ## Pull/merge request comments {#pullmerge-request-comments} > [!IMPORTANT] INTEGRATION WITH GIT PLATFORM REQUIRED > To get automatic pull/merge request comments, integrate your Tuist project with a Git platform. Once your Tuist project is connected with your Git platform such as [GitHub](https://github.com), and you start using `tuist xcodebuild test` or `tuist test` as part of your CI wortkflow, Tuist will post a comment directly in your pull/merge requests, including which tests were run and which skipped: ![GitHub app comment with a Tuist Preview link](/images/guides/develop/selective-testing/github-app-comment.png) --- URL: "/en/guides/develop/registry/xcodeproj-integration" LLMS_URL: "/en/guides/develop/registry/xcodeproj-integration.md" title: "Generated project with the XcodeProj-based package integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in a generated Xcode project with the XcodeProj-based package integration." --- # Generated project with the XcodeProj-based package integration {#generated-project-with-xcodeproj-based-integration} When using the XcodeProj-based integration, you can use the ``--replace-scm-with-registry`` flag to resolve dependencies from the registry if they are available. Add it to the `installOptions` in your `Tuist.swift` file: ```swift import ProjectDescription let tuist = Tuist( fullHandle: "{account-handle}/{project-handle}", project: .tuist( installOptions: .options(passthroughSwiftPackageManagerArguments: ["--replace-scm-with-registry"]) ) ) ``` If you want to ensure that the registry is used every time you resolve dependencies, you will need to update `dependencies` in your `Tuist/Package.swift` file to use the registry identifier instead of a URL. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `swift-composable-architecture` package, do the following: ```diff dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") + .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ] ``` --- URL: "/en/guides/develop/registry/xcode-project" LLMS_URL: "/en/guides/develop/registry/xcode-project.md" title: "Xcode project" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in an Xcode project." --- # Xcode project {#xcode-project} To add packages using the registry in your Xcode project, use the default Xcode UI. You can search for packages in the registry by clicking on the `+` button in the `Package Dependencies` tab in Xcode. If the package is available in the registry, you will see the `tuist.dev` registry in the top right: ![Adding package dependencies](/images/guides/develop/build/registry/registry-add-package.png) > [!NOTE] > Xcode currently doesn't support automatically replacing source control packages with their registry equivalents. You will need to manually remove the source control package and add the registry package to speed up the resolution. --- URL: "/en/guides/develop/registry/swift-package" LLMS_URL: "/en/guides/develop/registry/swift-package.md" title: "Swift package" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in a Swift package." --- # Swift package {#swift-package} If you are working on a Swift package, you can use the `--replace-scm-with-registry` flag to resolve dependencies from the registry if they are available: ```bash swift package --replace-scm-with-registry resolve ``` If you want to ensure that the registry is used every time you resolve dependencies, you will need to update `dependencies` in your `Package.swift` file to use the registry identifier instead of a URL. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `swift-composable-architecture` package, do the following: ```diff dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") + .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ] ``` --- URL: "/en/guides/develop/registry/generated-project" LLMS_URL: "/en/guides/develop/registry/generated-project.md" title: "Generated project with the Xcode package integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in a generated Xcode project with the Xcode package integration." --- # Generated project with the Xcode package integration {#generated-project-with-xcode-based-integration} If you are using the Xcode's default integration of packages with Tuist Projects, you need to use the registry identifier instead of a URL when adding a package: ```swift import ProjectDescription let project = Project( name: "MyProject", packages: [ // Source control resolution // .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "0.1.0") // Registry resolution .package(id: "pointfreeco.swift-composable-architecture", from: "0.1.0") ], .target( name: "App", product: .app, bundleId: "io.tuist.App", dependencies: [ .package(product: "ComposableArchitecture"), ] ) ) ``` --- URL: "/en/guides/develop/registry/continuous-integration" LLMS_URL: "/en/guides/develop/registry/continuous-integration.md" title: "Continuous integration" titleTemplate: ":title · Registry · Develop · Guides · Tuist" description: "Learn how to use the Tuist Registry in continuous integration." --- # Continuous Integration (CI) {#continuous-integration-ci} To use the registry on your CI, you need to ensure that you have logged in to the registry by running `tuist registry login` as part of your workflow. > [!NOTE] ONLY XCODE INTEGRATION > Creating a new pre-unlocked keychain is required only if you are using the Xcode integration of packages. Since the registry credentials are stored in a keychain, you need to ensure the keychain can be accessed in the CI environment. Note some CI providers or automation tools like [Fastlane](https://fastlane.tools/) already create a temporary keychain or provide a built-in way how to create one. However, you can also create one by creating a custom step with the following code: ```bash TMP_DIRECTORY=$(mktemp -d) KEYCHAIN_PATH=$TMP_DIRECTORY/keychain.keychain KEYCHAIN_PASSWORD=$(uuidgen) security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security default-keychain -s $KEYCHAIN_PATH security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH ``` `tuist registry login` will then store the credentials in the default keychain. Ensure that your default keychain is created and unlocked _before_ `tuist registry login` is run. Additionally, you need to ensure the `TUIST_CONFIG_TOKEN` environment variable is set. You can create one by following the documentation here. An example workflow for GitHub Actions could then look like this: ```yaml name: Build jobs: build: steps: - # Your set up steps... - name: Create keychain run: | TMP_DIRECTORY=$(mktemp -d) KEYCHAIN_PATH=$TMP_DIRECTORY/keychain.keychain KEYCHAIN_PASSWORD=$(uuidgen) security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security default-keychain -s $KEYCHAIN_PATH security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH - name: Log in to the Tuist Registry env: TUIST_CONFIG_TOKEN: ${{ secrets.TUIST_CONFIG_TOKEN }} run: tuist registry login - # Your build steps ``` ### Incremental resolution across environments {#incremental-resolution-across-environments} Clean/cold resolutions are slightly faster with our registry, and you can experience even greater improvements if you persist the resolved dependencies across CI builds. Note that thanks to the registry, the size of the directory that you need to store and restore is much smaller than without the registry, taking significantly less time. To cache dependencies when using the default Xcode package integration, the best way is to specify a custom `-clonedSourcePackagesDirPath` when resolving dependencies via `xcodebuild`, such as: ```sh xcodebuild -resolvePackageDependencies -clonedSourcePackagesDirPath .build ``` Additionally, you will need to find a path of the `Package.resolved`. You can grab the path by running `ls **/Package.resolved`. The path should look something like `App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved`. For Swift packages and the XcodeProj-based integration, we can use the default `.build` directory located either in the root of the project or in the `Tuist` directory. Make sure the path is correct when setting up your pipeline. Here's an example workflow for GitHub Actions for resolving and caching dependencies when using the default Xcode package integration: ```yaml - name: Restore cache id: cache-restore uses: actions/cache/restore@v4 with: path: .build key: ${{ runner.os }}-${{ hashFiles('App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} restore-keys: .build - name: Resolve dependencies if: steps.cache-restore.outputs.cache-hit != 'true' run: xcodebuild -resolvePackageDependencies -clonedSourcePackagesDirPath .build - name: Save cache id: cache-save uses: actions/cache/save@v4 with: path: .build key: ${{ steps.cache-restore.outputs.cache-primary-key }} ``` --- URL: "/en/guides/develop/registry" LLMS_URL: "/en/guides/develop/registry.md" title: "Registry" titleTemplate: ":title · Develop · Guides · Tuist" description: "Optimize your Swift package resolution times by leveraging the Tuist Registry." --- # Registry {#registry} > [!IMPORTANT] REQUIREMENTS > - A Tuist account and project As the number of dependencies grows, so does the time to resolve them. While other package managers like [CocoaPods](https://cocoapods.org/) or [npm](https://www.npmjs.com/) are centralized, Swift Package Manager is not. Because of that, SwiftPM needs to resolve dependencies by doing a deep clone of each repository, which can be time-consuming and takes up more memory than a centralized approach would. To address this, Tuist provides an implementation of the [Package Registry](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/PackageRegistryUsage.md), so you can download only the commits you _actually need_. The packages in the registry are based on the [Swift Package Index](https://swiftpackageindex.com/) – if you can find a package there, the package is also available in the Tuist Registry. Additionally, the packages are distributed across the globe using an edge storage for minimum latency when resolving them. ## Usage {#usage} To set up and log in to the registry, run the following command in your project's directory: ```bash tuist registry setup ``` This command generates a registry configuration files and logs you in to the registry. To ensure the rest of your team can access the registry, ensure the generated files is committed and that your team members run the following command to log in: ```bash tuist registry login ``` Now you can access the registry! To resolve dependencies from the registry instead of from source control, continue reading based on your project setup: - Xcode project - Generated project with the Xcode package integration - Generated project with the XcodeProj-based package integration - Swift package To set up the registry on the CI, follow this guide: Continuous integration. ### Package registry identifiers {#package-registry-identifiers} When you use package registry identifiers in a `Package.swift` or `Project.swift` file, you need to convert the URL of the package to the registry convention. The registry identifier is always in the form of `{organization}.{repository}`. For example, to use the registry for the `https://github.com/pointfreeco/swift-composable-architecture` package, the package registry identifier would be `pointfreeco.swift-composable-architecture`. > [!NOTE] > The identifier can't contain more than one dot. If the repository name contains a dot, it's replaced with an underscore. > For example, the `https://github.com/groue/GRDB.swift` package would have the registry identifier `groue.GRDB_swift`. --- URL: "/en/guides/develop/projects/tma-architecture" LLMS_URL: "/en/guides/develop/projects/tma-architecture.md" title: "The Modular Architecture (TMA)" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about The Modular Architecture (TMA) and how to structure your projects using it." --- # The Modular Architecture (TMA) {#the-modular-architecture-tma} TMA is an architectural approach to structure Apple OS applications to enable scalability, optimize build and test cycles, and ensure good practices in your team. Its core idea is to build your apps by building independent features that are interconnected using clear and concise APIs. These guidelines introduce the principles of the architecture, helping you identify and organize your application features in different layers. It also introduces tips, tools, and advice if you decide to use this architecture. > [!INFO] µFEATURES > This architecture was previously known as µFeatures. We've renamed it to The Modular Architecture (TMA) to better reflect its purpose and the principles behind it. ## Core principle {#core-principle} Developers should be able to **build, test, and try** their features fast, independently of the main app, and while ensuring Xcode features like UI previews, code completion, and debugging work reliably. ## What is a module {#what-is-a-module} A module represents an application feature and is a combination of the following five targets (where target referts to an Xcode target): - **Source:** Contains the feature source code (Swift, Objective-C, C++, JavaScript...) and its resources (images, fonts, storyboards, xibs). - **Interface:** It's a companion target that contains the public interface and models of the feature. - **Tests:** Contains the feature unit and integration tests. - **Testing:** Provides testing data that can be used in tests and the example app. It also provides mocks for module classes and protocols that can be used by other features as we'll see later. - **Example:** Contains an example app that developers can use to try out the feature under certain conditions (different languages, screen sizes, settings). We recommend following a naming convention for targets, something that you can enforce in your project thanks to Tuist's DSL. | Target | Dependencies | Content | | ---- | ---- | ---- | | `Feature` | `FeatureInterface` | Source code and resources | | `FeatureInterface` | - | Public interface and models | | `FeatureTests` | `Feature`, `FeatureTesting` | Unit and integration tests | | `FeatureTesting` | `FeatureInterface` | Testing data and mocks | | `FeatureExample` | `FeatureTesting`, `Feature` | Example app | > [!TIP] UI Previews > `Feature` can use `FeatureTesting` as a Development Asset to allow for UI previews > [!IMPORTANT] COMPILER DIRECTIVES INSTEAD OF TESTING TARGETS > Alternatively, you can use compiler directives to include test data and mocks in the `Feature` or `FeatureInterface` targets when compiling for `Debug`. You simplify the graph, but you'll end up compiling code that you won't need for running the app. ## Why a module {#why-a-module} ### Clear and concise APIs {#clear-and-concise-apis} When all the app source code lives in the same target it is very easy to build implicit dependencies in code and end up with the so well-known spaghetti code. Everything is strongly coupled, the state is sometimes unpredictable, and introducing new changes become a nightmare. When we define features in independent targets we need to design public APIs as part of our feature implementation. We need to decide what should be public, how our feature should be consumed, what should remain private. We have more control over how we want our feature clients to use the feature and we can enforce good practices by designing safe APIs. ### Small modules {#small-modules} [Divide and conquer](https://en.wikipedia.org/wiki/Divide_and_conquer). Working in small modules allows you to have more focus and test and try the feature in isolation. Moreover, development cycles are much faster since we have a more selective compilation, compiling only the components that are necessary to get our feature working. The compilation of the whole app is only necessary at the very end of our work, when we need to integrate the feature into the app. ### Reusability {#reusability} Reusing code across apps and other products like extensions is encouraged using frameworks or libraries. By building modules reusing them is pretty straightforward. We can build an iMessage extension, a Today Extension, or a watchOS application by just combining existing modules and adding _(when necessary)_ platform-specific UI layers. ## Dependencies {#dependencies} When a module depends on another module, it declares a dependency against its interface target. The benefit of this is two-fold. It prevents the implementation of a module to be coupled to the implementation of another module, and it speeds up clean builds because they only have to compile the implementation of our feature, and the interfaces of direct and transitive dependencies. This approach is inspired by SwiftRock's idea of [Reducing iOS Build Times by using Interface Modules](https://swiftrocks.com/reducing-ios-build-times-by-using-interface-targets). Depending on interfaces requires apps to build the graph of implementations at runtime, and dependency-inject it into the modules that need it. Although TMA is non-opinionated about how to do this, we recommend using dependency-injection solutions or patterns or solutions that don't add built-time indirections or use platform APIs that were not designed for this purpose. ## Product types {#product-types} When building a module, you can choose between **libraries and frameworks**, and **static and dynamic linking** for the targets. Without Tuist, making this decision is a bit more complex because you need to configure the dependency graph manually. However, thanks to Tuist Projects, this is no longer a problem. We recommend using dynamic libraries or frameworks during development using bundle accessors to decouple the bundle-accessing logic from the library or framework nature of the target. This is key for fast compilation times and to ensure [SwiftUI Previews](https://developer.apple.com/documentation/swiftui/previews-in-xcode) work reliably. And static libraries or frameworks for the release builds to ensure the app boots fast. You can leverage dynamic configuration to change the product type at generation-time: ```bash # You'll have to read the value of the variable from the manifest {#youll-have-to-read-the-value-of-the-variable-from-the-manifest} # and use it to change the linking type {#and-use-it-to-change-the-linking-type} TUIST_PRODUCT_TYPE=static-library tuist generate ``` ```swift // You can place this in your manifest files or helpers // and use the returned value when instantiating targets. func productType() -> Product { if case let .string(productType) = Environment.productType { return productType == "static-library" ? .staticLibrary : .framework } else { return .framework } } ``` > [!IMPORTANT] MERGEABLE LIBRARIES > Apple attempted to alleviate the cumbersomeness of switching between static and dynamic libraries by introducing [mergeable libraries](https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries). However, that introduces build-time non-determinism that makes your build non-reproducible and harder to optimize so we don't recommend using it. ## Code {#code} TMA is non-opinionated about the code architecture and patterns for your modules. However, we'd like to share some tips based on our experience: - **Leveraging the compiler is great.** Over-leveraging the compiler might end up being non-productive and cause some Xcode features like previews to work unreliably. We recommend using the compiler to enforce good practices and catch errors early, but not to the point that it makes the code harder to read and maintain. - **Use Swift Macros sparingly.** They can be very powerful but can also make the code harder to read and maintain. - **Embrace the platform and the language, don't abstract them.** Trying to come up with ellaborated abstraction layers might end up being counterproductive. The platform and the language are powerful enough to build great apps without the need for additional abstraction layers. Use good programming and design patterns as a reference to build your features. ## Resources {#resources} - [Building µFeatures](https://speakerdeck.com/pepibumur/building-ufeatures) - [Framework Oriented Programming](https://speakerdeck.com/pepibumur/framework-oriented-programming-mobilization-dot-pl) - [A Journey into frameworks and Swift](https://speakerdeck.com/pepibumur/a-journey-into-frameworks-and-swift) - [Leveraging frameworks to speed up our development on iOS - Part 1](https://developers.soundcloud.com/blog/leveraging-frameworks-to-speed-up-our-development-on-ios-part-1) - [Library Oriented Programming](https://academy.realm.io/posts/justin-spahr-summers-library-oriented-programming/) - [Building Modern Frameworks](https://developer.apple.com/videos/play/wwdc2014/416/) - [The Unofficial Guide to xcconfig files](https://pewpewthespells.com/blog/xcconfig_guide.html) - [Static and Dynamic Libraries](https://pewpewthespells.com/blog/static_and_dynamic_libraries.html) --- URL: "/en/guides/develop/projects/templates" LLMS_URL: "/en/guides/develop/projects/templates.md" title: "Templates" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to create and use templates in Tuist to generate code in your projects." --- # Templates {#templates} In projects with an established architecture, developers might want to bootstrap new components or features that are consistent with the project. With `tuist scaffold` you can generate files from a template. You can define your own templates or use the ones that are vendored with Tuist. These are some scenarios where scaffolding might be useful: - Create a new feature that follows a given architecture: `tuist scaffold viper --name MyFeature`. - Create new projects: `tuist scaffold feature-project --name Home` > [!NOTE] NON-OPINIONATED > Tuist is not opinionated about the content of your templates, and what you use them for. They are only required to be in a specific directory. ## Defining a template {#defining-a-template} To define templates, you can run `tuist edit` and then create a directory called `name_of_template` under `Tuist/Templates` that represents your template. Templates need a manifest file, `name_of_template.swift` that describes the template. So if you are creating a template called `framework`, you should create a new directory `framework` at `Tuist/Templates` with a manifest file called `framework.swift` that could look like this: ```swift import ProjectDescription let nameAttribute: Template.Attribute = .required("name") let template = Template( description: "Custom template", attributes: [ nameAttribute, .optional("platform", default: "ios"), ], items: [ .string( path: "Project.swift", contents: "My template contents of name \(nameAttribute)" ), .file( path: "generated/Up.swift", templatePath: "generate.stencil" ), .directory( path: "destinationFolder", sourcePath: "sourceFolder" ), ] ) ``` ## Using a template {#using-a-template} After defining the template, we can use it from the `scaffold` command: ```bash tuist scaffold name_of_template --name Name --platform macos ``` > [!NOTE] > Since platform is an optional argument, we can also call the command without the `--platform macos` argument. If `.string` and `.files` don't provide enough flexibility, you can leverage the [Stencil](https://stencil.fuller.li/en/latest/) templating language via the `.file` case. Besides that, you can also use additional filters defined here. Using string interpolation, `\(nameAttribute)` above would resolve to `{{ name }}`. If you'd like to use Stencil filters in the template definition, you can use that interpolation manually and add any filters you like. For example, you might use `{ { name | lowercase } }` instead of `\(nameAttribute)` to get the lowercased value of the name attribute. You can also use `.directory` which gives the possibility to copy entire folders to a given path. > [!TIP] PROJECT DESCRIPTION HELPERS > Templates support the use of project description helpers to reuse code across templates. --- URL: "/en/guides/develop/projects/synthesized-files" LLMS_URL: "/en/guides/develop/projects/synthesized-files.md" title: "Synthesized files" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about synthesized files in Tuist projects." --- # Synthesized files {#synthesized-files} Tuist can generate files and code at generation-time to bring some convenience to managing and working with Xcode projects. In this page you'll learn about this functionality, and how you can use it in your projects. ## Target resources {#target-resources} Xcode projects support adding resources to targets. However, they present teams with a few challenges, specially when working with a modular project where sources and resources are often moved around: - **Inconsistent runtime access**: Where the resources end up in the final product and how you access them depends on the target product. For example, if your target represents an application, the resources are copied to the application bundle. This leads to code accessing the resources that makes assumptions on the bundle structure, which is not ideal because it makes the code harder to reason about and the resources to move around. - **Products that don't support resources**: There are certain products like static libraries that are not bundles and therefore don't support resources. Because of that, you either have to resort to a different product type, for example frameworks, that might add some overhead on your project or app. For example, static frameworks will be linked statically to the final product, and a build phase is required to only copy the resources to the final product. Or dynamic frameworks, where Xcode will copy both the binary and the resources into the final product, but it'll increase the startup time of your app because the framework needs to be loaded dynamically. - **Prone to runtime errors**: Resources are identified by their name and extension (strings). Therefore, a typo in any of those will lead to a runtime error when trying to access the resource. This is not ideal because it's not caught at compile time and might lead to crashes in release. Tuist solves the problems above by **synthesizing a unified interface to access bundles and resources** that abstracts away the implementation details. > [!IMPORTANT] RECOMMENDED > Even though accessing resources through the Tuist-synthesized interface is not mandatory, we recommend it because it makes the code easier to reason about and the resources to move around. ## Resources {#resources} Tuist provides interfaces to declare the content of files such as `Info.plist` or entitlements in Swift. This is useful to ensure consistency across targets and projects, and leverage the compiler to catch issues at compile time. You can also come up with your own abstractions to model the content and share it across targets and projects. When your project is generated, Tuist will synthesize the content of those files and write them into the `Derived` directory relative to the directory containing the project that defines them. > [!TIP] GITIGNORE THE DERIVED DIRECTORY > We recommend adding the `Derived` directory to the `.gitignore` file of your project. ## Bundle accessors {#bundle-accessors} Tuist synthesizes an interface to access the bundle that contains the target resources. ### Swift {#swift} The target will contain an extension of the `Bundle` type that exposes the bundle: ```swift let bundle = Bundle.module ``` ### Objective-C {#objectivec} In Objective-C, you'll get an interface `{Target}Resources` to access the bundle: ```objc NSBundle *bundle = [MyFeatureResources bundle]; ``` > [!TIP] SUPPORTING RESOURCES IN LIBRARIES THROUGH BUNDLES > If a target product, for example a library, doesn't support resources, Tuist will include the resources in a target of product type `bundle` ensuring that it ends up in the final product and that the interface points to the right bundle. ## Resource accessors {#resource-accessors} Resources are identified by their name and extension using strings. This is not ideal because it's not caught at compile time and might lead to crashes in release. To prevent that, Tuist integrates [SwiftGen](https://github.com/SwiftGen/SwiftGen) into the project generation process to synthesize an interface to access the resources. Thanks to that, you can confidently access the resources leveraging the compiler to catch any issues. Tuist includes [templates](https://github.com/tuist/tuist/tree/main/Sources/TuistGenerator/Templates) to synthesize accessors for the following resource types by default: | Resource type | Synthesized file | | --- | ---- | | Images and colors | `Assets+{Target}.swift` | | Strings | `Strings+{Target}.swift` | | Plists | `{NameOfPlist}.swift` | | Fonts | `Fonts+{Target}.swift` | | Files | `Files+{Target}.swift` | > Note: You can disable the synthesizing of resource accessors on a per-project basis by passing the `disableSynthesizedResourceAccessors` option to the project options. #### Custom templates {#custom-templates} If you want to provide your own templates to synthesize accessors to other resource types, which must be supported by [SwiftGen](https://github.com/SwiftGen/SwiftGen), you can create them at `Tuist/ResourceSynthesizers/{name}.stencil`, where the name is the camel-case version of the resource. | Resource | Template name | | --- | --- | | strings | `Strings.stencil` | | assets | `Assets.stencil` | | plists | `Plists.stencil` | | fonts | `Fonts.stencil` | | coreData | `CoreData.stencil` | | interfaceBuilder | `InterfaceBuilder.stencil` | | json | `JSON.stencil` | | yaml | `YAML.stencil` | | files | `Files.stencil` | If you want to configure the list of resource types to synthesize accessors for, you can use the `Project.resourceSynthesizers` property passing the list of resource synthesizers you want to use: ```swift let project = Project(resourceSynthesizers: [.string(), .fonts()]) ``` > [!NOTE] REFERENCE > You can check out [this fixture](https://github.com/tuist/tuist/tree/main/fixtures/ios_app_with_templates) to see an example of how to use custom templates to synthesize accessors to resources. --- URL: "/en/guides/develop/projects/plugins" LLMS_URL: "/en/guides/develop/projects/plugins.md" title: "Plugins" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to create and use plugins in Tuist to extend its functionality." --- # Plugins {#plugins} Plugins are a tool to share and reuse Tuist artifacts across multiple projects. The following artifacts are supported: - Project description helpers across multiple projects. - Templates across multiple projects. - Tasks across multiple projects. - Resource accessor template across multiple projects Note that plugins are designed to be a simple way to extend Tuist's functionality. Therefore there are **some limitations to consider**: - A plugin cannot depend on another plugin. - A plugin cannot depend on third-party Swift packages - A plugin cannot use project description helpers from the project that uses the plugin. If you need more flexibility, consider suggesting a feature for the tool or building your own solution upon Tuist's generation framework, [`TuistGenerator`](https://github.com/tuist/tuist/tree/main/Sources/TuistGenerator). ## Plugin types {#plugin-types} ### Project description helper plugin {#project-description-helper-plugin} A project description helper plugin is represented by a directory containing a `Plugin.swift` manifest file that declares the plugin's name and a `ProjectDescriptionHelpers` directory containing the helper Swift files. ::: code-group ```bash [Plugin.swift] import ProjectDescription let plugin = Plugin(name: "MyPlugin") ``` ```bash [Directory structure] . ├── ... ├── Plugin.swift ├── ProjectDescriptionHelpers └── ... ``` ::: ### Resource accessor templates plugin {#resource-accessor-templates-plugin} If you need to share synthesized resource accessors you can use this type of plugin. The plugin is represented by a directory containing a `Plugin.swift` manifest file that declares the plugin's name and a `ResourceSynthesizers` directory containing the resource accessor template files. ::: code-group ```bash [Plugin.swift] import ProjectDescription let plugin = Plugin(name: "MyPlugin") ``` ```bash [Directory structure] . ├── ... ├── Plugin.swift ├── ResourceSynthesizers ├───── Strings.stencil ├───── Plists.stencil ├───── CustomTemplate.stencil └── ... ``` ::: The name of the template is the [camel case](https://en.wikipedia.org/wiki/Camel_case) version of the resource type: | Resource type | Template file name | | ------------- | ------------------ | | Strings | Strings.stencil | | Assets | Assets.stencil | | Property Lists | Plists.stencil | | Fonts | Fonts.stencil | | Core Data | CoreData.stencil | | Interface Builder | InterfaceBuilder.stencil | | JSON | JSON.stencil | | YAML | YAML.stencil | When defining the resource synthesizers in the project, you can specify the plugin name to use the templates from the plugin: ```swift let project = Project(resourceSynthesizers: [.strings(plugin: "MyPlugin")]) ``` ### Task plugin {#task-plugin-badge-typewarning-textdeprecated-} > [!WARNING] DEPRECATED > Task plugins are deprecated. Check out [this blog post](https://tuist.dev/blog/2025/04/15/automation-in-swift-projects) if you are looking for an automation solution for your project. Tasks are `$PATH`-exposed executables that are invocable through the `tuist` command if they follow the naming convention `tuist-`. In earlier versions, Tuist provided some weak conventions and tools under `tuist plugin` to `build`, `run`, `test` and `archive` tasks represented by executables in Swift Packages, but we have deprecated this feature since it increases the maintenance burden and complexity of the tool. If you were using Tuist for distributing tasks, we recommend building your - You can continue using the `ProjectAutomation.xcframework` distributed with every Tuist release to have access to the project graph from your logic with `let graph = try Tuist.graph()`. The command uses sytem process to run the `tuist` command, and return the in-memory representation of the project graph. - To distribute tasks, we recommend including the a fat binary that supports the `arm64` and `x86_64` in GitHub releases, and using [Mise](https://mise.jdx.dev) as an installation tool. To instruct Mise on how to install your tool, you'll need a plugin repository. You can use [Tuist's](https://github.com/asdf-community/asdf-tuist) as a reference. - If you name your tool `tuist-{xxx}` and users can install it by running `mise install`, they can run it either invoking it directly, or through `tuist xxx`. > [!NOTE] THE FUTURE OF PROJECTAUTOMATION > We plan to consolidate the models of `ProjectAutomation` and `XcodeGraph` into a single backward-compatible framework that exposes the entirity of the project graph to the user. Moreover, we'll extract the generation logic into a new layer, `XcodeGraph` that you can also use from your own CLI. Think of it as building your own Tuist. ## Using plugins {#using-plugins} To use a plugin, you'll have to add it to your project's `Tuist.swift` manifest file: ```swift import ProjectDescription let tuist = Tuist( project: .tuist(plugins: [ .local(path: "/Plugins/MyPlugin") ]) ) ``` If you want to reuse a plugin across projects that live in different repositories, you can push your plugin to a Git repository and reference it in the `Tuist.swift` file: ```swift import ProjectDescription let tuist = Tuist( project: .tuist(plugins: [ .git(url: "https://url/to/plugin.git", tag: "1.0.0"), .git(url: "https://url/to/plugin.git", sha: "e34c5ba") ]) ) ``` After adding the plugins, `tuist install` will fetch the plugins in a global cache directory. > [!NOTE] NO VERSION RESOLUTION > As you might have noted, we don't provide version resolution for plugins. We recommend using Git tags or SHAs to ensure reproducibility. > [!TIP] PROJECT DESCRIPTION HELPERS PLUGINS > When using a project description helpers plugin, the name of the module that contains the helpers is the name of the plugin > ```swift > import ProjectDescription > import MyTuistPlugin > let project = Project.app(name: "MyCoolApp", platform: .iOS) > ``` --- URL: "/en/guides/develop/projects/manifests" LLMS_URL: "/en/guides/develop/projects/manifests.md" title: "Manifests" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the manifest files that Tuist uses to define projects and workspaces and configure the generation process." --- # Manifests {#manifests} Tuist defaults to Swift files as the primary way to define projects and workspaces and configure the generation process. These files are referred to as **manifest files** throughout the documentation. The decision of using Swift was inspired by the [Swift Package Manager](https://www.swift.org/documentation/package-manager/), which also uses Swift files to define packages. Thanks to the usage of Swift, we can leverage the compiler to validate the correctness of the content and reuse code across different manifest files, and Xcode to provide a first-class editing experience thanks to the syntax highlighting, auto-completion, and validation. > [!NOTE] CACHING > Since manifest files are Swift files that need to be compiled, Tuist caches the compilation results to speed up the parsing process. Therefore, you'll notice that the first time you run Tuist, it might take a bit longer to generate the project. Subsequent runs will be faster. ## Project.swift {#projectswift} The `Project.swift` manifest declares an Xcode project. The project gets generated in the same directory where the manifest file is located with the name indicated in the `name` property. ```swift // Project.swift let project = Project( name: "App", targets: [ // .... ] ) ``` > [!WARNING] ROOT VARIABLES > The only variable that should be at the root of the manifest is `let project = Project(...)`. If you need to reuse code across various parts of the manifest, you can use Swift functions. ## Workspace.swift {#workspaceswift} By default, Tuist generates an [Xcode Workspace](https://developer.apple.com/documentation/xcode/projects-and-workspaces) containing the project being generated and the projects of its dependencies. If for any reason you'd like to customize the workspace to add additional projects or include files and groups, you can do so by defining a `Workspace.swift` manifest. ```swift // Workspace.swift import ProjectDescription let workspace = Workspace( name: "App-Workspace", projects: [ "./App", // Path to directory containing the Project.swift file ] ) ``` > [!NOTE] > Tuist will resolve the dependency graph and include the projects of the dependencies in the workspace. You don't need to include them manually. This is necessary for the build system to resolve the dependencies correctly. ### Multi or mono-project {#multi-or-monoproject} A question that often comes up is whether to use a single project or multiple projects in a workspace. In a world without Tuist where a mono-project setup would lead to frequent Git conflicts the usage of workspaces is encouraged. However, since we don't recommend including the Tuist-generated Xcode projects in the Git repository, Git conflicts are not an issue. Therefore, the decision of using a single project or multiple projects in a workspace is up to you. In the Tuist project we lean on mono-projects because the cold generation time is faster (fewer manifest files to compile) and we leverage project description helpers as a unit of encapsulation. However, you might want to use Xcode projects as a unit of encapsulation to represent different domains of your application, which aligns more closely with the Xcode's recommended project structure. ## Tuist.swift {#tuistswift} Tuist provides sensible defaults to simplify project configuration. However, you can customize the configuration by defining a `Tuist.swift` at the root of the project, which is used by Tuist to determine the root of the project. ```swift import ProjectDescription let tuist = Tuist( project: .tuist(generationOptions: .options(enforceExplicitDependencies: true)) ) ``` --- URL: "/en/guides/develop/projects/inspect/implicit-dependencies" LLMS_URL: "/en/guides/develop/projects/inspect/implicit-dependencies.md" title: "Implicit imports" titleTemplate: ":title · Inspect · Projects · Develop · Guides · Tuist" description: "Learn how to use Tuist to find implicit imports." --- # Implicit imports {#implicit-imports} To alleviate the complexity of maintaining an Xcode project graph with raw Xcode project, Apple designed the build system in a way that allows dependencies to be implicitly defined. This means that a product, for example an app, can depend on a framework, even without declaring the dependency explicitly. At a small scale, this is is fine, but as the project graph grows in complexity, the implicitness might manifest as unreliable incremental builds or editor-based features such as previews or code completion. The problem is that you can't prevent implicit dependencies from happening. Any developer can add an `import` statement to their Swift code, and the implicit dependency will be created. This is where Tuist comes in. Tuist provides a command to inspect the implicit dependencies by statically analyzing the code in your project. The following command will output the implicit dependencies of your project: ```bash tuist inspect implicit-imports ``` If the command detects any implicit imports, it exits with an exit code other than zero. > [!TIP] VALIDATE IN CI > We strongly recommend to run this command as part of your continuous integration command every time new code is pushed upstream. > [!IMPORTANT] NOT ALL IMPLICIT CASES ARE DETECTED > Since Tuist relies on static code analysis to detect implicit dependencies, it might not catch all cases. For example, Tuist is unable to understand conditional imports through compiler directives in code. --- URL: "/en/guides/develop/projects/hashing" LLMS_URL: "/en/guides/develop/projects/hashing.md" title: "Hashing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about Tuist's hashing logic upon which features like binary caching and selective testing are built." --- # Hashing {#hashing} Features like caching or smart test execution require a way to determine whether a target has changed. Tuist calculates a hash for each target in the dependency graph to determine if a target has changed. The hash is calculated based on the following attributes: - The target's attributes (e.g., name, platform, product, etc.) - The target's files - The hash of the target's dependencies ### Cache attributes {#cache-attributes} Additionally, when calculating the hash for caching, we also hash the following attributes. #### Swift version {#swift-version} We hash the Swift version obtained from running the command `/usr/bin/xcrun swift --version` to prevent compilation errors due to Swift version mismatches between the targets and the binaries. > [!NOTE] MODULE STABILITY > Previous versions of binary caching relied on the `BUILD_LIBRARY_FOR_DISTRIBUTION` build setting to enable [module stability](https://www.swift.org/blog/library-evolution#enabling-library-evolution-support) and enable using binaries with any compiler version. However, it caused compilation issues in projects with targets that don't support module stability. Generated binaries are bound to the Swift version used to compile them, and the Swift version must match the one used to compile the project. #### Configuration {#configuration} The idea behind the flag `-configuration` was to ensure debug binaries were not used in release builds and viceversa. However, we are still missing a mechanism to remove the other configurations from the projects to prevent them from being used. ## Debugging {#debugging} If you notice non-deterministic behaviors when using the caching across environments or invocations, it might be related to differences across the environments or a bug in the hashing logic. We recommend following these steps to debug the issue: 1. Ensure the same [configuration](#configuration) and [Swift version](#swift-version) is used across environments. 2. Check if there are differences between the Xcode projects generated by two consecutive invocations of `tuist generate` or across environments. You can use the `diff` command to compare the projects. The generated projects might include **absolute paths** causing the hashing logic to be non-deterministic. > [!NOTE] BETTER DEBUGGING EXPERIENCE PLANNED > Improving our debugging experience is in our roadmap. The print-hashes command, which lacks the context to understand the differences, will be replaced by a more user-friendly command that uses a tree-like structure to show the differences between the hashes. --- URL: "/en/guides/develop/projects/editing" LLMS_URL: "/en/guides/develop/projects/editing.md" title: "Editing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to use Tuist's edit workflow to declare your project leveraging Xcode's build system and editor capabilities." --- # Editing {#editing} Unlike traditional Xcode projects or Swift Packages, where changes are done through Xcode's UI, Tuist-managed projects are defined in Swift code contained in **manifest files**. If you're familiar with Swift Packages and the `Package.swift` file, the approach is very similar. You could edit these files using any text editor, but we recommend to use Tuist-provided workflow for that, `tuist edit`. The workflow creates an Xcode project that contains all manifest files and allows you to edit and compile them. Thanks to using Xcode, you get all the benefits of **code completion, syntax highlighting, and error checking**. ## Edit the project {#edit-the-project} To edit your project, you can run the following command in a Tuist project directory or a sub-directory: ```bash tuist edit ``` The command creates an Xcode project in a global directory and opens it in Xcode. The project includes a `Manifests` directory that you can build to ensure all your manifests are valid. > [!INFO] GLOB-RESOLVED MANIFESTS > `tuist edit` resolves the manifests to be included by using the glob `**/{Manifest}.swift` from the project's root directory (the one containing the `Tuist.swift` file). Make sure there's a valid `Tuist.swift` at the root of the project. ## Edit and generate workflow {#edit-and-generate-workflow} As you might have noticed, the editing can't be done from the generated Xcode project. That's by design to prevent the generated project from having a dependency on Tuist, ensuring you can move from Tuist in the future with little effort. When iterating on a project, we recommend running `tuist edit` from a terminal session to get an Xcode project to edit the project, and use another terminal session to run `tuist generate`. --- URL: "/en/guides/develop/projects/dynamic-configuration" LLMS_URL: "/en/guides/develop/projects/dynamic-configuration.md" title: "Dynamic configuration" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how how to use environment variables to dynamically configure your project." --- # Dynamic configuration {#dynamic-configuration} There are certain scenarios where you might need to dynamically configure your project at generation time. For example, you might want to change the name of the app, the bundle identifier, or the deployment target based on the environment where the project is being generated. Tuist supports that via environment variables, which can be accessed from the manifest files. ## Configuration through environment variables {#configuration-through-environment-variables} Tuist allows passing configuration through environment variables that can be accessed from the manifest files. For example: ```bash TUIST_APP_NAME=MyApp tuist generate ``` If you want to pass multiple environment variables just separate them with a space. For example: ```bash TUIST_APP_NAME=MyApp TUIST_APP_LOCALE=pl tuist generate ``` ## Reading the environment variables from manifests {#reading-the-environment-variables-from-manifests} Variables can be accessed using the `Environment` type. Any variables following the convention `TUIST_XXX` defined in the environment or passed to Tuist when running commands will be accessible using the `Environment` type. The following example shows how we access the `TUIST_APP_NAME` variable: ```swift func appName() -> String { if case let .string(environmentAppName) = Environment.appName { return environmentAppName } else { return "MyApp" } } ``` Accessing variables returns an instance of type `Environment.Value?` which can take any of the following values: | Case | Description | | --- | --- | | `.string(String)` | Used when the variable represents a string. | You can also retrieve the string or boolean `Environment` variable using either of the helper methods defined below, these methods require a default value to be passed to ensure the user gets consistent results each time. This avoids the need to define the function appName() defined above. ::: code-group ```swift [String] Environment.appName.getString(default: "TuistServer") ``` ```swift [Boolean] Environment.isCI.getBoolean(default: false) ``` ::: --- URL: "/en/guides/develop/projects/directory-structure" LLMS_URL: "/en/guides/develop/projects/directory-structure.md" title: "Directory structure" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the structure of Tuist projects and how to organize them." --- # Directory structure {#directory-structure} Although Tuist projects are commonly used to supersede Xcode projects, they are not limited to this use case. Tuist projects are also used to generate other types of projects, such as SPM packages, templates, plugins, and tasks. This document describes the structure of Tuist projects and how to organize them. In later sections, we'll cover how to define templates, plugins, and tasks. ## Standard Tuist projects {#standard-tuist-projects} Tuist projects are **the most common type of project generated by Tuist.** They are used to build apps, frameworks, and libraries among others. Unlike Xcode projects, Tuist projects are defined in Swift, which makes them more flexible and easier to maintain. Tuist projects are also more declarative, which makes them easier to understand and reason about. The following structure shows a typical Tuist project that generates an Xcode project: ```bash Tuist.swift Tuist/ Package.swift ProjectDescriptionHelpers/ Projects/ App/ Project.swift Feature/ Project.swift Workspace.swift ``` - **Tuist directory:** This directory has two purposes. First, it signals **where the root of the project is**. This allows constructing paths relative to the root of the project, and also running Tuist commands from any directory within the project. Second, it's the container for the following files: - **ProjectDescriptionHelpers:** This directory contains Swift code that's shared across all the manifest files. Manifest files can `import ProjectDescriptionHelpers` to use the code defined in this directory. Sharing code is useful to avoid duplications and ensure consistency across the projects. - **Package.swift:** This file contains Swift Package dependencies for Tuist to integrate them using Xcode projects and targets (like [CocoaPods](https://cococapods)) that are configurable and optimizable. Learn more here. - **Root directory**: The root directory of your project that also contains the `Tuist` directory. - Tuist.swift: This file contains configuration for Tuist that's shared across all the projects, workspaces, and environments. For example, it can be used to disable automatic generation of schemes, or to define the deployment target of the projects. - Workspace.swift: This manifest represents an Xcode workspace. It's used to group other projects and can also add additional files and schemes. - Project.swift: This manifest represents an Xcode project. It's used to define the targets that are part of the project, and their dependencies. When interacting with the above project, commands expect to find either a `Workspace.swift` or a `Project.swift` file in the working directory or the directory indicated via the `--path` flag. The manifest should be in a directory or subdirectory of a directory containing a `Tuist` directory, which represents the root of the project. > [!TIP] > Xcode workspaces allowed splitting projects into multiple Xcode projects to reduce the likelihood of merge conflicts. If that's what you were using workspaces for, you don't need them in Tuist. Tuist auto-generates a workspace containing a project and its dependencies' projects. ## Swift Package {#swift-package-badge-typewarning-textbeta-} Tuist also supports SPM package projects. If you are working on an SPM package, you shouldn't need to update anything. Tuist automatically picks up on your root `Package.swift` and all the features of Tuist work as if it was a `Project.swift` manifest. To get started, run `tuist install` and `tuist generate` in your SPM package. Your project should now have all the same schemes and files that you would see in the vanilla Xcode SPM integration. However, now you can also run `tuist cache` and have majority of your SPM dependencies and modules precompiled, making subsequent builds extremely fast. --- URL: "/en/guides/develop/projects/dependencies" LLMS_URL: "/en/guides/develop/projects/dependencies.md" title: "Dependencies" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to declare dependencies in your Tuist project." --- # Dependencies {#dependencies} When a project grows, it's common to split it into multiple targets to share code, define boundaries, and improve build times. Multiple targets means defining dependencies between them forming a **dependency graph**, which might include external dependencies as well. ## XcodeProj-codified graphs {#xcodeprojcodified-graphs} Due to Xcode and XcodeProj's design, the maintenance of a dependency graph can be a tedious and error-prone task. Here are some examples of the problems that you might encounter: - Because Xcode's build system outputs all the project's products into the same directory in derived data, targets might be able to import products that they shouldn't. Compilations might fail on CI, where clean builds are more common, or later on when a different configuration is used. - The transitive dynamic dependencies of a target need to be copied into any of the directories that are part of the `LD_RUNPATH_SEARCH_PATHS` build setting. If they aren't, the target won't be able to find them at runtime. This is easy to think about and set up when the graph is small, but it becomes a problem as the graph grows. - When a target links a static [XCFramework](https://developer.apple.com/documentation/xcode/creating-a-multi-platform-binary-framework-bundle), the target needs an additional build phase for Xcode to process the bundle and extract the right binary for the current platform and architecture. This build phase is not added automatically, and it's easy to forget to add it. The above are just a few examples, but there are many more that we've encountered over the years. Imagine if you required a team of engineers to maintain a dependency graph and ensure its validity. Or even worse, that the intricacies were resolved at build-time by a closed-source build system that you can't control or customize. Sounds familiar? This is the approach that Apple took with Xcode and XcodeProj and that the Swift Package Manager has inherited. We strongly believe that the dependency graph should be **explicit** and **static** because only then can it be **validated** and **optimized**. With Tuist, you focus on describing what depends on what, and we take care of the rest. The intricacies and implementation details are abstracted away from you. In the following sections you'll learn how to declare dependencies in your project. > [!TIP] GRAPH VALIDATION > Tuist validates the graph when generating the project to ensure that there are no cycles and that all the dependencies are valid. Thanks to this, any team can take part in evolving the dependency graph without worrying about breaking it. ## Local dependencies {#local-dependencies} Targets can depend on other targets in the same and different projects, and on binaries. When instantiating a `Target`, you can pass the `dependencies` argument with any of the following options: - `Target`: Declares a dependency with a target within the same project. - `Project`: Declares a dependency with a target in a different project. - `Framework`: Declares a dependency with a binary framework. - `Library`: Declares a dependency with a binary library. - `XCFramework`: Declares a dependency with a binary XCFramework. - `SDK`: Declares a dependency with a system SDK. - `XCTest`: Declares a dependency with XCTest. > [!NOTE] DEPENDENCY CONDITIONS > Every dependency type accepts a `condition` option to conditionally link the dependency based on the platform. By default, it links the dependency for all platforms the target supports. ## External dependencies {#external-dependencies} Tuist also allows you to declare external dependencies in your project. ### Swift Packages {#swift-packages} Swift Packages are our recommended way of declaring dependencies in your project. You can integrate them using Xcode's default integration mechanism or using Tuist's XcodeProj-based integration. #### Tuist's XcodeProj-based integration {#tuists-xcodeprojbased-integration} Xcode's default integration while being the most convenient one, lacks flexibility and control that's required for medium and large projects. To overcome this, Tuist offers an XcodeProj-based integration that allows you to integrate Swift Packages in your project using XcodeProj's targets. Thanks to that, we can not only give you more control over the integration but also make it compatible with workflows like caching and smart test runs. XcodeProj's integration is more likely to take more time to support new Swift Package features or handle more package configurations. However, the mapping logic between Swift Packages and XcodeProj targets is open-source and can be contributed to by the community. This is contrary to Xcode's default integration, which is closed-source and maintained by Apple. To add external dependencies, you'll have to create a `Package.swift` either under `Tuist/` or at the root of the project. ::: code-group ```swift [Tuist/Package.swift] // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription import ProjectDescriptionHelpers let packageSettings = PackageSettings( productTypes: [ "Alamofire": .framework, // default is .staticFramework ] ) #endif let package = Package( name: "PackageName", dependencies: [ .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), ], targets: [ .binaryTarget( name: "Sentry", url: "https://github.com/getsentry/sentry-cocoa/releases/download/8.40.1/Sentry.xcframework.zip", checksum: "db928e6fdc30de1aa97200576d86d467880df710cf5eeb76af23997968d7b2c7" ), ] ) ``` ::: > [!TIP] PACKAGE SETTINGS > The `PackageSettings` instance wrapped in a compiler directive allows you to configure how packages are integrated. For example, in the example above it's used to override the default product type used for packages. By default, you shouldn't need it. The `Package.swift` file is just an interface to declare external dependencies, nothing else. That's why you don't define any targets or products in the package. Once you have the dependencies defined, you can run the following command to resolve and pull the dependencies into the `Tuist/Dependencies` directory: ```bash tuist install # Resolving and fetching dependencies. {#resolving-and-fetching-dependencies} # Installing Swift Package Manager dependencies. {#installing-swift-package-manager-dependencies} ``` As you might have noticed, we take an approach similar to [CocoaPods](https://cocoapods.org)', where the resolution of dependencies is its own command. This gives control to the users over when they'd like dependencies to be resolved and updated, and allows opening the Xcode in project and have it ready to compile. This is an area where we believe the developer experience provided by Apple's integration with the Swift Package Manager degrates over time as the project grows. From your project targets you can then reference those dependencies using the `TargetDependency.external` dependency type: ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "App", organizationName: "tuist.io", targets: [ .target( name: "App", destinations: [.iPhone], product: .app, bundleId: "io.tuist.app", deploymentTargets: .iOS("13.0"), infoPlist: .default, sources: ["Targets/App/Sources/**"], dependencies: [ .external(name: "Alamofire"), // [!code ++] ] ), ] ) ``` ::: > [!NOTE] NO SCHEMES GENERATED FOR EXTERNAL PACKAGES > The **schemes** are not automatically created for Swift Package projects to keep the schemes list clean. You can create them via Xcode's UI. #### Xcode's default integration {#xcodes-default-integration} If you want to use Xcode's default integration mechanism, you can pass the list `packages` when instantiating a project: ```swift let project = Project(name: "MyProject", packages: [ .remote(url: "https://github.com/krzyzanowskim/CryptoSwift", requirement: .exact("1.8.0")) ]) ``` And then reference them from your targets: ```swift let target = .target(name: "MyTarget", dependencies: [ .package(product: "CryptoSwift", type: .runtime) ]) ``` For Swift Macros and Build Tool Plugins, you'll need to use the types `.macro` and `.plugin` respectively. > [!WARNING] SPM Build Tool Plugins > SPM build tool plugins must be declared using [Xcode's default integration](#xcode-s-default-integration) mechanism, even when using Tuist's [XcodeProj-based integration](#tuist-s-xcodeproj-based-integration) for your project dependencies. A practical application of an SPM build tool plugin is performing code linting during Xcode's "Run Build Tool Plug-ins" build phase. In a package manifest this is defined as follows: ```swift // swift-tools-version: 5.9 import PackageDescription let package = Package( name: "Framework", products: [ .library(name: "Framework", targets: ["Framework"]), ], dependencies: [ .package(url: "https://github.com/SimplyDanny/SwiftLintPlugins", .upToNextMajor(from: "0.56.1")), ], targets: [ .target( name: "Framework", plugins: [ .plugin(name: "SwiftLint", package: "SwiftLintPlugin"), ] ), ] ) ``` To generate an Xcode project with the build tool plugin intact, you must declare the package in the project manifest's `packages` array, and then include a package with type `.plugin` in a target's dependencies. ```swift import ProjectDescription let project = Project( name: "Framework", packages: [ .remote(url: "https://github.com/SimplyDanny/SwiftLintPlugins", requirement: .upToNextMajor(from: "0.56.1")), ], targets: [ .target( name: "Framework", dependencies: [ .package(product: "SwiftLintBuildToolPlugin", type: .plugin), ] ), ] ) ``` ### Carthage {#carthage} Since [Carthage](https://github.com/carthage/carthage) outputs `frameworks` or `xcframeworks`, you can run `carthage update` to output the dependencies in the `Carthage/Build` directory and then use the `.framework` or `.xcframework` target dependency type to declare the dependency in your target. You can wrap this in a script that you can run before generating the project. ```bash #!/usr/bin/env bash carthage update tuist generate ``` > [!WARNING] BUILD AND TEST > If you build and test your project through `tuist build` and `tuist test`, you will similarly need to ensure that the Carthage-resolved dependencies are present by running the `carthage update` command before `tuist build` or `tuist test` are run. ### CocoaPods {#cocoapods} [CocoaPods](https://cocoapods.org) expects an Xcode project to integrate the dependencies. You can use Tuist to generate the project, and then run `pod install` to integrate the dependencies by creating a workspace that contains your project and the Pods dependencies. You can wrap this in a script that you can run before generating the project. ```bash #!/usr/bin/env bash tuist generate pod install ``` > [!WARNING] > CocoaPods dependencies are not compatible with workflows like `build` or `test` that run `xcodebuild` right after generating the project. They are also incompatible with binary caching and selective testing since the fingerprinting logic doesn't account for the Pods dependencies. ## Static or dynamic {#static-or-dynamic} Frameworks and libraries can be linked either statically or dynamically, **a choice that has significant implications for aspects like app size and boot time**. Despite its importance, this decision is often made without much consideration. The **general rule of thumb** is that you want as many things as possible to be statically linked in release builds to achieve fast boot times, and as many things as possible to be dynamically linked in debug builds to achieve fast iteration times. The challenge with changing between static and dynamic linking in a project graph is that is not trivial in Xcode because a change has cascading effect on the entire graph (e.g. libraries can't contain resources, static frameworks don't need to be embedded). Apple tried to solve the problem with compile time solutions like Swift Package Manager's automatic decision between static and dynamic linking, or [Mergeable Libraries](https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries). However, this adds new dynamic variables to the compilation graph, adding new sources of non-determinism, and potentially causing some features like Swift Previews that rely on the compilation graph to become unreliable. Luckily, Tuist conceptually compresses the complexity associated with changing between static and dynamic and synthesizes bundle accessors that are standard across linking types. In combination with dynamic configurations via environment variables, you can pass the linking type at invocation time, and use the value in your manifests to set the product type of your targets. ```swift // Use the value returned by this function to set the product type of your targets. func productType() -> Product { if case let .string(linking) = Environment.linking { return linking == "static" ? .staticFramework : .framework } else { return .framework } } ``` Note that Tuist does not default to convenience through implicit configuration due to its costs. What this means is that we rely on you setting the linking type and any additional build settings that are sometimes required, like the [`-ObjC` linker flag](https://github.com/pointfreeco/swift-composable-architecture/discussions/1657#discussioncomment-4119184), to ensure the resulting binaries are correct. Therefore, the stance that we take is providing you with the resources, usually in the shape of documentation, to make the right decisions. > [!TIP] EXAMPLE: COMPOSABLE ARCHITECTURE > A Swift Package that many projects integrate is [Composable Architecture](https://github.com/pointfreeco/swift-composable-architecture). As described [here](https://github.com/pointfreeco/swift-composable-architecture/discussions/1657#discussioncomment-4119184) and the [troubleshooting section](#troubleshooting), you'll need to set the `OTHER_LDFLAGS` build setting to `$(inherited) -ObjC` when linking the packages statically, which is Tuist's default linking type. Alternatively, you can override the product type for the package to be dynamic. ### Scenarios {#scenarios} There are some scenarios where setting the linking entirely to static or dynamic is not feasible or a good idea. The following is a non-exhaustive list of scenarios where you might need to mix static and dynamic linking: - **Apps with extensions:** Since apps and their extensions need to share code, you might need to make those targets dynamic. Otherwise, you'll end up with the same code duplicated in both the app and the extension, causing the binary size to increase. - **Pre-compiled external dependencies:** Sometimes you are provided with pre-compiled binaries that are either static or dynamic. Static binaries can be wrapped in dynamic frameworks or libraries to be linked dynamically. When making changes to the graph, Tuist will analyze it and display a warning if it detects a "static side effect". This warning is meant to help you identify issues that might arise from linking a target statically that depends transitively on a static target through dynamic targets. These side effects often manifest as increased binary size or, in the worst cases, runtime crashes. ## Troubleshooting {#troubleshooting} ### Objective-C Dependencies {#objectivec-dependencies} When integrating Objective-C dependencies, the inclusion of certain flags on the consuming target may be necessary to avoid runtime crashes as detailed in [Apple Technical Q&A QA1490](https://developer.apple.com/library/archive/qa/qa1490/_index.html). Since the build system and Tuist have no way of inferring whether the flag is necessary or not, and since the flag comes with potentially undesirable side effects, Tuist will not automatically apply any of these flags, and because Swift Package Manager considers `-ObjC` to be included via an `.unsafeFlag` most packages cannot include it as part of their default linking settings when required. Consumers of Objective-C dependencies (or internal Objective-C targets) should apply `-ObjC` or `-force_load` flags when required by setting `OTHER_LDFLAGS` on consuming targets. ### Firebase & Other Google Libraries {#firebase-other-google-libraries} Google's open source libraries — while powerful — can be difficult to integrate within Tuist as they often use non-standard architecture and techniques in how they are built. Here are a few tips that may be necessary to follow to integrate Firebase and Google's other Apple-platform libraries: #### Ensure `-ObjC` is added to `OTHER_LDFLAGS` {#ensure-objc-is-added-to-other_ldflags} Many of Google's libraries are written in Objective-C. Because of this, any consuming target will need to include the `-ObjC` tag in its `OTHER_LDFLAGS` build setting. This can either be set in an `.xcconfig` file or manually specified in the target's settings within your Tuist manifests. An example: ```swift Target.target( ... settings: .settings( base: ["OTHER_LDFLAGS": "$(inherited) -ObjC"] ) ... ) ``` Refer to the [Objective-C Dependencies](#objective-c-dependencies) section above for more details. #### Set the product type for `FBLPromises` to dynamic framework {#set-the-product-type-for-fblpromises-to-dynamic-framework} Certain Google libraries depend on `FBLPromises`, another of Google's libraries. You may encounter a crash that mentions `FBLPromises`, looking something like this: ``` NSInvalidArgumentException. Reason: -[FBLPromise HTTPBody]: unrecognized selector sent to instance 0x600000cb2640. ``` Explicitly setting the product type of `FBLPromises` to `.framework` in your `Package.swift` file should fix the issue: ```swift [Tuist/Package.swift] // swift-tools-version: 5.10 import PackageDescription #if TUIST import ProjectDescription import ProjectDescriptionHelpers let packageSettings = PackageSettings( productTypes: [ "FPLPromises": .framework, ] ) #endif let package = Package( ... ``` ### Transitive static dependencies leaking through `.swiftmodule` {#transitive-static-dependencies-leaking-through-swiftmodule} When a dynamic framework or library depends on static ones through `import StaticSwiftModule`, the symbols are included in the `.swiftmodule` of the dynamic framework or library, potentially [causing the compilation to fail](https://forums.swift.org/t/compiling-a-dynamic-framework-with-a-statically-linked-library-creates-dependencies-in-swiftmodule-file/22708/1). To prevent that, you'll have to import the static dependency using [`@_implementationOnly`](https://github.com/apple/swift/blob/main/docs/ReferenceGuides/UnderscoredAttributes.md#_implementationonly): ```swift @_implementationOnly import StaticModule ``` --- URL: "/en/guides/develop/projects/cost-of-convenience" LLMS_URL: "/en/guides/develop/projects/cost-of-convenience.md" title: "The cost of convenience" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the cost of convenience in Xcode and how Tuist helps you prevent the issues that come with it." --- # The cost of convenience {#the-cost-of-convenience} Designing a code editor that the spectrum **from small to large-scale projects can use** is a challenging task. Many tools approach the problem by layering their solution and providing extensibility. The bottom-most layer is very low-level and close to the underlying build system, and the top-most layer is a high-level abstraction that's convenient to use but less flexible. By doing so, they make the simple things easy, and everything else possible. However, **[Apple](https://www.apple.com) decided to take a different approach with Xcode**. The reason is unknown, but it's likely that optimizing for the challenges of large-scale projects has never been their goal. They overinvested in convenience for small projects, provided little flexibility, and strongly coupled the tools with the underlying build system. To achieve the convenience, they provide sensible defaults, which you can easily replace, and added a lot of implicit build-time-resolved behaviors that are the culprit of many issues at scale. ## Explicitness and scale {#explicitness-and-scale} When working at scale, **explicitness is key**. It allows the build system to analyze and understand the project structure and dependencies ahead of time, and perform optimizations that would be impossible otherwise. The same explicitness is also key in ensuring that editor features such as [SwiftUI previews](https://developer.apple.com/documentation/swiftui/previews-in-xcode) or [Swift Macros](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros/) work reliably and predictably. Because Xcode and Xcode projects embraced implicitness as a valid design choice to achieve convenience, a principle that the Swift Package Manager has inherited, the difficulties of using Xcode are also present in the Swift Package Manager. > [!INFO] THE ROLE OF TUIST > We could summarize Tuist's role as a tool that prevents implicitly-defined projects and leverages explicitness to provide a better developer experience (e.g. validations, optimizations). Tools like [Bazel](https://bazel.build) take it further by bringing it down to the build system level. This is an issue that's barely discussed in the community, but it's a significant one. While working on Tuist, we've noticed many organizations and developers thinking that the current challenges they face will be addressed by the [Swift Package Manager](https://www.swift.org/documentation/package-manager/), but what they don't realize is that because it's building on the same principles, even though it mitigates the so well-known Git conflicts, they degrade the developer experience in other areas and continue to make the projects non-optimizable. In the following sections, we'll discuss some real examples of how implicitness affects the developer experience and the project's health. The list is not exhaustive, but it should give you a good idea of the challenges that you might face when working with Xcode projects or Swift Packages. ## Convenience getting in your way {#convenience-getting-in-your-way} ### Shared built products directory {#shared-built-products-directory} Xcode uses a directory inside the derived data directory for each product. Inside it, it stores the build artifacts, such as the compiled binaries, the dSYM files, and the logs. Because all the products of a project go into the same directory, which is visible by default from other targets to link against, **you might end up with targets that implicitly depend on each other.** While this might not be a problem when having just a few targets, it might manifest as failing builds that are hard to debug when the project grows. The consequence of this design decision is that many projects acidentally compile with a graph that is not well-defined. > [!TIP] TUIST DETECTION OF IMPLICIT DEPENDENCIES > Tuist provides a command to detect implicit dependencies. You can use the command to validate in CI that all your dependencies are explicit. ### Find implicit dependencies in schemes {#find-implicit-dependencies-in-schemes} Defining and maintaining a dependency graph in Xcode gets harder as the project grows. It's hard because they are codified in the `.pbxproj` files as build phases and build settings, there are no tools to visualize and work with the graph, and the changes in the graph (e.g. adding a new dynamic precompiled framework), might require configuration changes upstream (e.g. adding a new build phase to copy the framework into the bundle). Apple decided at some point that instead of evolving the graph model into something more manageable, it'd make more sense to add an option to resolve implicit dependencies at build time. This is once again a questionable design choice because you might end up with slower build times or unpredictable builds. For example, a build might pass locally due to some state in derive data, which acts as a [singleton](https://en.wikipedia.org/wiki/Singleton_pattern), but then fail to compile on CI because the state is different. > [!TIP] > We recommend disabling this in your project schemes, and use like Tuist that eases the management of the dependency graph. ### SwiftUI Previews and static libraries/frameworks {#swiftui-previews-and-static-librariesframeworks} Some editor features like SwiftUI Previews or Swift Macros require the compilation of the dependency graph from the file that's being edited. This integration between the editor requires that the build system resolves any implicitness and output the right artifacts that are necessary for those features to work. As you can imagine, **the more implicit the graph is, the more challenging the task is for the build system**, and therefore it's not surprising that many of these features don't work reliably. We often hear from developers that they stopped using SwiftUI previews long time ago because they were too unreliable. Instead, they are using either example apps, or avoiding certaing things, like the usage of static libraries or script build phases, because they cause the feature to break. ### Mergeable libraries {#mergeable-libraries} Dynamic frameworks, while more flexible and easier to work with, have a negative impact in the launch time of apps. On the other side, static libraries are faster to launch, but impact the compilation time and are a bit harder to work with, specially in complex graph scenarios. *Wouldn't it be great if you could change between one or the other depending on the configuration?* That's what Apple must have thought when they decided to work on mergeable libraries. But once again, they moved more build-time inference to the build-time. If reasoning about a dependency graph, imagine having to do so when the static or dynamic nature of the target will be resolved at build-time based on some build settings in some targets. Good luck making that work reliably while ensuring features like SwiftUI previews don't break. **Many users come to Tuist wanting to use mergeable libraries and our answer is always the same. You don't need to.** You can control the static or dynamic nature of your targets at generation-time leading to a project whose graph is known ahead of compilation. No variables need to be resolved at build-time. ```bash # The value of TUIST_DYNAMIC can be read from the project {#the-value-of-tuist_dynamic-can-be-read-from-the-project} # to set the product as static or dynamic based on the value. {#to-set-the-product-as-static-or-dynamic-based-on-the-value} TUIST_DYNAMIC=1 tuist generate ``` ## Explicit, explicit, and explicit {#explicit-explicit-and-explicit} If there's an important non-written principle that we recommend every developer or organization that wants their development with Xcode to scale, is that they should embrace explicitness. And if explicitness is hard to manage with raw Xcode projects, they should consider something else, either [Tuist](https://tuist.io) or [Bazel](https://bazel.build). **Only then reliability, predicability, and optimizations will be possible.** ## Future {#future} Whether Apple will do something to prevent all the above issues is unknown. Their continuous decisions embedded into Xcode and the Swift Package Manager don't suggest that they will. Once you allow implicit configuration as a valid state, **it's hard to move from there without introducing breaking changes.** Going back to first principles and rethinking the design of the tools might lead to breaking many Xcode projects that accidentally compiled for years. Imagine the community uproar if that happened. Apple finds itself in a bit of a chicken-and-egg problem. Convenience is what helps developers get started quickly and build more apps for their ecosystem. But their decisions to make the experience convenience at that scale, is making it hard for them to ensure some of the Xcode features work reliably. Because the future is unknown, we try to **be as close as possible to the industry standards and Xcode projects**. We prevent the above issues, and leverage the knowledge that we have to provide a better developer experience. Ideally we wouldn't have to resort to project generation for that, but the lack of extensibility of Xcode and the Swift Package Manager make it the only viable option. And it's also a safe option because they'll have to break the Xcode projects to break Tuist projects. Ideally, **the build system was more extensible**, but wouldn't it be a bad idea to have plugins/extensions that contract with a world of implicitness? It doesn't seem like a good idea. So it seems like we'll need external tools like Tuist or [Bazel](https://bazel.build) to provide a better developer experience. Or maybe Apple will surprise us all and make Xcode more extensible and explicit... Until that happens, you have to choose whether you want to embrace the convencience of Xcode and take on the debt that comes with it, or trust us on this journey to provide a better developer experience. We won't disappoint you. --- URL: "/en/guides/develop/projects/code-sharing" LLMS_URL: "/en/guides/develop/projects/code-sharing.md" title: "Code sharing" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn how to share code across manifest files to reduce duplications and ensure consistency" --- # Code sharing {#code-sharing} One of the inconveniences of Xcode when we use it with large projects is that it doesn't allow reusing elements of the projects other than the build settings through `.xcconfig` files. Being able to reuse project definitions is useful for the following reasons: - It eases the **maintenance** because changes can be applied in one place and all the projects get the changes automatically. - It makes it possible to define **conventions** that new projects can conform to. - Projects are more **consistent** and therefore the likelihood of broken builds due inconsistencies is significantly less. - Adding a new projects becomes an easy task because we can reuse the existing logic. Reusing code across manifest files is possible in Tuist thanks to the concept of **project description helpers**. > [!TIP] A TUIST UNIQUE ASSET > Many organizations like Tuist because they see in project description helpers a platform for platform teams to codify their own conventions and come up with their own language for describing their projects. For example, YAML-based project generators have to come up with their own YAML-based propietary templating solution, or force organizations onto building their tools upon. ## Project description helpers {#project-description-helpers} Project description helpers are Swift files that get compiled into a module, `ProjectDescriptionHelpers`, that manifest files can import. The module is compiled by gathering all the files in the `Tuist/ProjectDescriptionHelpers` directory. You can import them into your manifest file by adding an import statement at the top of the file: ```swift // Project.swift import ProjectDescription import ProjectDescriptionHelpers ``` `ProjectDescriptionHelpers` are available in the following manifests: - `Project.swift` - `Package.swift` (only behind the `#TUIST` compiler flag) - `Workspace.swift` ## Example {#example} The snippets below contain an example of how we extend the `Project` model to add static constructors and how we use them from a `Project.swift` file: ::: code-group ```swift [Tuist/Project+Templates.swift] import ProjectDescription extension Project { public static func featureFramework(name: String, dependencies: [TargetDependency] = []) -> Project { return Project( name: name, targets: [ .target( name: name, destinations: .iOS, product: .framework, bundleId: "io.tuist.\(name)", infoPlist: "\(name).plist", sources: ["Sources/\(name)/**"], resources: ["Resources/\(name)/**",], dependencies: dependencies ), .target( name: "\(name)Tests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.\(name)Tests", infoPlist: "\(name)Tests.plist", sources: ["Sources/\(name)Tests/**"], resources: ["Resources/\(name)Tests/**",], dependencies: [.target(name: name)] ) ] ) } } ``` ```swift {2} [Project.swift] import ProjectDescription import ProjectDescriptionHelpers let project = Project.featureFramework(name: "MyFeature") ``` ::: > [!TIP] A TOOL TO ESTABLISH CONVENTIONS > Note how through the function we are defining conventions about the name of the targets, the bundle identifier, and the folders structure. --- URL: "/en/guides/develop/projects/best-practices" LLMS_URL: "/en/guides/develop/projects/best-practices.md" title: "Best practices" titleTemplate: ":title · Projects · Develop · Guides · Tuist" description: "Learn about the best practices for working with Tuist and Xcode projects." --- # Best practices {#best-practices} Over the years working with different teams and projects, we've identified a set of best practices that we recommend following when working with Tuist and Xcode projects. These practices are not mandatory, but they can help you structure your projects in a way that makes them easier to maintain and scale. ## Xcode {#xcode} ### Discouraged patterns {#discouraged-patterns} #### Configurations to model remote environments {#configurations-to-model-remote-environments} Many organizations use build configurations to model different remote environments (e.g., `Debug-Production` or `Release-Canary`), but this approach has some downsides: - **Inconsistencies:** If there are configuration inconsistencies throughout the graph, the build system might end up using the wrong configuration for some targets. - **Complexity:** Projects can end up with a long list of local configurations and remote environments that are hard to reason about and maintain. Build configurations were designed to embody different build settings, and projects rarely need more than just `Debug` and `Release`. The need to model different environments can be achieved differently: - **In Debug builds:** You can include all the configurations that should be accessible in development in the app (e.g. endpoints), and switch them at runtime. The switch can happen either using scheme launch environment variables, or with a UI within the app. - **In Release builds:** In case of release, you can only include the configuration that the release build is bound to, and not include the runtime logic for switching configurations by using compiler directives. --- URL: "/en/guides/develop/projects/adoption/swift-package" LLMS_URL: "/en/guides/develop/projects/adoption/swift-package.md" title: "Use Tuist with a Swift Package" titleTemplate: ":title · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to use Tuist with a Swift Package." --- # Using Tuist with a Swift Package {#using-tuist-with-a-swift-package-badge-typewarning-textbeta-} Tuist supports using `Package.swift` as a DSL for your projects and it converts your package targets into a native Xcode project and targets. > [!WARNING] > The aim of this feature is to provide an easy way for developers to assess the impact of adopting Tuist in their Swift Packages. Therefore, we don't plan to support the full range of Swift Package Manager features nor to bring every Tuist's unique features like project description helpers to the packages world. > [!NOTE] ROOT DIRECTORY > Tuist commands expect a certain directory structure whose root is identified by a `Tuist` or a `.git` directory. ## Using Tuist with a Swift Package {#using-tuist-with-a-swift-package} We are going to use Tuist with the [TootSDK Package](https://github.com/TootSDK/TootSDK) repository, which contains a Swift Package. The first thing that we need to do is to clone the repository: ```bash git clone https://github.com/TootSDK/TootSDK cd TootSDK ``` Once in the repository's directory, we need to install the Swift Package Manager dependencies: ```bash tuist install ``` Under the hood `tuist install` uses the Swift Package Manager to resolve and pull the dependencies of the package. After the resolution completes, you can then generate the project: ```bash tuist generate ``` Voilà! You have a native Xcode project that you can open and start working on. --- URL: "/en/guides/develop/projects/adoption/new-project" LLMS_URL: "/en/guides/develop/projects/adoption/new-project.md" title: "Create a new project" titleTemplate: ":title · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to create a new project with Tuist." --- # Create a new project {#create-a-new-project} The most straightforward way to start a new project with Tuist is to use the `tuist init` command. This command launches an interactive CLI that guides you through setting up your project. When prompted, make sure to select the option to create a "generated project". You can then edit the project running `tuist edit`, and Xcode will open a project where you can edit the project. One of the files that are generated is the `Project.swift`, which contains the definition of your project. If you are familiar with the Swift Package Manager, think of it as the `Package.swift` but with the lingo of Xcode projects. ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "MyApp", targets: [ .target( name: "MyApp", destinations: .iOS, product: .app, bundleId: "io.tuist.MyApp", infoPlist: .extendingDefault( with: [ "UILaunchScreen": [ "UIColorName": "", "UIImageName": "", ], ] ), sources: ["MyApp/Sources/**"], resources: ["MyApp/Resources/**"], dependencies: [] ), .target( name: "MyAppTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyAppTests", infoPlist: .default, sources: ["MyApp/Tests/**"], resources: [], dependencies: [.target(name: "MyApp")] ), ] ) ``` ::: > [!NOTE] > We intentionally keep the list of available templates short to minimize maintenance overhead. If you want to create a project that doesn't represent an application, for example a framework, you can use `tuist init` as a starting point and then modify the generated project to suit your needs. ## Manually creating a project {#manually-creating-a-project} Alternatively, you can create the project manually. We recommend doing this only if you're already familiar with Tuist and its concepts. The first thing that you'll need to do is to create additional directories for the project structure: ```bash mkdir MyFramework cd MyFramework ``` Then create a `Tuist.swift` file, which will configure Tuist and is used by Tuist to determine the root directory of the project, and a `Project.swift`, where your project will be declared: ::: code-group ```swift [Project.swift] import ProjectDescription let project = Project( name: "MyFramework", targets: [ .target( name: "MyFramework", destinations: .macOS, product: .framework, bundleId: "io.tuist.MyFramework", sources: ["MyFramework/Sources/**"], dependencies: [] ) ] ) ``` ```swift [Tuist.swift] import ProjectDescription let tuist = Tuist() ``` ::: > [!IMPORTANT] > Tuist uses the `Tuist/` directory to determine the root of your project, and from there it looks for other manifest files globbing the directories. We recommend creating those files with your editor of choice, and from that point on, you can use `tuist edit` to edit the project with Xcode. --- URL: "/en/guides/develop/projects/adoption/migrate/xcodegen-project" LLMS_URL: "/en/guides/develop/projects/adoption/migrate/xcodegen-project.md" title: "Migrate an XcodeGen project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate your projects from XcodeGen to Tuist." --- # Migrate an XcodeGen project {#migrate-an-xcodegen-project} [XcodeGen](https://github.com/yonaskolb/XcodeGen) is a project-generation tool that uses YAML as [a configuration format](https://github.com/yonaskolb/XcodeGen/blob/master/Docs/ProjectSpec.md) to define Xcode projects. Many organizations **adopted it trying to escape from the frequent Git conflicts that arise when working with Xcode projects.** However, frequent Git conflicts is just one of the many problems that organizations experience. Xcode exposes developers with a lot of intricacies and implicit configurations that make it hard to maintain and optimize projects at scale. XcodeGen falls short there by design because it's a tool that generates Xcode projects, not a project manager. If you need a tool that helps you beyond generating Xcode projects, you might want to consider Tuist. > [!TIP] SWIFT OVER YAML > Many organizations prefer Tuist as a project generation tool too because it uses Swift as a configuration format. Swift is a programming language that developers are familiar with, and that provides them with the convenience of using Xcode's autocompletion, type-checking, and validation features. What follows are some considerations and guidelines to help you migrate your projects from XcodeGen to Tuist. ## Project generation {#project-generation} Both Tuist and XcodeGen provide a `generate` command that turns your project declaration into Xcode projects and workspaces. ::: code-group ```bash [XcodeGen] xcodegen generate ``` ```bash [Tuist] tuist generate ``` ::: The difference lays in the editing experience. With Tuist, you can run the `tuist edit` command, which generates an Xcode project on the fly that you can open and start working on. This is particularly useful when you want to make quick changes to your project. ## `project.yaml` {#projectyaml} XcodeGen's `project.yaml` description file becomes `Project.swift`. Moreover, you can have `Workspace.swift` as a way to customize how projects are grouped in workspaces. You can also have a project `Project.swift` with targets that reference targets from other projects. In those cases, Tuist will generate an Xcode Workspace including all the projects. ::: code-group ```bash [XcodeGen directory structure] / project.yaml ``` ```bash [Tuist directory structure] / Tuist.swift Project.swift Workspace.swift ``` ::: > [!TIP] XCODE'S LANGUAGE > Both XcodeGen and Tuist embrace Xcode's language and concepts. However, Tuist's Swift-based configuration provides you with the convenience of using Xcode's autocompletion, type-checking, and validation features. ## Spec templates {#spec-templates} One of the disadvantages of YAML as a language for project configuration is that it doesn't support reusability across YAML files out of the box. This is a common need when describing projects, which XcodeGen had to solve with their own propietary solution named *"templates"*. With Tuist's re-usability is built into the language itself, Swift, and through a Swift module named project description helpers, which allow reusing code across all your manifest files. ::: code-group ```swift [Tuist/ProjectDescriptionHelpers/Target+Features.swift] import ProjectDescription extension Target { /** This function is a factory of targets that together represent a feature. */ static func featureTargets(name: String) -> [Target] { // ... } } ``` ```swift [Project.swift] import ProjectDescription import ProjectDescriptionHelpers // [!code highlight] let project = Project(name: "MyProject", targets: Target.featureTargets(name: "MyFeature")) // [!code highlight] ``` --- URL: "/en/guides/develop/projects/adoption/migrate/xcode-project" LLMS_URL: "/en/guides/develop/projects/adoption/migrate/xcode-project.md" title: "Migrate an Xcode project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate an Xcode project to a Tuist project." --- # Migrate an Xcode project {#migrate-an-xcode-project} Unless you create a new project using Tuist, in which case you get everything configured automatically, you'll have to define your Xcode projects using Tuist's primitives. How tedious this process is, depends on how complex your projects are. As you probably know, Xcode projects can become messy and complex over time: groups that don't match the directory structure, files that are shared across targets, or file references that point to nonexisting files (to mention some). All that accumulated complexity makes it hard for us to provide a command that reliably migrates project. Moreover, manual migration is an excellent exercise to clean up and simplify your projects. Not only the developers in your project will be thankful for that, but Xcode, who will be faster processing and indexing them. Once you have fully adopted Tuist, it will make sure that projects are consistently defined and that they remain simple. In the aim of easing that work, we are giving you some guidelines based on the feedback that we have received from the users. ## Create project scaffold {#create-project-scaffold} First of all, create a scaffold for your project with the following Tuist files: ::: code-group ```js [Tuist.swift] import ProjectDescription let tuist = Tuist() ``` ```js [Project.swift] import ProjectDescription let project = Project( name: "MyApp-Tuist", targets: [ /** Targets will go here **/ ] ) ``` ```js [Tuist/Package.swift] // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies ] ) ``` ::: `Project.swift` is the manifest file where you'll define your project, and `Package.swift` is the manifest file where you'll define your dependencies. The `Tuist.swift` file is where you can define project-scoped Tuist settings for your project. > [!TIP] PROJECT NAME WITH -TUIST SUFFIX > To prevent conflicts with the existing Xcode project, we recommend adding the `-Tuist` suffix to the project name. You can drop it once you've fully migrated your project to Tuist. ## Build and test the Tuist project in CI {#build-and-test-the-tuist-project-in-ci} To ensure the migration of each change is valid, we recommend extending your continuous integration to build and test the project generated by Tuist from your manifest file: ```bash tuist install tuist generate tuist build -- ...{xcodebuild flags} # or tuist test ``` ## Extract the project build settings into `.xcconfig` files {#extract-the-project-build-settings-into-xcconfig-files} Extract the build settings from the project into an `.xcconfig` file to make the project leaner and easier to migrate. You can use the following command to extract the build settings from the project into an `.xcconfig` file: ```bash mkdir -p xcconfigs/ tuist migration settings-to-xcconfig -p MyApp.xcodeproj -x xcconfigs/MyApp-Project.xcconfig ``` Then update your `Project.swift` file to point to the `.xcconfig` file you've just created: ```swift import ProjectDescription let project = Project( name: "MyApp", settings: .settings(configurations: [ .debug(name: "Debug", xcconfig: "./xcconfigs/MyApp-Project.xcconfig"), // [!code ++] .release(name: "Release", xcconfig: "./xcconfigs/MyApp-Project.xcconfig"), // [!code ++] ]), targets: [ /** Targets will go here **/ ] ) ``` Then extend your continuous integration pipeline to run the following command to ensure that changes to build settings are made directly to the `.xcconfig` files: ```bash tuist migration check-empty-settings -p Project.xcodeproj ``` ## Extract package dependencies {#extract-package-dependencies} Extract all your project's dependencies into the `Tuist/Package.swift` file: ```swift // swift-tools-version: 5.9 import PackageDescription #if TUIST import ProjectDescription let packageSettings = PackageSettings( // Customize the product types for specific package product // Default is .staticFramework // productTypes: ["Alamofire": .framework,] productTypes: [:] ) #endif let package = Package( name: "MyApp", dependencies: [ // Add your own dependencies here: // .package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"), // You can read more about dependencies here: https://docs.tuist.io/documentation/tuist/dependencies .package(url: "https://github.com/onevcat/Kingfisher", .upToNextMajor(from: "7.12.0")) // [!code ++] ] ) ``` > [!TIP] PRODUCT TYPES > You can override the product type for a specific package by adding it to the `productTypes` dictionary in the `PackageSettings` struct. By default, Tuist assumes that all packages are static frameworks. ## Determine the migration order {#determine-the-migration-order} We recommend migrating the targets from the one that is the most dependent upon to the least. You can use the following command to list the targets of a project, sorted by the number of dependencies: ```bash tuist migration list-targets -p Project.xcodeproj ``` Start migrating the targets from the top of the list, as they are the ones that are the most depended upon. ## Migrate targets {#migrate-targets} Migrate the targets one by one. We recommend doing a pull request for each target to ensure that the changes are reviewed and tested before merging them. ### Extract the target build settings into `.xcconfig` files {#extract-the-target-build-settings-into-xcconfig-files} Like you did with the project build settings, extract the target build settings into an `.xcconfig` file to make the target leaner and easier to migrate. You can use the following command to extract the build settings from the target into an `.xcconfig` file: ```bash tuist migration settings-to-xcconfig -p MyApp.xcodeproj -t TargetX -x xcconfigs/TargetX.xcconfig ``` ### Define the target in the `Project.swift` file {#define-the-target-in-the-projectswift-file} Define the target in `Project.targets`: ```swift import ProjectDescription let project = Project( name: "MyApp", settings: .settings(configurations: [ .debug(name: "Debug", xcconfig: "./xcconfigs/Project.xcconfig"), .release(name: "Release", xcconfig: "./xcconfigs/Project.xcconfig"), ]), targets: [ .target( // [!code ++] name: "TargetX", // [!code ++] destinations: .iOS, // [!code ++] product: .framework, // [!code ++] // or .staticFramework, .staticLibrary... bundleId: "io.tuist.targetX", // [!code ++] sources: ["Sources/TargetX/**"], // [!code ++] dependencies: [ // [!code ++] /** Dependencies go here **/ // [!code ++] /** .external(name: "Kingfisher") **/ // [!code ++] /** .target(name: "OtherProjectTarget") **/ // [!code ++] ], // [!code ++] settings: .settings(configurations: [ // [!code ++] .debug(name: "Debug", xcconfig: "./xcconfigs/TargetX.xcconfig"), // [!code ++] .debug(name: "Release", xcconfig: "./xcconfigs/TargetX.xcconfig"), // [!code ++] ]) // [!code ++] ), // [!code ++] ] ) ``` > [!NOTE] TEST TARGETS > If the target has an associated test target, you should define it in the `Project.swift` file as well repeating the same steps. ### Validate the target migration {#validate-the-target-migration} Run `tuist build` and `tuist test` to ensure the project builds and tests pass. Additionally, you can use [xcdiff](https://github.com/bloomberg/xcdiff) to compare the generated Xcode project with the existing one to ensure that the changes are correct. ### Repeat {#repeat} Repeat until all the targets are fully migrated. Once you are done, we recommend updating your CI and CD pipelines to build and test the project using `tuist build` and `tuist test` commands to benefit from the speed and reliability that Tuist provides. ## Troubleshooting {#troubleshooting} ### Compilation errors due to missing files. {#compilation-errors-due-to-missing-files} If the files associated to your Xcode project targets were not all contained in a file-system directory representing the target, you might end up with a project that doesn't compile. Make sure the list of files after generating the project with Tuist matches the list of files in the Xcode project, and take the opportunity to align the file structure with the target structure. --- URL: "/en/guides/develop/projects/adoption/migrate/swift-package" LLMS_URL: "/en/guides/develop/projects/adoption/migrate/swift-package.md" title: "Migrate a Swift Package" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate from Swift Package Manager as a solution for managing your projects to Tuist projects." --- # Migrate a Swift Package {#migrate-a-swift-package} Swift Package Manager emerged as a dependency manager for Swift code that uninentionally found itself solving the problem of managing projects and supporting other programming languages like Objective-C. Because the tool was designed with a different purpose in mind, it can be challenging to use it to manage projects at scale because it lacks flexibility, performance, and power that Tuist provides. This is well captured in the [Scaling iOS at Bumble](https://medium.com/bumble-tech/scaling-ios-at-bumble-239e0fa009f2) article, which includes the following table comparing the performance of Swift Package Manager and native Xcode projects: A table that compares the regression in performance when using SPM over native Xcode projects We often come across developers and organizations that challenge the need for Tuist considering that Swift Package Manager can take a similar project management role. Some venture into a migration to later on realize that their developer experience has degraded signicantly. For instance, the rename of a file might take up to 15 seconds to re-index. 15 seconds! **Whether Apple will make Swift Package Manager a built-for-scale project manager is uncertain.** However, we are not seeing any signs that it's happening. In fact, we are seeing quite the opposite. They are making Xcode-inspired decisions, like achieving convenience through implicit configurations, which as you might know, is the source of complications at scale. We believe it'd take Apple to go to first principles and revisit some decisions that made sense as a dependency manager but not as a project manager, for example the usage of a compiled language as an interface to define projects. > [!TIP] SPM AS JUST A DEPENDENCY MANAGER > Tuist treats Swift Package Manager as a dependency manager, and it's a great one. We use it to resolve dependencies and to build them. We don't use it to define projects because it's not designed for that. ## Migrating from Swift Package Manager to Tuist {#migrating-from-swift-package-manager-to-tuist} The similarities between Swift Package Manager and Tuist make the migration process straightforward. The main difference is that you'll be defining your projects using Tuist's DSL instead of `Package.swift`. First, create a `Project.swift` file next to your `Package.swift` file. The `Project.swift` file will contain the definition of your project. Here's an example of a `Project.swift` file that defines a project with a single target: ```swift import ProjectDescription let project = Project( name: "App", targets: [ .target( name: "App", destinations: .iOS, product: .app, bundleId: "io.tuist.App", sources: ["Sources/**/*.swift"]* ), ] ) ``` Some things to note: - **ProjectDescription**: Instead of using `PackageDescription`, you'll be using `ProjectDescription`. - **Project:** Instead of exporting a `package` instance, you'll be exporting a `project` instance. - **Xcode language:** The primitives that you use to define your project mimic Xcode's language, so you'll find schemes, targets, and build phases among others. Then create a `Tuist.swift` file with the following content: ```swift import ProjectDescription let tuist = Tuist() ``` The `Tuist.swift` contains the configuration for your project and its path serves as a reference to determine the root of your project. You can check out the directory structure document to learn more about the structure of Tuist projects. ## Editing the project {#editing-the-project} You can use `tuist edit` to edit the project in Xcode. The command will generate an Xcode project that you can open and start working on. ```bash tuist edit ``` Depending on the size of the project, you might consider using it in one shot or incrementally. We recommend starting with a small project to get familiar with the DSL and the workflow. Our advise is always to start from the most depended upon target and work all the way up to the top-level target. --- URL: "/en/guides/develop/projects/adoption/migrate/bazel-project" LLMS_URL: "/en/guides/develop/projects/adoption/migrate/bazel-project.md" title: "Migrate a Bazel project" titleTemplate: ":title · Migrate · Adoption · Projects · Develop · Guides · Tuist" description: "Learn how to migrate your projects from Bazel to Tuist." --- # Migrate a Bazel project {#migrate-a-bazel-project} [Bazel](https://bazel.build) is a build system that Google open-sourced in 2015. It's a powerful tool that allows you to build and test software of any size, quickly and reliably. Some large organizations like [Spotify](https://engineering.atspotify.com/2023/10/switching-build-systems-seamlessly/), [Tinder](https://medium.com/tinder/bazel-hermetic-toolchain-and-tooling-migration-c244dc0d3ae), or [Lyft](https://semaphoreci.com/blog/keith-smiley-bazel) use it, however, it requires an upfront (i.e., learning the technology) and ongoing investment (i.e., keeping up with Xcode updates) to introduce and maintain. While this works for some organizations that treat it as a cross-cutting concern, it might not be the best fit for others that want to focus on their product development. For instance, we've seen organizations whose iOS platform team introduced Bazel and had to drop it after the engineers that led the effort left the company. Apple's stance on the strong coupling between Xcode and the build system is another factor that makes it hard to maintain Bazel projects over time. > [!TIP] TUIST UNIQUENESS LIES IN ITS FINESSE > Instead of fighting Xcode and Xcode projects, Tuist embraces it. It's the same concepts (e.g., targets, schemes, build settings), a familiar language (i.e., Swift), and a simple and enjoyable experience that makes maintaining and scaling projects everyone's job and not just the iOS platform team's. ## Rules {#rules} Bazel uses rules to define how to build and test software. The rules are written in [Starlark](https://github.com/bazelbuild/starlark), a Python-like language. Tuist uses Swift as a configuration language, which provides developers with the convenience of using Xcode's autocompletion, type-checking, and validation features. For example, the following rule describes how to build a Swift library in Bazel: ::: code-group ```txt [BUILD (Bazel)] swift_library( name = "MyLibrary.library", srcs = glob(["**/*.swift"]), module_name = "MyLibrary" ) ``` ```swift [Project.swift (Tuist)] let project = Project( // ... targets: [ .target(name: "MyLibrary", product: .staticLibrary, sources: ["**/*.swift"]) ] ) ``` ::: Here's another example but compating how to define unit tests in Bazel and Tuist: :::code-group ```txt [BUILD (Bazel)] ios_unit_test( name = "MyLibraryTests", bundle_id = "io.tuist.MyLibraryTests", minimum_os_version = "16.0", test_host = "//MyApp:MyLibrary", deps = [":MyLibraryTests.library"], ) ``` ```swift [Project.swift (Tuist)] let project = Project( // ... targets: [ .target( name: "MyLibraryTests", destinations: .iOS, product: .unitTests, bundleId: "io.tuist.MyLibraryTests", sources: "Tests/MyLibraryTests/**", dependencies: [ .target(name: "MyLibrary"), ] ) ] ) ``` ::: ## Swift Package Manager dependencies {#swift-package-manager-dependencies} In Bazel, you can use the [`rules_swift_package_manager`](https://github.com/cgrindel/rules_swift_package_manager) [Gazelle](https://github.com/bazelbuild/bazel-gazelle/blob/master/extend.md) plugin to use Swift Packages as dependencies. The plugin requires a `Package.swift` as a source of truth for the dependencies. Tuist's interface is similar to Bazel's in that sense. You can use the `tuist install` command to resolve and pull the dependencies of the package. After the resolution completes, you can then generate the project with the `tuist generate` command. ```bash tuist install # Fetch dependencies defined in Tuist/Package.swift tuist generate # Generate an Xcode project ``` ## Project generation {#project-generation} The community provides a set of rules, [rules_xcodeproj](https://github.com/MobileNativeFoundation/rules_xcodeproj), to generate Xcode projects off Bazel-declared projects. Unlike Bazel, where you need to add some configuration to your `BUILD` file, Tuist doesn't require any configuration at all. You can run `tuist generate` in the root directory of your project, and Tuist will generate an Xcode project for you. --- URL: "/en/guides/develop/projects" LLMS_URL: "/en/guides/develop/projects.md" title: "Projects" titleTemplate: ":title · Develop · Guides · Tuist" description: "Learn about Tuist's DSL for defining Xcode projects." --- # Generated projects {#generated-projects} Generated is a viable alternative that helps to overcome these challenges while keeping complexity and costs at an acceptable level. It considers Xcode projects as a fundamental element, ensuring resilience against future Xcode updates, and leverages Xcode project generation to provide teams with a modularization-focused declarative API. Tuist uses the project declaration to simplify the complexities of modularization**, optimize workflows like build or test across various environments, and facilitate and democratize the evolution of Xcode projects. ## How does it work? {#how-does-it-work} To get started with generated projects, all you need is to define your project using **Tuist's Domain Specific Language (DSL)**. This entails using manifest files such as `Workspace.swift` or `Project.swift`. If you've worked with the Swift Package Manager before, the approach is very similar. Once you've defined your project, Tuist offers various workflows to manage and interact with it: - **Generate:** This is a foundational workflow. Use it to create an Xcode project that's compatible with Xcode. - **Build:** This workflow not only generates the Xcode project but also employs `xcodebuild` to compile it. - **Test:** Operating much like the build workflow, this not only generates the Xcode project but utilizes `xcodebuild` to test it. ## Challenges with Xcode projects {#challenges-with-xcode-projects} As Xcode projects grow, **organizations may face a decline in productivity** due to several factors, including unreliable incremental builds, frequent clearing of Xcode's global cache by developers encountering issues, and fragile project configurations. To maintain rapid feature development, organizations typically explore various strategies. Some organizations choose to bypass the compiler by abstracting the platform using JavaScript-based dynamic runtimes, such as [React Native](https://reactnative.dev/). While this approach may be effective, it [complicates access to the platform's native features](https://shopify.engineering/building-app-clip-react-native). Other organizations opt for **modularizing the codebase**, which helps establish clear boundaries, making the codebase easier to work with and improving the reliability of build times. However, the Xcode project format is not designed for modularity and results in implicit configurations that few understand and frequent conflicts. This leads to a bad bus factor, and although incremental builds may improve, developers might still frequently clear Xcode's build cache (i.e., derived data) when builds fail. To address this, some organizations choose to **abandon Xcode's build system** and adopt alternatives like [Buck](https://buck.build/) or [Bazel](https://bazel.build/). However, this comes with a [high complexity and maintenance burden](https://bazel.build/migrate/xcode). ## Alternatives {#alternatives} ### Swift Package Manager {#swift-package-manager} While the Swift Package Manager (SPM) primarily focuses on dependencies, Tuist offers a different approach. With Tuist, you don't just define packages for SPM integration; you shape your projects using familiar concepts like projects, workspaces, targets, and schemes. ### XcodeGen {#xcodegen} [XcodeGen](https://github.com/yonaskolb/XcodeGen) is a dedicated project generator designed to reduce conflicts in collaborative Xcode projects and simplify some complexities of Xcode's internal workings. However, projects are defined using serializable formats like [YAML](https://yaml.org/). Unlike Swift, this doesn't allow developers to build upon abstractions or checks without incorporating additional tools. While XcodeGen does offer a way to map dependencies to an internal representation for validation and optimization, it still exposes developers to the nuances of Xcode. This might make XcodeGen a suitable foundation for [building tools](https://github.com/MobileNativeFoundation/rules_xcodeproj), as seen in the Bazel community, but it's not optimal for inclusive project evolution that aims to maintain a healthy and productive environment. ### Bazel {#bazel} [Bazel](https://bazel.build) is an advanced build system renowned for its remote caching features, gaining popularity within the Swift community primarily for this capability. However, given the limited extensibility of Xcode and its build system, substituting it with Bazel's system demands significant effort and maintenance. Only a few companies with abundant resources can bear this overhead, as evident from the select list of firms investing heavily to integrate Bazel with Xcode. Interestingly, the community created a [tool](https://github.com/MobileNativeFoundation/rules_xcodeproj) that employs Bazel's XcodeGen to generate an Xcode project. This results in a convoluted chain of conversions: from Bazel files to XcodeGen YAML and finally to Xcode Projects. Such layered indirection often complicates troubleshooting, making issues more challenging to diagnose and resolve. --- URL: "/en/guides/develop/insights" LLMS_URL: "/en/guides/develop/insights.md" title: "Insights" titleTemplate: ":title · Develop · Guides · Tuist" description: "Get insights into your projects to maintain a product developer environment." --- # Insights {#insights} > [!IMPORTANT] REQUIREMENTS > - A Tuist account and project Working on large projects shouldn't feel like a chore. In fact, it should be as enjoyable as working on a project you started just two weeks ago. One of the reasons it is not is because as the project grows, the developer experience suffers. The build times increase and tests become slow and flaky. It's often easy to overlook these issues until it gets to a point where they become unbearable – however, at that point, it's difficult to address them. Tuist Insights provides you with the tools to monitor the health of your project and maintain a productive developer environment as your project scales. In other words, Tuist Insights helps you to anwer questions such as: - Has the build time significantly increased in the last week? - Have my tests become slower? Which ones? > [!NOTE] > Tuist Insights are in early development. ## Builds {#builds} While you probably have some metrics for the performance of CI workflows, you might not have the same visibility into the local development environment. However, local build times are one of the most important factors that contribute to the developer experience. To start tracking local build times, you can leverage the `tuist inspect build` command by adding it to your scheme's post-action: ![Post-action for inspecting builds](/images/guides/develop/insights/inspect-build-scheme-post-action.png) In case you're using [Mise](https://mise.jdx.dev/), your script will need to activate `tuist` in the post-action environment: ```sh # -C ensures that Mise loads the configuration from the Mise configuration # file in the project's root directory. eval "$($HOME/.local/bin/mise activate -C $SRCROOT bash --shims)" tuist inspect build ``` Your local builds are now tracked as long as you are logged in to your Tuist account. You can now access your build times in the Tuist dashboard and see how they evolve over time: > [!TIP] > To quickly access the dashboard, run `tuist project show --web` from the CLI. ![Dashboard with build insights](/images/guides/develop/insights/builds-dashboard.png) ## Generated projects {#generated-projects} > [!NOTE] > Auto-generated schemes automatically include the `tuist inspect build` post-action. > > If you are not interested in tracking build insights in your auto-generated schemes, disable them using the buildInsightsDisabled generation option. If you are using generated projects, you can set up a custom build post-action using a custom scheme, such as: ```swift let project = Project( name: "MyProject", targets: [ // Your targets ], schemes: [ .scheme( name: "MyApp", shared: true, buildAction: .buildAction(targets: ["MyApp"]), testAction: .testAction(targets: ["MyAppTests"]), runAction: .runAction(configuration: "Debug"), postActions: [ .postAction( name: "Inspect Build", scriptText: """ eval \"$($HOME/.local/bin/mise activate -C $SRCROOT bash --shims)\" tuist inspect build """ ) ] ) ] ) ``` If you're not using Mise, your script can be simplified to just: ```swift .postAction( name: "Inspect Build", script: "tuist inspect build", execution: .always ) ``` ## Continuous integration {#continuous-integration} To track build times also on the CI, you will need to ensure that your CI is authenticated. Additionally, you will either need to: - Use the `tuist xcodebuild` command when invoking `xcodebuild` actions. - Add `-resultBundlePath` to your `xcodebuild` invocation. When `xcodebuild` builds your project without `-resultBundlePath`, the `.xcactivitylog` file is not generated. But the `tuist inspect build` post-action requires that file to be generated to analyze your build. --- URL: "/en/guides/develop/cache" LLMS_URL: "/en/guides/develop/cache.md" title: "Cache" titleTemplate: ":title · Develop · Guides · Tuist" description: "Optimize your build times by caching compiled binaries and sharing them across different environments." --- # Cache {#cache} > [!IMPORTANT] REQUIREMENTS > - A generated project > - A Tuist account and project Xcode's build system provides [incremental builds](https://en.wikipedia.org/wiki/Incremental_build_model), enhancing efficiency under normal circumstances. However, this feature falls short in [Continuous Integration (CI) environments](https://en.wikipedia.org/wiki/Continuous_integration), where data essential for incremental builds is not shared across different builds. Additionally, **developers often reset this data locally to troubleshoot complex compilation problems**, leading to more frequent clean builds. This results in teams spending excessive time waiting for local builds to finish or for Continuous Integration pipelines to provide feedback on pull requests. Furthermore, the frequent context switching in such an environment compounds this unproductiveness. Tuist addresses these challenges effectively with its caching feature. This tool optimizes the build process by caching compiled binaries, significantly reducing build times both in local development and CI environments. This approach not only accelerates feedback loops but also minimizes the need for context switching, ultimately boosting productivity. ## Warming {#warming} Tuist efficiently utilizes hashes for each target in the dependency graph to detect changes. Utilizing this data, it builds and assigns unique identifiers to binaries derived from these targets. At the time of graph generation, Tuist then seamlessly substitutes the original targets with their corresponding binary versions. This operation, known as *"warming,"* produces binaries for local use or for sharing with teammates and CI environments via Tuist. The process of warming the cache is straightforward and can be initiated with a simple command: ```bash tuist cache ``` The command re-uses binaries to speed up the process. ## Usage {#usage} By default, when Tuist commands necessitate project generation, they automatically substitute dependencies with their binary equivalents from the cache, if available. Additionally, if you specify a list of targets to focus on, Tuist will also replace any dependent targets with their cached binaries, provided they are available. For those who prefer a different approach, there is an option to opt out of this behavior entirely by using a specific flag: ::: code-group ```bash [Project generation] tuist generate # Only dependencies tuist generate Search # Dependencies + Search dependencies tuist generate Search Settings # Dependencies, and Search and Settings dependencies tuist generate --no-binary-cache # No cache at all ``` ```bash [Testing] tuist test ``` ::: > [!WARNING] > Binary caching is a feature designed for development workflows such as running the app on a simulator or device, or running tests. It is not intended for release builds. When archiving the app, generate a project with the sources by using the `--no-binary-cache` flag. ## Supported products {#supported-products} Only the following target products are cacheable by Tuist: - Frameworks (static and dynamic) that don't depend on [XCTest](https://developer.apple.com/documentation/xctest) - Bundles - Swift Macros We are working on supporting libraries and targets that depend on XCTest. > [!NOTE] UPSTREAM DEPENDENCIES > When a target is non-cacheable it makes the upstream targets non-cacheable too. For example, if you have the dependency graph `A > B`, where A depends on B, if B is non-cacheable, A will also be non-cacheable. ## Efficiency {#efficiency} The level of efficiency that can be achieved with binary caching depends strongly on the graph structure. To achieve the best results, we recommend the following: 1. Avoid very nested dependency graphs. The shallower the graph, the better. 2. Define dependencies with protocol/interface targets instead of implementation ones, and dependency-inject implementations from the top-most targets. 3. Split frequently-modified targets into smaller ones whose likelihood of change is lower. The above suggestions are part of the The Modular Architecture, which we propose as a way to structure your projects to maximize the benefits not only of binary caching but also of Xcode's capabilities. ## Recommended setup {#recommended-setup} We recommend having a CI job that **runs in every commit in the main branch** to warm the cache. This will ensure the cache always contains binaries for the changes in `main` so local and CI branch build incrementally upon them. > [!TIP] CACHE WARMING USES BINARIES > The `tuist cache` command also makes use of the binary cache to speed up the warming. The following are some examples of common workflows: ### A developer starts to work on a new feature {#a-developer-starts-to-work-on-a-new-feature} 1. They create a new branch from `main`. 2. They run `tuist generate`. 3. Tuist pulls the most recent binaries from `main` and generates the project with them. ### A developer pushes changes upstream {#a-developer-pushes-changes-upstream} 1. The CI pipeline will run `tuist build` or `tuist test` to build or test the project. 2. The workflow will pull the most recent binaries from `main` and generate the project with them. 3. It will then build or test the project incrementally. ## Troubleshooting {#troubleshooting} ### It doesn't use binaries for my targets {#it-doesnt-use-binaries-for-my-targets} Ensure that the hashes are deterministic across environments and runs. This might happen if the project has references to the environment, for example through absolute paths. You can use the `diff` command to compare the projects generated by two consecutive invocations of `tuist generate` or across environments or runs. Also make sure that the target doesn't depend either directly or indirectly on a non-cacheable target. --- URL: "/en/guides/develop/build" LLMS_URL: "/en/guides/develop/build.md" title: "Build" titleTemplate: ":title · Develop · Guides · Tuist" description: "Learn how to use Tuist to build your projects efficiently." --- # Build {#build} Projects are usually built through a build-system-provided CLI (e.g. `xcodebuild`). Tuist wraps them to improve the user experience and integrate the workflows with the platform to provide optimizations and analytics. You might wonder what's the value of using `tuist build` over generating the project with `tuist generate` (if needed) and building it with the platform-specific CLI. Here are some reasons: - **Single command:** `tuist build` ensures the project is generated if needed before compiling the project. - **Beautified output:** Tuist enriches the output using tools like [xcbeautify](https://github.com/cpisciotta/xcbeautify) that make the output more user-friendly. - Cache: It optimizes the build by deterministically reusing the build artifacts from a remote cache. - **Analytics:** It collects and reports metrics that are correlated with other data points to provide you with actionable information to make informed decisions. ## Usage {#usage} `tuist build` generates the project if needed, and then build it using the platform-specific build tool. We support the use of the `--` terminator to forward all subsequent arguments directly to the underlying build tool. This is useful when you need to pass arguments that are not supported by `tuist build` but are supported by the underlying build tool. ::: code-group ```bash [Build a scheme] tuist build MyScheme ``` ```bash [Build a specific configuration] tuist build MyScheme -- -configuration Debug ``` ```bash [Build all schemes without binary cache] tuist build --no-binary-cache ``` ::: --- URL: "/en/guides/automate/continuous-integration" LLMS_URL: "/en/guides/automate/continuous-integration.md" title: "Continuous Integration (CI)" titleTemplate: ":title · Automate · Guides · Tuist" description: "Learn how to use Tuist in your CI workflows." --- # Continuous Integration (CI) {#continuous-integration-ci} You can use Tuist in [continuous integration](https://en.wikipedia.org/wiki/Continuous_integration) environments. The following sections provide examples of how to do this on different CI platforms. ## Examples {#examples} To run Tuist commands in your CI workflows, you’ll need to install it in your CI environment. ### Xcode Cloud {#xcode-cloud} In [Xcode Cloud](https://developer.apple.com/xcode-cloud/), which uses Xcode projects as the source of truth, you'll need to add a [post-clone](https://developer.apple.com/documentation/xcode/writing-custom-build-scripts#Create-a-custom-build-script) script to install Tuist and run the commands you need, for example `tuist generate`: :::code-group ```bash [Mise] #!/bin/sh curl https://mise.jdx.dev/install.sh | sh mise install # Installs the version from .mise.toml # Runs the version of Tuist indicated in the .mise.toml file {#runs-the-version-of-tuist-indicated-in-the-misetoml-file} mise exec -- tuist generate ``` ```bash [Homebrew] #!/bin/sh brew install --formula tuist@x.y.z tuist generate ``` ::: ### Codemagic {#codemagic} In [Codemagic](https://codemagic.io), you can add an additional step to your workflow to install Tuist: ::: code-group ```yaml [Mise] workflows: lint: name: Build max_build_duration: 30 environment: xcode: 15.0.1 scripts: - name: Install Mise script: | curl https://mise.jdx.dev/install.sh | sh mise install # Installs the version from .mise.toml - name: Build script: mise exec -- tuist build ``` ```yaml [Homebrew] workflows: lint: name: Build max_build_duration: 30 environment: xcode: 15.0.1 scripts: - name: Install Tuist script: | brew install --formula tuist@x.y.z - name: Build script: tuist build ``` ::: ### GitHub Actions {#github-actions} On [GitHub Actions](https://docs.github.com/en/actions) you can add an additional step to install Tuist, and in the case of managing the installation of Mise, you can use the [mise-action](https://github.com/jdx/mise-action), which abstracts the installation of Mise and Tuist: ::: code-group ```yaml [Mise] name: Build Application on: pull_request: branches: - main push: branches: - main jobs: build: runs-on: macos-latest steps: - uses: actions/checkout@v3 - uses: jdx/mise-action@v2 - run: tuist build ``` ```yaml [Homebrew] name: test on: pull_request: branches: - main push: branches: - main jobs: lint: runs-on: macos-latest steps: - uses: actions/checkout@v3 - run: brew install --formula tuist@x.y.z - run: tuist build ``` ::: ::: tip We recommend using `mise use --pin` in your Tuist projects to pin the version of Tuist across environments. The command will create a `.tool-versions` file containing the version of Tuist. ::: ## Authentication {#authentication} When using server-side features such as cache, you'll need a way to authenticate requests going from your CI workflows to the server. For that, you can generate a project-scoped token by running the following command: ```bash tuist project tokens create my-handle/MyApp ``` The command will generate a token for the project with full handle `my-account/my-project`. Set the value to the environment variable `TUIST_CONFIG_TOKEN` in your CI environment ensuring it's configured as a secret so it's not exposed. > [!IMPORTANT] CI ENVIRONMENT DETECTION > Tuist only uses the token when it detects it's running on a CI environment. If your CI environment is not detected, you can force the token usage by setting the environment variable `CI` to `1`. --- URL: "/en/guides/ai/mcp" LLMS_URL: "/en/guides/ai/mcp.md" title: "Model Context Protocol (MCP)" titleTemplate: ":title · AI · Guides · Tuist" description: "Learn how to use Tuist's MCP server to have a language-based interface for your app development environment." --- # Model Context Protocol (MCP) [Model Context Protocol (MCP)](https://www.claudemcp.com) is a standard proposed by [Claude](https://claude.ai) for LLMs to interact with development environments. You can think of it as the USB-C of LLMs. Like shipping containers, which made cargo and transportation more interoperable, or protocols like TCP, which decoupled the application layer from the transport layer, MCP makes LLM-powered applications such as [Claude](https://claude.ai/) and editors like [Zed](https://zed.dev) or [Cursor](https://www.cursor.com) interoperable with other domains. Tuist provides a local server through its CLI so that you can interact with your **app development environment**. By connecting your client apps to it, you can use language to interact with your projects. In this page you'll learn about how to set it up and its capabilities. > [!NOTE] > Tuist MCP server uses Xcode's most-recent projects as the source of truth for projects you want to interact with. ## Set it up ### [Claude](https://claude.ai) If you are using [Claude desktop](https://claude.ai/download), you can run the tuist mcp setup claude command to configure your Claude environment. Alternatively, can manually edit the file at `~/Library/Application\ Support/Claude/claude_desktop_config.json`, and add the Tuist MCP server: :::code-group ```json [Global Tuist installation (e.g. Homebrew)] { "mcpServers": { "tuist": { "command": "tuist", "args": ["mcp", "start"] } } } ``` ```json [Mise installation] { "mcpServers": { "tuist": { "command": "mise", "args": ["x", "tuist@latest", "--", "tuist", "mcp", "start"] // Or tuist@x.y.z to fix the version } } } ``` ::: ## Capabilities In the following sections you'll learn about the capabilities of the Tuist MCP server. ### Resources #### Recent projects and workspaces Tuist keeps a record of the Xcode projects and workspaces you’ve recently worked with, giving your application access to their dependency graphs for powerful insights. You can query this data to uncover details about your project structure and relationships, such as: - What are the direct and transitive dependencies of a specific target? - Which target has the most source files, and how many does it include? - What are all the static products (e.g., static libraries or frameworks) in the graph? - Can you list all targets, sorted alphabetically, along with their names and product types (e.g., app, framework, unit test)? - Which targets depend on a particular framework or external dependency? - What’s the total number of source files across all targets in the project? - Are there any circular dependencies between targets, and if so, where? - Which targets use a specific resource (e.g., an image or plist file)? - What’s the deepest dependency chain in the graph, and which targets are involved? - Can you show me all the test targets and their associated app or framework targets? - Which targets have the longest build times based on recent interactions? - What are the differences in dependencies between two specific targets? - Are there any unused source files or resources in the project? - Which targets share common dependencies, and what are they? With Tuist, you can dig into your Xcode projects like never before, making it easier to understand, optimize, and manage even the most complex setups! --- URL: "/en/contributors/translate" LLMS_URL: "/en/contributors/translate.md" title: "Translate" titleTemplate: ":title · Contributors · Tuist" description: "This document describes the principles that guide the development of Tuist." --- # Translate {#translate} Languages can be barriers to understanding. We want to make sure that Tuist is accessible to as many people as possible. If you speak a language that Tuist doesn't support, you can help us by translating the various surfaces of Tuist. Since maintaining translations is a continuous effort, we add languages as we see contributors willing to help us maintain them. The following languages are currently supported: - English - Korean - Japanese - Russian > [!TIP] REQUEST A NEW LANGUAGE > If you believe Tuist would benefit from supporting a new language, please create a new [topic in the community forum](https://community.tuist.io/c/general/4) to discuss it with the community. ## How to translate {#how-to-translate} We use [Crowdin](https://crowdin.com/) to manage the translations. First, go to the project that you want to contribute to: - [Documentation](https://crowdin.com/project/tuist-documentation) - [Website](https://crowdin.com/project/tuist-documentation) You'll need an account to start translating. You can sign in with GitHub. Once you have access, you can start translating. You'll see the list of resources that are available for translation. When you click on a resource, the editor will open, and you'll see a split view with the resource in the source language on the left and the translation on the right. Translate the text on the right and save your changes. As translations are updated, Crowdin will push them automatically to the right repository opening a pull request, which the maintainers will review and merge. > [!IMPORTANT] DON'T MODIFY THE RESOURCES IN THE TARGET LANGUAGE > Crowdin segments the files to bind source and target languages. If you modify the source language, you'll break the binding, and the reconciliation might yield unexpected results. ## Guidelines {#guidelines} The following are the guidelines we follow when translating. ### Custom containers and GitHub alerts {#custom-containers-and-github-alerts} When translating [custom containers](https://vitepress.dev/guide/markdown#custom-containers) or [GitHub Alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts), only translate the title and the content **but not the type of alert**. ::: details Example with GitHub Alert ```markdown > [!WARNING] 루트 변수 > 매니페스트의 루트에 있어야 하는 변수는... // Instead of > [!주의] 루트 변수 > 매니페스트의 루트에 있어야 하는 변수는... ``` ::: ::: details Example with custom container ```markdown ::: warning 루트 변수\ 매니페스트의 루트에 있어야 하는 변수는... ::: # Instead of ::: 주의 루트 변수\ 매니페스트의 루트에 있어야 하는 변수는... ::: ``` ::: ### Heading titles {#heading-titles} When translating headings, only translate tht title but not the id. For example, when translating the following heading: ```markdown # Add dependencies {#add-dependencies} ``` It should be translated as (note the id is not translated): ```markdown # 의존성 추가하기 {#add-dependencies} ``` --- URL: "/en/contributors/principles" LLMS_URL: "/en/contributors/principles.md" title: "Principles" titleTemplate: ":title · Contributors · Tuist" description: "This document describes the principles that guide the development of Tuist." --- # Principles {#principles} This page describes principles that are pillars to the design and development of Tuist. They evolve with the project and are meant to ensure a sustainable growth that is well-aligned with the project foundation. ## Default to conventions {#default-to-conventions} One of the reasons why Tuist exists is because Xcode is weak in conventions and that leads to complex projects that are hard to scale up and maintain. For that reason, Tuist takes a different approach by defaulting to simple and thoroughly designed conventions. **Developers can opt-out from the conventions, but that’s a conscious decision that doesn’t feel natural.** For example, there’s a convention for defining dependencies between targets by using the provided public interface. By doing that, Tuist ensures that the projects are generated with the right configurations for the linking to work. Developers have the option to define the dependencies through build settings, but they’d be doing it implicitly and therefore breaking Tuist features such as `tuist graph` or `tuist cache` that rely on some conventions being followed. The reason why we default to conventions is that the more decision we can make on behalf of the developers, the more focus they’ll have crafting features for their apps. When we are left with no conventions like it’s the case in many projects, we have to make decisions that will end up not being consistent with other decisions and as a consequence, there’ll be an accidental complexity that will be hard to manage. ## Manifests are the source of truth {#manifests-are-the-source-of-truth} Having many layers of configurations and contracts between them results in a project setup that is hard to reason about and maintain. Think for a second on an average project. The definition of the project lives in the `.xcodeproj` directories, the CLI in scripts (e.g `Fastfiles`), and the CI logic in pipelines. Those are three layers with contracts between them that we need to maintain. *How often have you been in a situation where you changed something in your projects, and then a week later you realized that the release scripts broke?* We can simplify this by having a single source of truth, the manifest files. Those files provide Tuist with the information that it needs to generate Xcode projects that developers can use to edit their files. Moreover, it allows having standard commands for building projects from a local or CI environment. **Tuist should own the complexity and expose a simple, safe, and enjoyable interface to describe their projects as explicitly as possible.** ## Make the implicit explicit {#make-the-implicit-explicit} Xcode supports implicit configurations. A good example of that is inferring the implicitly defined dependencies. While implicitness is fine for small projects, where configurations are simple, as projects get larger it might cause slowness or odd behaviors. Tuist should provide explicit APIs for implicit Xcode behaviors. It should also support defining Xcode implicitness but implemented in such a way that encourages developers to opt for the explicit approach. Supporting Xcode implicitness and intricacies facilitates the adoption of Tuist, after which teams can take some time to get rid of the implicitness. The definition of dependencies is a good example of that. While developers can define dependencies through build settings and phases, Tuist provides a beautiful API that encourages its adoption. **Designing the API to be explicit allows Tuist to run some checks and optimizations on the projects that otherwise wouldn’t be possible.** Moreover, it enables features like `tuist graph`, which exports a representation of the dependency graph, or `tuist cache`, which caches all the targets as binaries. > [!TIP] > We should treat each request to port features from Xcode as an opportunity to simplify concepts with simple and explicit APIs. ## Keep it simple {#keep-it-simple} One of the main challenges when scaling Xcode projects comes from the fact that **Xcode exposes a lot of complexity to the users.** Due to that, teams have a high bus factor and only a few people in the team understand the project and the errors that the build system throws. That’s a bad situation to be in because the team relies on a few people. Xcode is a great tool, but so many years of improvements, new platforms, and programming languages, are reflected on their surface, which struggled to remain simple. Tuist should take the opportunity to keep things simple because working on simple things is fun and motivates us. No one wants to spend time trying to debug an error that happens at the very end of the compilation process, or understanding why they are not able to run the app on their devices. Xcode delegates the tasks to its underlying build system and in some cases it does a very poor job translating errors into actionable items. Have you ever got a *“framework X not found”* error and you didn’t know what to do? Imagine if we got a list of potential root causes for the bug. ## Start from the developer’s experience {#start-from-the-developers-experience} Part of the reason why there is a lack of innovation around Xcode, or put differently, not as much as in other programming environments, is because **we often start analyzing problems from existing solutions.** As a consequence, most of the solutions that we find nowadays revolve around the same ideas and workflows. While it’s good to include existing solutions in the equations, we should not let them constrain our creativity. We like to think as [Tom Preston](https://tom.preston-werner.com/) puts it in [this podcast](https://tom.preston-werner.com/): *“Most things can be achieved, whatever you have in your head you can probably pull off with code as long as is possible within the constrains of the universe”.* If **we imagine how we’d like the developer experience to be**, it’s just a matter of time to pull it off — by starting to analyze the problems from the developer experience gives us a unique point of view that will lead us to solutions that users will love to use. We might feel tempted to follow what everyone is doing, even if that means sticking with the inconveniences that everyone continues to complain about. Let’s not do that. How do I imagine archiving my app? How would I love code signing to be? What processes can I help streamline with Tuist? For example, adding support for [Fastlane](https://fastlane.tools/) is a solution to a problem that we need to understand first. We can get to the root of the problem by asking “why” questions. Once we narrow down where the motivation comes from, we can think of how Tuist can help them best. Maybe the solution is integrating with Fastlane, but it’s important we don’t disregard other equally valid solutions that we can put on the table before making trade-offs. ## Errors can and will happen {#errors-can-and-will-happen} We, developers, have an inherent temptation to disregard that errors can happen. As a result, we design and test software only considering the ideal scenario. Swift, its type system, and a well-architected code might help prevent some errors, but not all of them because some are out of our control. We can’t assume the user will always have an internet connection, or that the system commands will return successfully. The environments in which Tuist runs are not sandboxes that we control, and hence we need to make an effort to understand how they might change and impact Tuist. Poorly handled errors result in bad user experience, and users might lose trust in the project. We want users to enjoy every single piece of Tuist, even the way we present errors to them. We should put ourselves in the shoes of users and imagine what we’d expect the error to tell us. If the programming language is the communication channel through which errors propagate, and the users are the destination of the errors, they should be written in the same language that the target (users) speak. They should include enough information to know what happened and hide the information that is not relevant. Also, they should be actionable by telling users what steps they can take to recover from them. And last but not least, our test cases should contemplate failing scenarios. Not only they ensure that we are handling errors as we are supposed to, but prevent future developers from breaking that logic. --- URL: "/en/contributors/issue-reporting" LLMS_URL: "/en/contributors/issue-reporting.md" title: "Issue reporting" titleTemplate: ":title · Contributors · Tuist" description: "Learn how to contribute to Tuist by reporting bugs" --- # Issue reporting {#issue-reporting} As user of Tuist, you might come across bugs or unexpected behaviors. If you do, we encourage you to report them so that we can fix them. ## GitHub issues is our ticketing platform {#github-issues-is-our-ticketing-platform} Issues should be reported on GitHub as [GitHub issues](https://github.com/tuist/tuist/issues) and not on Slack or other platforms. GitHub is better for tracing and managing issues, is closer to the codebase, and allows us to track the progress of the issue. Moreover, it encourages a long-form description of the problem, which forces the reporter to think about the problem and provide more context. ## Context is crucial {#context-is-crucial} An issue without enough context will be deemed incomplete and the author will be asked for additional context. If not provided, the issue will be closed. Think about it this way: the more context you provide, the easier it is for us to understand the problem and fix it. So if you want your issue to be fixed, provide as much context as possible. Try to answer the following questions: - What were you trying to do? - How does your graph look? - What version of Tuist are you using? - Is this blocking you? We also require you to provide a minimal **reproducible project**. ## Reproducible project {#reproducible-project} ### What is a reproducible project? {#what-is-a-reproducible-project} A reproducible project is a small Tuist project to demonstrate a problem - often this problem is caused by a bug in Tuist. Your reproducible project should contain the bare minimum features needed to clearly demonstrate the bug. ### Why should you create a reproducible test case? {#why-should-you-create-a-reproducible-test-case} A reproducible projects lets us isolate the cause of a problem, which is the first step towards fixing it! The most important part of any bug report is to describe the exact steps needed to reproduce the bug. A reproducible project is a great way to share a specific environment that causes a bug. Your reproducible project is the best way to help people that want to help you. ### Steps to create a reproducible project {#steps-to-create-a-reproducible-project} - Create a new git repository. - Initialize a project using `tuist init` in the repository directory. - Add the code needed to recreate the error you’ve seen. - Publish the code (your GitHub account is a good place to do this) and then link to it when creating an issue. ### Benefits of reproducible projects {#benefits-of-reproducible-projects} - **Smaller surface area:** By removing everything but the error, you don’t have to dig to find the bug. - **No need to publish secret code:** You might not be able to publish your main site (for many reasons). Remaking a small part of it as a reproducible test case allows you to publicly demonstrate a problem without exposing any secret code. - **Proof of the bug:** Sometimes a bug is caused by some combination of settings on your machine. A reproducible test case allows contributors to pull down your build and test it on their machines as well. This helps verify and narrow down the cause of a problem. - **Get help with fixing your bug:** If someone else can reproduce your problem, they often have a good chance of fixing the problem. It’s almost impossible to fix a bug without first being able to reproduce it. --- URL: "/en/contributors/get-started" LLMS_URL: "/en/contributors/get-started.md" title: "Get started" titleTemplate: ":title · Contributors · Tuist" description: "Get started contributing to Tuist by following this guide." --- # Get started {#get-started} If you have experience building apps for Apple platforms, like iOS, adding code to Tuist shouldn’t be much different. There are two differences compared to developing apps that are worth mentioning: - **The interactions with CLIs happen through the terminal.** The user executes Tuist, which performs the desired task, and then returns successfully or with a status code. During the execution, the user can be notified by sending output information to the standard output and standard error. There are no gestures, or graphical interactions, just the user intent. - **There’s no runloop that keeps the process alive waiting for input**, like it happens in an iOS app when the app receives system or user events. CLIs run in its process and finishes when the work is done. Asynchronous work can be done using system APIs like [DispatchQueue](https://developer.apple.com/documentation/dispatch/dispatchqueue) or [structured concurrency](https://developer.apple.com/tutorials/app-dev-training/managing-structured-concurrency), but need to make sure the process is running while the asynchronous work is being executed. Otherwise, the process will terminate the asynchronous work. If you don’t have any experience with Swift, we recommend [Apple’s official book](https://docs.swift.org/swift-book/) to get familiar with the language and the most used elements from the Foundation’s API. ## Minimum requirements {#minimum-requirements} To contribute to Tuist, minimum requirements are: - macOS 14.0+ - Xcode 16.3+ ## Set up the project locally {#set-up-the-project-locally} To start working on the project, we can follow the steps below: - Clone the repository by running: `git clone git@github.com:tuist/tuist.git` - [Install](https://mise.jdx.dev/getting-started.html) Mise to provision the development environment. - Run `mise install` to install the system dependencies needed by Tuist - Run `tuist install` to install the external dependencies needed by Tuist - (Optional) Run `tuist auth login` to get access to the Tuist Cache - Run `tuist generate` to generate the Tuist Xcode project using Tuist itself **The generated project opens automatically**. If you need to open again without generating it, run open `Tuist.xcworkspace` (or use Finder). > [!NOTE] XED . > If you try to open the project using `xed .`, it will open the package, and not the project generated by Tuist. We recommend using the Tuist-generated project to dog-food the tool. ## Edit the project {#edit-the-project} If you needed to edit the project, for example to add dependencies or adjust targets, you can use the `tuist edit` command. This is barely used, but it's good to know that it exists. ## Run Tuist {#run-tuist} ### From Xcode {#from-xcode} To run `tuist` from the generated Xcode project, edit the `tuist` scheme, and set the arguments that you'd like to pass to the command. For example, to run the `tuist generate` command, you can set the arguments to `generate --no-open` to prevent the project from opening after the generation. ![An example of a scheme configuration to run the generate command with Tuist](/images/contributors/scheme-arguments.png) You'll also have to set the working directory to the root of the project being generated. You can do that either by using the `--path` argument, which all the commands accept, or configuring the working directory in the scheme as shown below: ![An example of how to set the working directory to run Tuist](/images/contributors/scheme-working-directory.png) > [!WARNING] PROJECTDESCRIPTION COMPILATION > The `tuist` CLI depends on the `ProjectDescription` framework's presence in the built products directory. If `tuist` fails to run because it can't find the `ProjectDescription` framework, build the `Tuist-Workspace` scheme first. ### From the terminal {#from-the-terminal} You can run `tuist` using Tuist itself through its `run` command: ```bash tuist run tuist generate --path /path/to/project --no-open ``` Alternatively, you can also run it through the Swift Package Manager directly: ```bash swift build --product ProjectDescription swift run tuist generate --path /path/to/project --no-open ``` --- URL: "/en/contributors/code-reviews" LLMS_URL: "/en/contributors/code-reviews.md" title: "Code reviews" titleTemplate: ":title · Contributors · Tuist" description: "Learn how to contribute to Tuist by reviewing code" --- # Code reviews {#code-reviews} Reviewing pull requests is a common type of contribution. Despite continuous integration (CI) ensuring the code does what’s supposed to do, it’s not enough. There are contribution traits that can’t be automated: design, code structure & architecture, tests quality, or typos. The following sections represent different aspects of the code review process. ## Readability {#readability} Does the code express its intention clearly? **If you need to spend a bunch of time figuring out what the code does, the code implementation needs to be improved.** Suggest splitting the code into smaller abstractions that are easier to understand. Alternative, and as a last resource, they can add a comment explaining the reasoning behind it. Ask yourself if you’d be able to understand the code in a near future, without any surrounding context like the pull request description. ## Small pull requests {#small-pull-requests} Large pull requests are hard to review and it’s easier to miss out details. If a pull request becomes too large and unmanageable, suggest the author to break it down. > [!NOTE] EXCEPTIONS > There are few scenarios where splitting up the pull request is not possible, like when the changes are tightly coupled and can’t be split. In those cases, the author should provide a clear explanation of the changes and the reasoning behind them. ## Consistency {#consistency} It’s important that the changes are consistent with the rest of the project. Inconsistencies complicate maintenance, and therefore we should avoid them. If there’s an approach to output messages to the user, or report errors, we should stick to that. If the author disagrees with the project’s standards, suggest them to open an issue where we can discuss them further. ## Tests {#tests} Tests allow changing code with confidence. The code on pull requests should be tested, and all tests should pass. A good test is a test that consistently produces the same result and that it’s easy to understand and maintain. Reviewers spend most of the review time in the implementation code, but tests are equally important because they are code too. ## Breaking changes {#breaking-changes} Breaking changes are a bad user experience for users of Tuist. Contributions should avoid introducing breaking changes unless it’s strictly necessary. There are many language features that we can leverage to evolve the interface of Tuist without resorting to a breaking change. Whether a change is breaking or not might not be obvious. A method to verify whether the change is breaking is running Tuist against the fixture projects in the fixtures directory. It requires putting ourselves in the user’s shoes and imagine how the changes would impact them. --- URL: "/en/contributors/cli/logging" LLMS_URL: "/en/contributors/cli/logging.md" title: "Logging" titleTemplate: ":title · CLI · Contributors · Tuist" description: "Learn how to contribute to Tuist by reviewing code" --- # Logging {#logging} The CLI embraces the [swift-log](https://github.com/apple/swift-log) interface for logging. The package abstracts away the implementation details of logging, allowing the CLI to be agnostic to the logging backend. The logger is dependency-injected using [swift-service-context](https://github.com/apple/swift-service-context) and can be accessed anywhere using: ```bash ServiceContext.current?.logger ``` > [!NOTE] > `swift-service-context` passes the instance using [task locals](https://developer.apple.com/documentation/swift/tasklocal) which don't propagate the value when using `Dispatch`, so if you run asynchronous code using `Dispatch`, you'll to get the instance from the context and pass it to the asynchronous operation. ## What to log {#what-to-log} Logs are not the CLI's UI. They are a tool to diagnose issues when they arise. Therefore, the more information you provide, the better. When building new features, put yourself in the shoes of a developer coming across unexpected behavior, and think about what information would be helpful to them. Ensure you you use the right [log level](https://www.swift.org/documentation/server/guides/libraries/log-levels.html). Otherwise developers won't be able to filter out the noise. --- URL: "/en/cli/shell-completions" LLMS_URL: "/en/cli/shell-completions.md" title: "Shell completions" titleTemplate: ":title · CLI · Tuist" description: "Learn how to configure your shell to auto-complete Tuist commands." --- # Shell completions If you have Tuist **globally installed** (e.g., via Homebrew), you can install shell completions for Bash and Zsh to autocomplete commands and options. ::: warning WHAT IS A GLOBAL INSTALLATION A global installation is an installation that's available in your shell's `$PATH` environment variable. This means you can run `tuist` from any directory in your terminal. This is the default installation method for Homebrew. ::: #### Zsh {#zsh} If you have [oh-my-zsh](https://ohmyz.sh/) installed, you already have a directory of automatically loading completion scripts — `.oh-my-zsh/completions`. Copy your new completion script to a new file in that directory called `_tuist`: ```bash tuist --generate-completion-script > ~/.oh-my-zsh/completions/_tuist ``` Without `oh-my-zsh`, you'll need to add a path for completion scripts to your function path, and turn on completion script autoloading. First, add these lines to `~/.zshrc`: ```bash fpath=(~/.zsh/completion $fpath) autoload -U compinit compinit ``` Next, create a directory at `~/.zsh/completion` and copy the completion script to the new directory, again into a file called `_tuist`. ```bash tuist --generate-completion-script > ~/.zsh/completion/_tuist ``` #### Bash {#bash} If you have [bash-completion](https://github.com/scop/bash-completion) installed, you can just copy your new completion script to file `/usr/local/etc/bash_completion.d/_tuist`: ```bash tuist --generate-completion-script > /usr/local/etc/bash_completion.d/_tuist ``` Without bash-completion, you'll need to source the completion script directly. Copy it to a directory such as `~/.bash_completions/`, and then add the following line to `~/.bash_profile` or `~/.bashrc`: ```bash source ~/.bash_completions/example.bash ``` #### Fish {#fish} If you use [fish shell](https://fishshell.com), you can copy your new completion script to `~/.config/fish/completions/tuist.fish`: ```bash mkdir -p ~/.config/fish/completions tuist --generate-completion-script > ~/.config/fish/completions/tuist.fish ``` --- URL: "/en/cli/logging" LLMS_URL: "/en/cli/logging.md" title: "Logging" titleTemplate: ":title · CLI · Tuist" description: "Learn how to enable and configure logging in Tuist." --- # Logging {#logging} The CLI logs messages internally to help you diagnose issues. ## Diagnose issues using logs {#diagnose-issues-using-logs} If a command invocation doesn't yield the intended results, you can diagnose the issue by inspecting the logs. The CLI forwards the logs to [OSLog](https://developer.apple.com/documentation/os/oslog) and the file-system. In every run, it creates a log file at `$XDG_STATE_HOME/tuist/logs/{uuid}.log` where `$XDG_STATE_HOME` takes the value `~/.local/state` if the environment variable is not set. By default, the CLI outputs the logs path when the execution exits unexpectedly. If it doesn't, you can find the logs in the path mentioned above (i.e., the most recent log file). > [!IMPORTANT] > Sensitive information is not redacted, so be cautious when sharing logs. ### Continuous integration {#diagnose-issues-using-logs-ci} In CI, where environments are disposable, you might want to configure your CI pipeline to export Tuist logs. Exporting artifacts is a common capability across CI services, and the configuration depends on the service you use. For example, in GitHub Actions, you can use the `actions/upload-artifact` action to upload the logs as an artifact: ```yaml name: Node CI on: [push] env: XDG_STATE_HOME: /tmp jobs: build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 # ... other steps - run: tuist generate # ... do something with the project - name: Export Tuist logs uses: actions/upload-artifact@v4 with: name: tuist-logs path: /tmp/tuist/logs/*.log ``` --- URL: "/en/cli/[command]" LLMS_URL: "/en/cli/[command].md" editLink: false titleTemplate: ":title · CLI · Tuist" ---