Merge branch 'main' into all-contributors/add-laxmorek

This commit is contained in:
Pedro Piñera Buendía 2020-10-29 10:11:31 +01:00 committed by GitHub
commit e2a11e7975
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
645 changed files with 12130 additions and 89457 deletions

View File

@ -10,6 +10,88 @@
"name": "kalkwarf", "name": "kalkwarf",
"avatar_url": "https://avatars1.githubusercontent.com/u/1033839?v=4", "avatar_url": "https://avatars1.githubusercontent.com/u/1033839?v=4",
"profile": "https://github.com/kalkwarf", "profile": "https://github.com/kalkwarf",
"contributions": [
"ideas",
"bug"
]
},
{
"login": "fortmarek",
"name": "Marek Fořt",
"avatar_url": "https://avatars0.githubusercontent.com/u/9371695?v=4",
"profile": "https://github.com/fortmarek",
"contributions": [
"ideas"
]
},
{
"login": "fortmarek",
"name": "Marek Fořt",
"avatar_url": "https://avatars0.githubusercontent.com/u/9371695?v=4",
"profile": "https://github.com/fortmarek",
"contributions": [
"ideas"
]
},
{
"login": "kwridan",
"name": "Kas",
"avatar_url": "https://avatars3.githubusercontent.com/u/11914919?v=4",
"profile": "http://www.matrixprojects.net",
"contributions": [
"code"
]
},
{
"login": "natanrolnik",
"name": "Natan Rolnik",
"avatar_url": "https://avatars3.githubusercontent.com/u/1164565?v=4",
"profile": "http://natanrolnik.me",
"contributions": [
"bug"
]
},
{
"login": "svastven",
"name": "svastven",
"avatar_url": "https://avatars0.githubusercontent.com/u/42235915?v=4",
"profile": "https://github.com/svastven",
"contributions": [
"ideas"
]
},
{
"login": "bhuemer",
"name": "Bernhard Huemer",
"avatar_url": "https://avatars2.githubusercontent.com/u/1212480?v=4",
"profile": "http://bhuemer.github.io",
"contributions": [
"ideas"
]
},
{
"login": "mollyIV",
"name": "Daniel Jankowski",
"avatar_url": "https://avatars0.githubusercontent.com/u/10795657?v=4",
"profile": "https://djankowski.dev",
"contributions": [
"ideas"
]
},
{
"login": "facumenzella",
"name": "Facundo Menzella",
"avatar_url": "https://avatars1.githubusercontent.com/u/1125252?v=4",
"profile": "https://github.com/facumenzella",
"contributions": [
"ideas"
]
},
{
"login": "eito",
"name": "Eric Ito",
"avatar_url": "https://avatars3.githubusercontent.com/u/775643?v=4",
"profile": "https://github.com/eito",
"contributions": [ "contributions": [
"ideas" "ideas"
] ]
@ -23,6 +105,33 @@
"code", "code",
"ideas" "ideas"
] ]
},
{
"login": "olejnjak",
"name": "Jakub Olejník",
"avatar_url": "https://avatars1.githubusercontent.com/u/3148214?v=4",
"profile": "https://github.com/olejnjak",
"contributions": [
"ideas"
]
},
{
"login": "lakpa",
"name": "ldindu",
"avatar_url": "https://avatars0.githubusercontent.com/u/389328?v=4",
"profile": "https://github.com/lakpa",
"contributions": [
"ideas"
]
},
{
"login": "gtsifrikas",
"name": "George Tsifrikas",
"avatar_url": "https://avatars2.githubusercontent.com/u/8904378?v=4",
"profile": "https://github.com/gtsifrikas",
"contributions": [
"blog"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@ -4,7 +4,7 @@ name: Checks
on: on:
push: push:
branches: branches:
- master - main
paths: paths:
- Sources/**/* - Sources/**/*
- Tests/**/* - Tests/**/*
@ -21,15 +21,18 @@ jobs:
swiftformat: swiftformat:
name: SwiftFormat name: SwiftFormat
runs-on: macOS-latest runs-on: macOS-latest
strategy:
matrix:
xcode: ['12.1']
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Select Xcode 11.5 - name: Select Xcode
run: sudo xcode-select -switch /Applications/Xcode_11.5.app run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app
- uses: actions/setup-ruby@v1 - uses: actions/setup-ruby@v1
with: with:
ruby-version: '2.x' ruby-version: '2.x'
- name: Install Bundler 2.0.2 - name: Install Bundler 2.1.4
run: gem install bundler --version 2.0.2 run: gem install bundler --version 2.1.4
- name: Install Bundler dependencies - name: Install Bundler dependencies
run: bundle install run: bundle install
- name: Run swiftformat - name: Run swiftformat
@ -37,15 +40,18 @@ jobs:
swiftlint: swiftlint:
name: Swiftlint name: Swiftlint
runs-on: macOS-latest runs-on: macOS-latest
strategy:
matrix:
xcode: ['12.1']
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Select Xcode 11.5 - name: Select Xcode
run: sudo xcode-select -switch /Applications/Xcode_11.5.app run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app
- uses: actions/setup-ruby@v1 - uses: actions/setup-ruby@v1
with: with:
ruby-version: '2.x' ruby-version: '2.x'
- name: Install Bundler 2.0.2 - name: Install Bundler 2.1.4
run: gem install bundler --version 2.0.2 run: gem install bundler --version 2.1.4
- name: Install Bundler dependencies - name: Install Bundler dependencies
run: bundle install run: bundle install
- name: Run swiftlint - name: Run swiftlint

View File

@ -16,10 +16,13 @@ jobs:
test: test:
name: Test name: Test
runs-on: macOS-latest runs-on: macOS-latest
strategy:
matrix:
xcode: ['12.1']
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Select Xcode 11.5 - name: Select Xcode
run: sudo xcode-select -switch /Applications/Xcode_11.5.app run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app
- name: Build Package - name: Build Package
working-directory: ./tools/fixturegen working-directory: ./tools/fixturegen
run: swift build run: swift build

37
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,37 @@
name: Release
on:
push:
tags:
- '*'
jobs:
release:
name: Release Tuist
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
with:
ref: main
fetch-depth: 0
- uses: actions/setup-ruby@v1
with:
ruby-version: '2.x'
# - name: Install Bundler 2.1.4
# run: gem install bundler --version 2.1.4
# - uses: actions/cache@v2
# with:
# path: vendor/bundle
# key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
# restore-keys: |
# ${{ runner.os }}-gems-
# - name: Bundle install
# run: |
# bundle config path vendor/bundle
# bundle install --jobs 4 --retry 3
# - name: Select Xcode 11.5
# run: sudo xcode-select -switch /Applications/Xcode_11.5.app
# - name: Create a new release
# uses: fortmarek/tapestry-action@0.1.1
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -3,7 +3,7 @@ name: Tuist
on: on:
push: push:
branches: branches:
- master - main
paths: paths:
- Sources/**/* - Sources/**/*
- Tests/**/* - Tests/**/*
@ -28,11 +28,18 @@ jobs:
runs-on: macOS-latest runs-on: macOS-latest
strategy: strategy:
matrix: matrix:
xcode: ['11.5', '12_beta'] xcode: ['11.5', '12.1']
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Select Xcode - name: Select Xcode
run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app
- uses: actions/cache@v2
name: 'Cache SPM dependencies'
with:
path: .build
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
restore-keys: |
${{ runner.os }}-spm-
- name: Run tests - name: Run tests
run: | run: |
rm -rf .coverage rm -rf .coverage
@ -43,11 +50,17 @@ jobs:
runs-on: macOS-latest runs-on: macOS-latest
strategy: strategy:
matrix: matrix:
xcode: ['11.5'] xcode: ['11.5', '12.1']
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Select Xcode - name: Select Xcode
run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app
- uses: actions/cache@v2
with:
path: .build
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
restore-keys: |
${{ runner.os }}-spm-
- name: Build Tuist for release - name: Build Tuist for release
run: swift build -c release --product tuist run: swift build -c release --product tuist
- name: Build Tuistenv for release - name: Build Tuistenv for release
@ -57,7 +70,7 @@ jobs:
runs-on: macOS-latest runs-on: macOS-latest
strategy: strategy:
matrix: matrix:
xcode: ['11.5'] xcode: ['11.5', '12.1']
feature: feature:
[ [
'generate-1', 'generate-1',
@ -65,34 +78,90 @@ jobs:
'generate-3', 'generate-3',
'generate-4', 'generate-4',
'generate-5', 'generate-5',
'generate-6',
'init', 'init',
'lint-project', 'lint-project',
'lint-code', 'lint-code',
'scaffold', 'scaffold',
'up', 'up',
'build', 'build',
'test',
] ]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Select Xcode - name: Select Xcode
run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app
- uses: actions/cache@v2
name: 'Cache SPM dependencies'
with:
path: .build
key: ${{ runner.os }}-${{ matrix.xcode }}-spm-${{ hashFiles('**/Package.resolved') }}
restore-keys: |
${{ runner.os }}-spm-
- uses: actions/setup-ruby@v1 - uses: actions/setup-ruby@v1
with: with:
ruby-version: '2.x' ruby-version: '2.x'
- name: Install Bundler 2.0.2 - name: Install Bundler 2.1.4
run: gem install bundler --version 2.0.2 run: gem install bundler --version 2.1.4
- name: Install Bundler dependencies - uses: actions/cache@v2
run: bundle install with:
path: vendor/bundle
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gems-
- name: Bundle install
run: |
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
- name: Run tests - name: Run tests
run: FEATURE=features/${{ matrix.feature }}.feature bundle exec rake features run: FEATURE=features/${{ matrix.feature }}.feature bundle exec rake features
cache_acceptance_tests: xcode_12_acceptance_tests:
name: Cache Acceptance tests (${{ matrix.feature }}) name: Xcode 12 Acceptance tests (${{ matrix.feature }})
runs-on: macOS-latest runs-on: macOS-latest
strategy: strategy:
matrix: matrix:
xcode: ['12_beta'] xcode: ['12.1']
feature: ['cache'] feature:
[
'generate-6',
'cache-xcframeworks',
'cache-frameworks',
'precompiled',
]
steps:
- uses: actions/checkout@v1
- name: Select Xcode
run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app
- uses: actions/cache@v2
name: 'Cache SPM dependencies'
with:
path: .build
key: ${{ runner.os }}-${{ matrix.xcode }}-spm-${{ hashFiles('**/Package.resolved') }}
restore-keys: |
${{ runner.os }}-spm-
- uses: actions/setup-ruby@v1
with:
ruby-version: '2.x'
- name: Install Bundler 2.1.4
run: gem install bundler --version 2.1.4
- uses: actions/cache@v2
with:
path: vendor/bundle
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gems-
- name: Bundle install
run: |
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
- name: Run tests
run: FEATURE=features/${{ matrix.feature }}.feature bundle exec rake features
upload:
if: github.ref == 'refs/heads/main'
name: Upload
runs-on: macOS-latest
strategy:
matrix:
xcode: ['12.1']
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Select Xcode - name: Select Xcode
@ -100,28 +169,18 @@ jobs:
- uses: actions/setup-ruby@v1 - uses: actions/setup-ruby@v1
with: with:
ruby-version: '2.x' ruby-version: '2.x'
- name: Install Bundler 2.0.2 - name: Install Bundler 2.1.4
run: gem install bundler --version 2.0.2 run: gem install bundler --version 2.1.4
- name: Install Bundler dependencies - uses: actions/cache@v2
run: bundle install
- name: Run tests
run: FEATURE=features/${{ matrix.feature }}.feature bundle exec rake features
upload:
if: github.ref == 'refs/heads/master'
name: Upload
runs-on: macOS-latest
steps:
- uses: actions/checkout@v1
- name: Select Xcode 11.4
run: sudo xcode-select -switch /Applications/Xcode_11.5.app
- uses: actions/setup-ruby@v1
with: with:
ruby-version: '2.x' path: vendor/bundle
- name: Install Bundler 2.0.2 key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
run: gem install bundler --version 2.0.2 restore-keys: |
- name: Install Bundler dependencies ${{ runner.os }}-gems-
run: bundle install - name: Bundle install
run: |
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
- name: Package build and upload it to GCS - name: Package build and upload it to GCS
run: | run: |
bundle exec rake package_commit bundle exec rake package_commit

View File

@ -14,10 +14,13 @@ jobs:
test: test:
name: Build name: Build
runs-on: macOS-latest runs-on: macOS-latest
strategy:
matrix:
xcode: ['12.1']
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Select Xcode 11.5 - name: Select Xcode
run: sudo xcode-select -switch /Applications/Xcode_11.5.app run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app
- name: Build Package - name: Build Package
working-directory: ./tools/tuistbench working-directory: ./tools/tuistbench
run: swift build run: swift build

View File

@ -1 +0,0 @@
1.0.0-beta.4

View File

@ -4,6 +4,87 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
## Next ## Next
### Added
- Allow specifying Development Region via new `developmentRegion` parameter in `Config`s GenerationOption. [#1062](https://github.com/tuist/tuist/pull/1867) by [@svastven](https://github.com/svastven).
- Require the `Config.swift` file to be in the Tuist directory [#693](https://github.com/tuist/tuist/issues/693) by [@mollyIV](https://github.com/mollyIV).
- Mapper for the caching logic to locate the built products directory [#1929](https://github.com/tuist/tuist/pull/1929) by [@pepibumur](https://github.com/pepibumur).
- Extended `BuildPhaseGenerator` to generate script build phases [#1932](https://github.com/tuist/tuist/pull/1932) by [@pepibumur](https://github.com/pepibumur).
- Extend the `TargetContentHasher` to account for the `Target.scripts` attribute [#1933](https://github.com/tuist/tuist/pull/1933) by [@pepibumur](https://github.com/pepibumur).
- Extend the `CacheController` to generate projects with the build phase to locate the targets' built products directory [#1933](https://github.com/tuist/tuist/pull/1933) by [@pepibumur](https://github.com/pepibumur).
- Add support for appClip [#1854](https://github.com/tuist/tuist/pull/1854) by [@lakpa](https://github.com/lakpa).
### Fixed
- Fixed non-framework/library targets having a header build phase [#367](https://github.com/tuist/tuist/issues/367) by [@eito](https://github.com/eito).
- Fixed missing profile scheme arguments when specified in manifest [#1543](https://github.com/tuist/tuist/issues/1543) by [@lakpa](https://github.com/lakpa).
- Fixed cache warming exporting unrelated .frameworks [#1939](https://github.com/tuist/tuist/pull/1939) by [@pepibumur](https://github.com/pepibumur).
- Fixed cache warming building from a clean state for every target [#1939](https://github.com/tuist/tuist/pull/1939) by [@pepibumur](https://github.com/pepibumur).
### Changed
- Some renames in the generation logic to make the generation logic easier to reason about [#1942](https://github.com/tuist/tuist/pull/1942) by [@pepibumur](https://github.com/pepibumur).
- Update some Swift dependencies [#1971](https://github.com/tuist/tuist/pull/1971) by [@pepibumur](https://github.com/pepibumur).
## 1.22.0 - Heimat
### Changed
- Autogenerated `xxx-Project` scheme is now shared [#1902](https://github.com/tuist/tuist/pull/1902) by [@fortmarek](https://github.com/fortmarek)
### Added
- Allow build phase scripts to disable dependency analysis [#1883](https://github.com/tuist/tuist/pull/1883) by [@bhuemer](https://github.com/bhuemer).
- The default generated project does not include a LaunchScreen storyboard [#265](https://github.com/tuist/tuist/issues/265) by [@mollyIV](https://github.com/mollyIV).
## 1.21.0 - PBWerk
### Added
- Allow ignoring cache when running tuist focus [#1879](https://github.com/tuist/tuist/pull/1879) by [@natanrolnik](https://github.com/natanrolnik).
### Changed
- Improve error message to have more actionable information [#921](https://github.com/tuist/tuist/issues/921) by [@mollyIV](https://github.com/mollyIV).
### Fixed
- Fix calculation of Settings hash related to Cache commands [#1869](Fix calculation of Settings hash related to Cache commands) by [@natanrolnik](https://github.com/natanrolnik)
- Fixed handling of `.tuist_version` file if the file had a trailing line break [#1900](Allow trailing line break in `.tuist_version`) by [@kalkwarf](https://github.com/kalkwarf)
## 1.20.0 - Heideberg
### Changed
- Revert using root `.package.resolved` [#1830](https://github.com/tuist/tuist/pull/1830) by [@fortmarek](https://github.com/fortmarek)
### Added
- Support for caching frameworks instead of xcframeworks [#1851](https://github.com/tuist/tuist/pull/1851)
### Fixed
- Skip synthesizing resource accessors when the file/folder is empty [#1829](https://github.com/tuist/tuist/pull/1829) by [@fortmarek](https://github.com/fortmarek)
- The redirect after the cloud authentication is not being captured from the CLI [#1846](https://github.com/tuist/tuist/pull/1846) by [@pepibumur](https://github.com/pepibumur).
## 1.19.0 - Milano
### Fixed
- Ensure `DEVELOPER_DIR` is used in all `swiftc` calls [#1819](https://github.com/tuist/tuist/pull/1819) by [@kwridan](https://github.com/kwridan)
- Fixed decoding bug on DefaultSettings [#1817](https://github.com/tuist/tuist/issues/1817) by [@jakeatoms](https://github.com/jakeatoms)
- Bool compiler error when generating accessor for plists [#1827](https://github.com/tuist/tuist/pull/1827) by [@fortmarek](https://github.com/fortmarek)
### Added
- Add Workspace Mappers [#1767](https://github.com/tuist/tuist/pull/1767) by [@kwridan](https://github.com/kwridan)
- Extended `Config`'s generationOptions with `.disableShowEnvironmentVarsInScriptPhases`. It does what you'd think. [#1782](https://github.com/tuist/tuist/pull/1782) by [@kalkwarf](https://github.com/kalkwarf)
- Generate `xxx-Project` scheme to build and test all available targets by [#1765](https://github.com/tuist/tuist/pull/1765) by [@fortmarek](https://github.com/fortmarek)
### Changed
- The `tuist edit` command adds `Setup.swift` and `Config.swift` to the generated project if they exist. [#1745](https://github.com/tuist/tuist/pull/1745) by [@laxmorek](https://github.com/laxmorek)
## 1.18.1 - Manaslu ## 1.18.1 - Manaslu
### Fixed ### Fixed

12
Gemfile
View File

@ -2,16 +2,16 @@
source "https://rubygems.org" source "https://rubygems.org"
gem "cucumber", "~> 5.1" gem "cucumber", "~> 5.2"
gem "rake", "~> 13.0" gem "rake", "~> 13.0"
gem "byebug", "~> 11.1" gem "byebug", "~> 11.1"
gem "minitest", "~> 5.14" gem "minitest", "~> 5.14"
gem "simctl", "~> 1.6" gem "simctl", "~> 1.6"
gem "rubocop", "~> 0.90.0" gem "rubocop", "~> 1.0.0"
gem "encrypted-environment", "~> 0.2.0" gem "encrypted-environment", "~> 0.2.0"
gem "google-cloud-storage", "~> 1.28" gem "google-cloud-storage", "~> 1.29"
gem "colorize", "~> 0.8.1" gem "colorize", "~> 0.8.1"
gem "cocoapods", "~> 1.9" gem "cocoapods", "~> 1.10"
gem "xcodeproj", "~> 1.18" gem "xcodeproj", "~> 1.19"
gem "highline", "~> 2.0" gem "highline", "~> 2.0"
gem "zip", "~> 2.0.2" gem "rubyzip", "~> 2.3.0"

View File

@ -2,14 +2,14 @@ GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
CFPropertyList (3.0.2) CFPropertyList (3.0.2)
activesupport (4.2.11.3) activesupport (5.2.4.4)
i18n (~> 0.7) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1) tzinfo (~> 1.1)
addressable (2.7.0) addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 5.0)
algoliasearch (1.27.2) algoliasearch (1.27.4)
httpclient (~> 2.8, >= 2.8.3) httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1) json (>= 1.5.1)
ast (2.4.1) ast (2.4.1)
@ -17,15 +17,14 @@ GEM
builder (3.2.4) builder (3.2.4)
byebug (11.1.3) byebug (11.1.3)
claide (1.0.3) claide (1.0.3)
cocoapods (1.9.3) cocoapods (1.10.0)
activesupport (>= 4.0.2, < 5) addressable (~> 2.6)
claide (>= 1.0.2, < 2.0) claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.9.3) cocoapods-core (= 1.10.0)
cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 1.2.2, < 2.0) cocoapods-downloader (>= 1.4.0, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-stats (>= 1.0.0, < 2.0)
cocoapods-trunk (>= 1.4.0, < 2.0) cocoapods-trunk (>= 1.4.0, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0)
colored2 (~> 3.1) colored2 (~> 3.1)
@ -35,21 +34,22 @@ GEM
molinillo (~> 0.6.6) molinillo (~> 0.6.6)
nap (~> 1.0) nap (~> 1.0)
ruby-macho (~> 1.4) ruby-macho (~> 1.4)
xcodeproj (>= 1.14.0, < 2.0) xcodeproj (>= 1.19.0, < 2.0)
cocoapods-core (1.9.3) cocoapods-core (1.10.0)
activesupport (>= 4.0.2, < 6) activesupport (> 5.0, < 6)
addressable (~> 2.6)
algoliasearch (~> 1.0) algoliasearch (~> 1.0)
concurrent-ruby (~> 1.1) concurrent-ruby (~> 1.1)
fuzzy_match (~> 2.0.4) fuzzy_match (~> 2.0.4)
nap (~> 1.0) nap (~> 1.0)
netrc (~> 0.11) netrc (~> 0.11)
public_suffix
typhoeus (~> 1.0) typhoeus (~> 1.0)
cocoapods-deintegrate (1.0.4) cocoapods-deintegrate (1.0.4)
cocoapods-downloader (1.3.0) cocoapods-downloader (1.4.0)
cocoapods-plugins (1.0.0) cocoapods-plugins (1.0.0)
nap nap
cocoapods-search (1.0.0) cocoapods-search (1.0.0)
cocoapods-stats (1.1.0)
cocoapods-trunk (1.5.0) cocoapods-trunk (1.5.0)
nap (>= 0.8, < 2.0) nap (>= 0.8, < 2.0)
netrc (~> 0.11) netrc (~> 0.11)
@ -57,14 +57,14 @@ GEM
colored2 (3.1.2) colored2 (3.1.2)
colorize (0.8.1) colorize (0.8.1)
concurrent-ruby (1.1.7) concurrent-ruby (1.1.7)
cucumber (5.1.1) cucumber (5.2.0)
builder (~> 3.2, >= 3.2.4) builder (~> 3.2, >= 3.2.4)
cucumber-core (~> 8.0, >= 8.0.1) cucumber-core (~> 8.0, >= 8.0.1)
cucumber-create-meta (~> 2.0, >= 2.0.2) cucumber-create-meta (~> 2.0, >= 2.0.2)
cucumber-cucumber-expressions (~> 10.3, >= 10.3.0) cucumber-cucumber-expressions (~> 10.3, >= 10.3.0)
cucumber-gherkin (~> 15.0, >= 15.0.2) cucumber-gherkin (~> 15.0, >= 15.0.2)
cucumber-html-formatter (~> 9.0, >= 9.0.0) cucumber-html-formatter (~> 9.0, >= 9.0.0)
cucumber-messages (~> 13.0, >= 13.0.1) cucumber-messages (~> 13.1, >= 13.1.0)
cucumber-wire (~> 4.0, >= 4.0.1) cucumber-wire (~> 4.0, >= 4.0.1)
diff-lcs (~> 1.4, >= 1.4.4) diff-lcs (~> 1.4, >= 1.4.4)
multi_test (~> 0.1, >= 0.1.2) multi_test (~> 0.1, >= 0.1.2)
@ -81,7 +81,7 @@ GEM
cucumber-messages (~> 13.0, >= 13.0.1) cucumber-messages (~> 13.0, >= 13.0.1)
cucumber-html-formatter (9.0.0) cucumber-html-formatter (9.0.0)
cucumber-messages (~> 13.0, >= 13.0.1) cucumber-messages (~> 13.0, >= 13.0.1)
cucumber-messages (13.0.1) cucumber-messages (13.1.0)
protobuf-cucumber (~> 3.10, >= 3.10.8) protobuf-cucumber (~> 3.10, >= 3.10.8)
cucumber-tag-expressions (2.0.4) cucumber-tag-expressions (2.0.4)
cucumber-wire (4.0.1) cucumber-wire (4.0.1)
@ -105,13 +105,14 @@ GEM
fourflusher (2.3.1) fourflusher (2.3.1)
fuzzy_match (2.0.4) fuzzy_match (2.0.4)
gh_inspector (1.1.3) gh_inspector (1.1.3)
google-api-client (0.43.0) google-api-client (0.46.1)
addressable (~> 2.5, >= 2.5.1) addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9) googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0) httpclient (>= 2.8.1, < 3.0)
mini_mime (~> 1.0) mini_mime (~> 1.0)
representable (~> 3.0) representable (~> 3.0)
retriable (>= 2.0, < 4.0) retriable (>= 2.0, < 4.0)
rexml
signet (~> 0.12) signet (~> 0.12)
google-cloud-core (1.5.0) google-cloud-core (1.5.0)
google-cloud-env (~> 1.0) google-cloud-env (~> 1.0)
@ -119,14 +120,14 @@ GEM
google-cloud-env (1.3.3) google-cloud-env (1.3.3)
faraday (>= 0.17.3, < 2.0) faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.0.1) google-cloud-errors (1.0.1)
google-cloud-storage (1.28.0) google-cloud-storage (1.29.1)
addressable (~> 2.5) addressable (~> 2.5)
digest-crc (~> 0.4) digest-crc (~> 0.4)
google-api-client (~> 0.33) google-api-client (~> 0.33)
google-cloud-core (~> 1.2) google-cloud-core (~> 1.2)
googleauth (~> 0.9) googleauth (~> 0.9)
mini_mime (~> 1.0) mini_mime (~> 1.0)
googleauth (0.13.1) googleauth (0.14.0)
faraday (>= 0.17.3, < 2.0) faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0) jwt (>= 1.4, < 3.0)
memoist (~> 0.16) memoist (~> 0.16)
@ -135,9 +136,9 @@ GEM
signet (~> 0.14) signet (~> 0.14)
highline (2.0.3) highline (2.0.3)
httpclient (2.8.3) httpclient (2.8.3)
i18n (0.9.5) i18n (1.8.5)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
json (2.3.0) json (2.3.1)
jwt (2.2.2) jwt (2.2.2)
memoist (0.16.2) memoist (0.16.2)
middleware (0.1.0) middleware (0.1.0)
@ -153,36 +154,37 @@ GEM
netrc (0.11.0) netrc (0.11.0)
os (1.1.1) os (1.1.1)
parallel (1.19.2) parallel (1.19.2)
parser (2.7.1.4) parser (2.7.2.0)
ast (~> 2.4.1) ast (~> 2.4.1)
protobuf-cucumber (3.10.8) protobuf-cucumber (3.10.8)
activesupport (>= 3.2) activesupport (>= 3.2)
middleware middleware
thor thor
thread_safe thread_safe
public_suffix (4.0.5) public_suffix (4.0.6)
rainbow (3.0.0) rainbow (3.0.0)
rake (13.0.1) rake (13.0.1)
regexp_parser (1.7.1) regexp_parser (1.8.2)
representable (3.0.4) representable (3.0.4)
declarative (< 0.1.0) declarative (< 0.1.0)
declarative-option (< 0.2.0) declarative-option (< 0.2.0)
uber (< 0.2.0) uber (< 0.2.0)
retriable (3.1.2) retriable (3.1.2)
rexml (3.2.4) rexml (3.2.4)
rubocop (0.90.0) rubocop (1.0.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.7.1.1) parser (>= 2.7.1.5)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.7) regexp_parser (>= 1.8)
rexml rexml
rubocop-ast (>= 0.3.0, < 1.0) rubocop-ast (>= 0.6.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0) unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (0.3.0) rubocop-ast (1.0.1)
parser (>= 2.7.1.4) parser (>= 2.7.1.5)
ruby-macho (1.4.0) ruby-macho (1.4.0)
ruby-progressbar (1.10.1) ruby-progressbar (1.10.1)
rubyzip (2.3.0)
signet (0.14.0) signet (0.14.0)
addressable (~> 2.3) addressable (~> 2.3)
faraday (>= 0.17.3, < 2.0) faraday (>= 0.17.3, < 2.0)
@ -201,31 +203,30 @@ GEM
thread_safe (~> 0.1) thread_safe (~> 0.1)
uber (0.1.0) uber (0.1.0)
unicode-display_width (1.7.0) unicode-display_width (1.7.0)
xcodeproj (1.18.0) xcodeproj (1.19.0)
CFPropertyList (>= 2.3.3, < 4.0) CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3) atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0) claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1) colored2 (~> 3.1)
nanaimo (~> 0.3.0) nanaimo (~> 0.3.0)
zip (2.0.2)
PLATFORMS PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
byebug (~> 11.1) byebug (~> 11.1)
cocoapods (~> 1.9) cocoapods (~> 1.10)
colorize (~> 0.8.1) colorize (~> 0.8.1)
cucumber (~> 5.1) cucumber (~> 5.2)
encrypted-environment (~> 0.2.0) encrypted-environment (~> 0.2.0)
google-cloud-storage (~> 1.28) google-cloud-storage (~> 1.29)
highline (~> 2.0) highline (~> 2.0)
minitest (~> 5.14) minitest (~> 5.14)
rake (~> 13.0) rake (~> 13.0)
rubocop (~> 0.90.0) rubocop (~> 1.0.0)
rubyzip (~> 2.3.0)
simctl (~> 1.6) simctl (~> 1.6)
xcodeproj (~> 1.18) xcodeproj (~> 1.19)
zip (~> 2.0.2)
BUNDLED WITH BUNDLED WITH
2.1.2 2.1.4

View File

@ -150,8 +150,8 @@
"repositoryURL": "https://github.com/apple/swift-argument-parser", "repositoryURL": "https://github.com/apple/swift-argument-parser",
"state": { "state": {
"branch": null, "branch": null,
"revision": "7255fd547f70468e19abbac5f7964f1ef309ad92", "revision": "92646c0cdbaca076c8d3d0207891785b3379cbff",
"version": "0.2.1" "version": "0.3.1"
} }
}, },
{ {
@ -168,8 +168,8 @@
"repositoryURL": "https://github.com/apple/swift-tools-support-core", "repositoryURL": "https://github.com/apple/swift-tools-support-core",
"state": { "state": {
"branch": null, "branch": null,
"revision": "f39fc6c12266697b1585589090f0004903974685", "revision": "243beea77d20db46647a3de4765c96e2c801c7c7",
"version": "0.1.10" "version": "0.1.12"
} }
}, },
{ {
@ -177,8 +177,8 @@
"repositoryURL": "https://github.com/httpswift/swifter.git", "repositoryURL": "https://github.com/httpswift/swifter.git",
"state": { "state": {
"branch": null, "branch": null,
"revision": "8b5afb48ae64d4f729f0489ddcfe09c62b9c3687", "revision": "9483a5d459b45c3ffd059f7b55f9638e268632fd",
"version": "1.4.7" "version": "1.5.0"
} }
}, },
{ {
@ -195,8 +195,8 @@
"repositoryURL": "https://github.com/thii/xcbeautify.git", "repositoryURL": "https://github.com/thii/xcbeautify.git",
"state": { "state": {
"branch": null, "branch": null,
"revision": "77bf4eae0f090dbf7cbcabb68fcaf0e7ff9826db", "revision": "fd7b0b6972809eead52b9016b383cf6d467e00b0",
"version": "0.8.0" "version": "0.8.1"
} }
}, },
{ {
@ -204,8 +204,8 @@
"repositoryURL": "https://github.com/tuist/XcodeProj", "repositoryURL": "https://github.com/tuist/XcodeProj",
"state": { "state": {
"branch": null, "branch": null,
"revision": "81bb2bb333eafa68f8ecd8187a4bb56d51e78e97", "revision": "2ae8e322ddbed99655802533441bc52f9ae76f24",
"version": "7.14.0" "version": "7.17.0"
} }
}, },
{ {
@ -231,8 +231,8 @@
"repositoryURL": "https://github.com/marmelroy/Zip.git", "repositoryURL": "https://github.com/marmelroy/Zip.git",
"state": { "state": {
"branch": null, "branch": null,
"revision": "80b1c3005ee25b4c7ce46c4029ac3347e8d5e37e", "revision": "bd19d974e8a38cc8d3a88c90c8a107386c3b8ccf",
"version": "2.0.0" "version": "2.1.1"
} }
} }
] ]

View File

@ -27,19 +27,19 @@ let package = Package(
targets: ["TuistGenerator"]), targets: ["TuistGenerator"]),
], ],
dependencies: [ dependencies: [
.package(url: "https://github.com/tuist/XcodeProj", .upToNextMajor(from: "7.11.0")), .package(url: "https://github.com/tuist/XcodeProj", .upToNextMajor(from: "7.17.0")),
.package(url: "https://github.com/IBM-Swift/BlueSignals", .upToNextMajor(from: "1.0.21")), .package(url: "https://github.com/IBM-Swift/BlueSignals", .upToNextMajor(from: "1.0.21")),
.package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "5.0.1")), .package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "5.1.1")),
.package(url: "https://github.com/rnine/Checksum.git", .upToNextMajor(from: "1.0.2")), .package(url: "https://github.com/rnine/Checksum.git", .upToNextMajor(from: "1.0.2")),
.package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.2.0")), .package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.4.0")),
.package(url: "https://github.com/thii/xcbeautify.git", .upToNextMajor(from: "0.8.0")), .package(url: "https://github.com/thii/xcbeautify.git", .upToNextMajor(from: "0.8.1")),
.package(url: "https://github.com/krzyzanowskim/CryptoSwift", .upToNextMajor(from: "1.3.0")), .package(url: "https://github.com/krzyzanowskim/CryptoSwift", .upToNextMajor(from: "1.3.0")),
.package(url: "https://github.com/stencilproject/Stencil", .branch("master")), .package(url: "https://github.com/stencilproject/Stencil", .branch("master")),
.package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", .upToNextMajor(from: "4.1.0")), .package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", .upToNextMajor(from: "4.1.0")),
.package(url: "https://github.com/httpswift/swifter.git", .upToNextMajor(from: "1.4.7")), .package(url: "https://github.com/httpswift/swifter.git", .upToNextMajor(from: "1.5.0")),
.package(url: "https://github.com/apple/swift-tools-support-core", .upToNextMinor(from: "0.1.1")), .package(url: "https://github.com/apple/swift-tools-support-core", .upToNextMinor(from: "0.1.12")),
.package(url: "https://github.com/apple/swift-argument-parser", .upToNextMajor(from: "0.0.6")), .package(url: "https://github.com/apple/swift-argument-parser", .upToNextMajor(from: "0.3.1")),
.package(url: "https://github.com/marmelroy/Zip.git", .upToNextMinor(from: "2.0.0")), .package(url: "https://github.com/marmelroy/Zip.git", .upToNextMinor(from: "2.1.1")),
.package(url: "https://github.com/tuist/GraphViz.git", .branch("tuist")), .package(url: "https://github.com/tuist/GraphViz.git", .branch("tuist")),
.package(url: "https://github.com/fortmarek/SwiftGen", .branch("stable")), .package(url: "https://github.com/fortmarek/SwiftGen", .branch("stable")),
.package(url: "https://github.com/fortmarek/StencilSwiftKit.git", .branch("stable")), .package(url: "https://github.com/fortmarek/StencilSwiftKit.git", .branch("stable")),
@ -75,11 +75,11 @@ let package = Package(
), ),
.target( .target(
name: "TuistKit", name: "TuistKit",
dependencies: ["XcodeProj", "SwiftToolsSupport-auto", "ArgumentParser", "TuistSupport", "TuistGenerator", "TuistCache", "TuistAutomation", "ProjectDescription", "Signals", "RxSwift", "RxBlocking", "Checksum", "TuistLoader", "TuistInsights", "TuistScaffold", "TuistSigning", "TuistCloud", "TuistDoc", "GraphViz", "TuistMigration"] dependencies: ["XcodeProj", "SwiftToolsSupport-auto", "ArgumentParser", "TuistSupport", "TuistGenerator", "TuistCache", "TuistAutomation", "ProjectDescription", "Signals", "RxSwift", "RxBlocking", "Checksum", "TuistLoader", "TuistInsights", "TuistScaffold", "TuistSigning", "TuistDependencies", "TuistCloud", "TuistDoc", "GraphViz", "TuistMigration"]
), ),
.testTarget( .testTarget(
name: "TuistKitTests", name: "TuistKitTests",
dependencies: ["TuistKit", "TuistAutomation", "TuistSupportTesting", "TuistCoreTesting", "ProjectDescription", "RxBlocking", "TuistLoaderTesting", "TuistCacheTesting", "TuistGeneratorTesting", "TuistScaffoldTesting", "TuistCloudTesting", "TuistAutomationTesting", "TuistSigningTesting", "TuistMigrationTesting", "TuistDocTesting"] dependencies: ["TuistKit", "TuistAutomation", "TuistSupportTesting", "TuistCoreTesting", "ProjectDescription", "RxBlocking", "TuistLoaderTesting", "TuistCacheTesting", "TuistGeneratorTesting", "TuistScaffoldTesting", "TuistCloudTesting", "TuistAutomationTesting", "TuistSigningTesting", "TuistDependenciesTesting", "TuistMigrationTesting", "TuistDocTesting"]
), ),
.testTarget( .testTarget(
name: "TuistKitIntegrationTests", name: "TuistKitIntegrationTests",
@ -151,7 +151,7 @@ let package = Package(
), ),
.target( .target(
name: "TuistCacheTesting", name: "TuistCacheTesting",
dependencies: ["TuistCache", "SwiftToolsSupport-auto", "TuistCore", "RxTest", "RxSwift"] dependencies: ["TuistCache", "SwiftToolsSupport-auto", "TuistCore", "RxTest", "RxSwift", "TuistSupportTesting"]
), ),
.target( .target(
name: "TuistCloud", name: "TuistCloud",
@ -229,6 +229,22 @@ let package = Package(
name: "TuistSigningIntegrationTests", name: "TuistSigningIntegrationTests",
dependencies: ["TuistSigning", "TuistSupportTesting", "TuistCoreTesting", "TuistSigningTesting"] dependencies: ["TuistSigning", "TuistSupportTesting", "TuistCoreTesting", "TuistSigningTesting"]
), ),
.target(
name: "TuistDependencies",
dependencies: ["TuistCore", "TuistSupport"]
),
.target(
name: "TuistDependenciesTesting",
dependencies: ["TuistDependencies"]
),
.testTarget(
name: "TuistDependenciesTests",
dependencies: ["TuistDependencies", "TuistDependenciesTesting", "TuistCoreTesting", "TuistSupportTesting"]
),
.testTarget(
name: "TuistDependenciesIntegrationTests",
dependencies: ["TuistDependencies", "TuistDependenciesTesting", "TuistCoreTesting", "TuistSupportTesting"]
),
.target( .target(
name: "TuistMigration", name: "TuistMigration",
dependencies: ["TuistCore", "TuistSupport", "XcodeProj", "SwiftToolsSupport-auto"] dependencies: ["TuistCore", "TuistSupport", "XcodeProj", "SwiftToolsSupport-auto"]

View File

@ -1,8 +1,6 @@
<div align="center"> <div align="center">
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<img src="assets/header.gif"/> <img src="assets/header.gif"/>
<a href="#contributors-"><img src="https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square" alt="All contributors"></a>
<img src="https://github.com/tuist/tuist/workflows/Tuist/badge.svg" alt="Tuist"> <img src="https://github.com/tuist/tuist/workflows/Tuist/badge.svg" alt="Tuist">
<img src="https://img.shields.io/github/v/release/tuist/tuist?include_prereleases&style=flat-square" alt="Latest Version"> <img src="https://img.shields.io/github/v/release/tuist/tuist?include_prereleases&style=flat-square" alt="Latest Version">
<a href="http://slack.tuist.io"><img src="http://slack.tuist.io/badge.svg" alt="Slack"></a> <a href="http://slack.tuist.io"><img src="http://slack.tuist.io/badge.svg" alt="Slack"></a>
@ -17,6 +15,7 @@
<img src="https://img.shields.io/opencollective/all/tuistapp?style=flat-square" alt="Backers and sponsors"> <img src="https://img.shields.io/opencollective/all/tuistapp?style=flat-square" alt="Backers and sponsors">
<img src="https://img.shields.io/github/license/tuist/tuist?style=flat-square" alt="License"> <img src="https://img.shields.io/github/license/tuist/tuist?style=flat-square" alt="License">
<img src="https://img.shields.io/badge/Powered%20by-Tuist-blue" alt="Powered by Tuist"> <img src="https://img.shields.io/badge/Powered%20by-Tuist-blue" alt="Powered by Tuist">
<a href="#contributors-"><img src="https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square" alt="Contributors"></a>
</div> </div>
## What's Tuist 🕺 ## What's Tuist 🕺
@ -55,6 +54,12 @@ The repository is a monorepo with multiple projects:
Do you want to know more about what Tuist can offer you? Or perhaps want to contribute to the project and you need a starting point? You can check out the [project documentation](https://tuist.io/docs/usage/getting-started/). Do you want to know more about what Tuist can offer you? Or perhaps want to contribute to the project and you need a starting point? You can check out the [project documentation](https://tuist.io/docs/usage/getting-started/).
## Supported by MacStadium
MacStadium supports this project by providing Mac mini hardware that we can use for running performance tests.
<img width="200" src="assets/MacStadium.png"/>
## Contribute 👩‍💻 ## Contribute 👩‍💻
If you are interested in contributed to the project, our documentation has a section with resources for contributors. We recommend starting from [this page](https://tuist.io/docs/contribution/getting-started/). If you are interested in contributed to the project, our documentation has a section with resources for contributors. We recommend starting from [this page](https://tuist.io/docs/contribution/getting-started/).
@ -68,8 +73,21 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- markdownlint-disable --> <!-- markdownlint-disable -->
<table> <table>
<tr> <tr>
<td align="center"><a href="https://github.com/kalkwarf"><img src="https://avatars1.githubusercontent.com/u/1033839?v=4" width="100px;" alt=""/><br /><sub><b>kalkwarf</b></sub></a><br /><a href="#ideas-kalkwarf" title="Ideas, Planning, & Feedback">🤔</a></td> <td align="center"><a href="https://github.com/kalkwarf"><img src="https://avatars1.githubusercontent.com/u/1033839?v=4" width="100px;" alt=""/><br /><sub><b>kalkwarf</b></sub></a><br /><a href="#ideas-kalkwarf" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/tuist/tuist/issues?q=author%3Akalkwarf" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/fortmarek"><img src="https://avatars0.githubusercontent.com/u/9371695?v=4" width="100px;" alt=""/><br /><sub><b>Marek Fořt</b></sub></a><br /><a href="#ideas-fortmarek" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/laxmorek"><img src="https://avatars2.githubusercontent.com/u/4774319?v=4" width="100px;" alt=""/><br /><sub><b>Kamil Harasimowicz</b></sub></a><br /><a href="https://github.com/tuist/tuist/commits?author=laxmorek" title="Code">💻</a> <a href="#ideas-laxmorek" title="Ideas, Planning, & Feedback">🤔</a></td> <td align="center"><a href="https://github.com/laxmorek"><img src="https://avatars2.githubusercontent.com/u/4774319?v=4" width="100px;" alt=""/><br /><sub><b>Kamil Harasimowicz</b></sub></a><br /><a href="https://github.com/tuist/tuist/commits?author=laxmorek" title="Code">💻</a> <a href="#ideas-laxmorek" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="http://www.matrixprojects.net"><img src="https://avatars3.githubusercontent.com/u/11914919?v=4" width="100px;" alt=""/><br /><sub><b>Kas</b></sub></a><br /><a href="https://github.com/tuist/tuist/commits?author=kwridan" title="Code">💻</a></td>
<td align="center"><a href="http://natanrolnik.me"><img src="https://avatars3.githubusercontent.com/u/1164565?v=4" width="100px;" alt=""/><br /><sub><b>Natan Rolnik</b></sub></a><br /><a href="https://github.com/tuist/tuist/issues?q=author%3Anatanrolnik" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/svastven"><img src="https://avatars0.githubusercontent.com/u/42235915?v=4" width="100px;" alt=""/><br /><sub><b>svastven</b></sub></a><br /><a href="#ideas-svastven" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="http://bhuemer.github.io"><img src="https://avatars2.githubusercontent.com/u/1212480?v=4" width="100px;" alt=""/><br /><sub><b>Bernhard Huemer</b></sub></a><br /><a href="#ideas-bhuemer" title="Ideas, Planning, & Feedback">🤔</a></td>
</tr>
<tr>
<td align="center"><a href="https://djankowski.dev"><img src="https://avatars0.githubusercontent.com/u/10795657?v=4" width="100px;" alt=""/><br /><sub><b>Daniel Jankowski</b></sub></a><br /><a href="#ideas-mollyIV" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/facumenzella"><img src="https://avatars1.githubusercontent.com/u/1125252?v=4" width="100px;" alt=""/><br /><sub><b>Facundo Menzella</b></sub></a><br /><a href="#ideas-facumenzella" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/eito"><img src="https://avatars3.githubusercontent.com/u/775643?v=4" width="100px;" alt=""/><br /><sub><b>Eric Ito</b></sub></a><br /><a href="#ideas-eito" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/olejnjak"><img src="https://avatars1.githubusercontent.com/u/3148214?v=4" width="100px;" alt=""/><br /><sub><b>Jakub Olejník</b></sub></a><br /><a href="#ideas-olejnjak" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/lakpa"><img src="https://avatars0.githubusercontent.com/u/389328?v=4" width="100px;" alt=""/><br /><sub><b>ldindu</b></sub></a><br /><a href="#ideas-lakpa" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/gtsifrikas"><img src="https://avatars2.githubusercontent.com/u/8904378?v=4" width="100px;" alt=""/><br /><sub><b>George Tsifrikas</b></sub></a><br /><a href="#blog-gtsifrikas" title="Blogposts">📝</a></td>
</tr> </tr>
</table> </table>

View File

@ -2,19 +2,9 @@
This document describes the process of releasing new versions of tuist. This document describes the process of releasing new versions of tuist.
1. First make sure you are in master and the latest changes are pulled: `git pull origin master` 1. Determine the new version:
2. Ensure that the project is in a releasable state by running the tests: `swift test` and `bundle exec rake features`.
3. Determine the new version:
- Major if there's been a breaking change. - Major if there's been a breaking change.
- Minor by default. - Minor by default.
- Patch if it's a hotfix release. - Patch if it's a hotfix release.
2. Run `tapestry github-release version-number` (eg `tapestry github-release 1.1.0`) *(Install [tapestry](https://github.com/ackeecz/tapestry) and [tuist](https://github.com/tuist/tuist) if you don't have them installed already)*.
4. Update the version in the `Constants.swift` file.
5. Update the `CHANGELOG.md` to include the version section.
6. Commit the changes and tag the commit with the version `git tag x.y.z`.
7. Select the Xcode version 11.5 before building the binaries: `sudo xcode-select -s /Applications/Xcode_11.5.app`.
8. Package and upload the release to GCS by running `bundle exec rake release`.
9. Upload the installation scripts to GCS by running `bundle exec rake release_scripts`.
10. Create a release on GitHub with the version as a title, the body from the CHANGELOG file, and attach the artifacts in the `build/` directory.
11. Run `tuist update` and verify that the new version is installed and runs.

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
SWIFTDOC_VERSION = "1.0.0-beta.4".freeze SWIFTDOC_VERSION = "1.0.0-beta.4".freeze
SWIFTLINT_VERSION = "0.40.2".freeze
require 'rubygems' require 'rubygems'
require 'cucumber' require 'cucumber'
@ -21,7 +22,7 @@ end
desc("Updates swift-doc binary with the latest version available.") desc("Updates swift-doc binary with the latest version available.")
task :swift_doc_update do task :swift_doc_update do
root_dir = Dir.pwd.strip root_dir = File.expand_path(__dir__)
Dir.mktmpdir do |temporary_dir| Dir.mktmpdir do |temporary_dir|
Dir.chdir(temporary_dir) do Dir.chdir(temporary_dir) do
system("curl", "-LO", "https://github.com/SwiftDocOrg/swift-doc/archive/#{SWIFTDOC_VERSION}.zip") system("curl", "-LO", "https://github.com/SwiftDocOrg/swift-doc/archive/#{SWIFTDOC_VERSION}.zip")
@ -32,7 +33,20 @@ task :swift_doc_update do
system("cp", "swift-doc/swift-doc-#{SWIFTDOC_VERSION}/.build/release/swift-doc", "#{root_dir}/vendor/swift-doc") system("cp", "swift-doc/swift-doc-#{SWIFTDOC_VERSION}/.build/release/swift-doc", "#{root_dir}/vendor/swift-doc")
end end
end end
system("rm .swift-doc.version && echo \"#{SWIFTDOC_VERSION}\" >> .swift-doc.version") File.write(File.join(root_dir, "vendor/.swiftdoc.version"), SWIFTDOC_VERSION)
end
desc("Updates swift-lint binary with the latest version available.")
task :swift_lint_update do
root_dir = File.expand_path(__dir__)
Dir.mktmpdir do |temporary_dir|
Dir.chdir(temporary_dir) do
system("curl", "-LO", "https://github.com/realm/SwiftLint/releases/download/#{SWIFTLINT_VERSION}/portable_swiftlint.zip")
extract_zip("portable_swiftlint.zip", "portable_swiftlint")
system("cp", "portable_swiftlint/swiftlint", "#{root_dir}/vendor/swiftlint")
end
end
File.write(File.join(root_dir, "vendor/.swiftlint.version"), SWIFTLINT_VERSION)
end end
desc("Formats the code style") desc("Formats the code style")
@ -61,6 +75,11 @@ task :style_ruby_correct do
system("bundle", "exec", "rubocop", "-a") system("bundle", "exec", "rubocop", "-a")
end end
desc("Builds and archive a release version of tuist and tuistenv for local testing.")
task :local_package do
package
end
desc("Builds, archives, and publishes tuist and tuistenv for release") desc("Builds, archives, and publishes tuist and tuistenv for release")
task :release do task :release do
decrypt_secrets decrypt_secrets

View File

@ -8,13 +8,17 @@ public struct Config: Codable, Equatable {
/// ///
/// - xcodeProjectName(TemplateString): When passed, Tuist generates the project with the specific name on disk instead of using the project name. /// - xcodeProjectName(TemplateString): When passed, Tuist generates the project with the specific name on disk instead of using the project name.
/// - organizationName(String): When passed, Tuist generates the project with the specific organization name. /// - organizationName(String): When passed, Tuist generates the project with the specific organization name.
/// - developmentRegion(String): When passed, Tuist generates the project with the specific development region.
/// - disableAutogeneratedSchemes: When passed, Tuist generates the project only with custom specified schemes, autogenerated default schemes are skipped /// - disableAutogeneratedSchemes: When passed, Tuist generates the project only with custom specified schemes, autogenerated default schemes are skipped
/// - disableSynthesizedResourceAccessors: When passed, Tuist does not synthesize resource accessors /// - disableSynthesizedResourceAccessors: When passed, Tuist does not synthesize resource accessors
/// - disableShowEnvironmentVarsInScriptPhases: When passed, Tuist disables echoing the ENV in shell script build phases
public enum GenerationOptions: Encodable, Decodable, Equatable { public enum GenerationOptions: Encodable, Decodable, Equatable {
case xcodeProjectName(TemplateString) case xcodeProjectName(TemplateString)
case organizationName(String) case organizationName(String)
case developmentRegion(String)
case disableAutogeneratedSchemes case disableAutogeneratedSchemes
case disableSynthesizedResourceAccessors case disableSynthesizedResourceAccessors
case disableShowEnvironmentVarsInScriptPhases
} }
/// Generation options. /// Generation options.
@ -26,7 +30,7 @@ public struct Config: Codable, Equatable {
/// Cloud configuration. /// Cloud configuration.
public let cloud: Cloud? public let cloud: Cloud?
/// Initializes the tuist cofiguration. /// Initializes the tuist configuration.
/// ///
/// - Parameters: /// - Parameters:
/// - compatibleXcodeVersions: List of Xcode versions the project is compatible with. /// - compatibleXcodeVersions: List of Xcode versions the project is compatible with.
@ -45,7 +49,12 @@ public struct Config: Codable, Equatable {
extension Config.GenerationOptions { extension Config.GenerationOptions {
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case xcodeProjectName, organizationName, disableAutogeneratedSchemes, disableSynthesizedResourceAccessors case xcodeProjectName
case organizationName
case developmentRegion
case disableAutogeneratedSchemes
case disableSynthesizedResourceAccessors
case disableShowEnvironmentVarsInScriptPhases
} }
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
@ -63,6 +72,12 @@ extension Config.GenerationOptions {
self = .organizationName(organizationName) self = .organizationName(organizationName)
return return
} }
if container.allKeys.contains(.developmentRegion), try container.decodeNil(forKey: .developmentRegion) == false {
var associatedValues = try container.nestedUnkeyedContainer(forKey: .developmentRegion)
let developmentRegion = try associatedValues.decode(String.self)
self = .developmentRegion(developmentRegion)
return
}
if container.allKeys.contains(.disableAutogeneratedSchemes), try container.decode(Bool.self, forKey: .disableAutogeneratedSchemes) { if container.allKeys.contains(.disableAutogeneratedSchemes), try container.decode(Bool.self, forKey: .disableAutogeneratedSchemes) {
self = .disableAutogeneratedSchemes self = .disableAutogeneratedSchemes
return return
@ -71,6 +86,11 @@ extension Config.GenerationOptions {
self = .disableSynthesizedResourceAccessors self = .disableSynthesizedResourceAccessors
return return
} }
if container.allKeys.contains(.disableShowEnvironmentVarsInScriptPhases), try container.decode(Bool.self, forKey: .disableShowEnvironmentVarsInScriptPhases) {
self = .disableShowEnvironmentVarsInScriptPhases
return
}
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Unknown enum case")) throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Unknown enum case"))
} }
@ -84,10 +104,15 @@ extension Config.GenerationOptions {
case let .organizationName(name): case let .organizationName(name):
var associatedValues = container.nestedUnkeyedContainer(forKey: .organizationName) var associatedValues = container.nestedUnkeyedContainer(forKey: .organizationName)
try associatedValues.encode(name) try associatedValues.encode(name)
case let .developmentRegion(developmentRegion):
var associatedValues = container.nestedUnkeyedContainer(forKey: .developmentRegion)
try associatedValues.encode(developmentRegion)
case .disableAutogeneratedSchemes: case .disableAutogeneratedSchemes:
try container.encode(true, forKey: .disableAutogeneratedSchemes) try container.encode(true, forKey: .disableAutogeneratedSchemes)
case .disableSynthesizedResourceAccessors: case .disableSynthesizedResourceAccessors:
try container.encode(true, forKey: .disableSynthesizedResourceAccessors) try container.encode(true, forKey: .disableSynthesizedResourceAccessors)
case .disableShowEnvironmentVarsInScriptPhases:
try container.encode(true, forKey: .disableShowEnvironmentVarsInScriptPhases)
} }
} }
} }
@ -103,10 +128,14 @@ public func == (lhs: TuistConfig.GenerationOptions, rhs: TuistConfig.GenerationO
return lhs.rawString == rhs.rawString return lhs.rawString == rhs.rawString
case let (.organizationName(lhs), .organizationName(rhs)): case let (.organizationName(lhs), .organizationName(rhs)):
return lhs == rhs return lhs == rhs
case let (.developmentRegion(lhs), .developmentRegion(rhs)):
return lhs == rhs
case (.disableAutogeneratedSchemes, .disableAutogeneratedSchemes): case (.disableAutogeneratedSchemes, .disableAutogeneratedSchemes):
return true return true
case (.disableSynthesizedResourceAccessors, .disableSynthesizedResourceAccessors): case (.disableSynthesizedResourceAccessors, .disableSynthesizedResourceAccessors):
return true return true
case (.disableShowEnvironmentVarsInScriptPhases, .disableShowEnvironmentVarsInScriptPhases):
return true
default: default:
return false return false
} }

View File

@ -0,0 +1,36 @@
import Foundation
/// Dependency contains the description of any kind of dependency of your Xcode project.
public struct Dependency: Codable, Equatable {
/// Name of the dependency
let name: String
/// Type of requirement for the given dependency
let requirement: Dependency.Requirement
public init(name: String, requirement: Dependency.Requirement) {
self.name = name
self.requirement = requirement
}
/// Carthage dependency initailizer
/// - Parameter name: Name of the dependency
/// - Parameter requirement: Type of requirement for the given dependency
/// - Returns Dependency
public static func carthage(name: String, requirement: Dependency.Requirement) -> Dependency {
Dependency(name: name, requirement: requirement)
}
public static func == (lhs: Dependency, rhs: Dependency) -> Bool {
lhs.name == rhs.name && lhs.requirement == rhs.requirement
}
}
public struct Dependencies: Codable, Equatable {
private let dependencies: [Dependency]
public init(_ dependencies: [Dependency]) {
self.dependencies = dependencies
dumpIfNeeded(self)
}
}

View File

@ -0,0 +1,30 @@
import Foundation
// The idea of Requirement comes from SPM PackageRequirement
// https://github.com/apple/swift-package-manager/blob/main/Sources/PackageDescription/PackageRequirement.swift
// This could be further extended for more cases
/// DependencyRequiement describes which dependency the project needs.
public extension Dependency {
enum Requirement: Codable, Equatable {
case exact(Version)
// case range(Range<Version>)
// case branch(String)
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self = .exact(try container.decode(Version.self, forKey: .exact))
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if case let .exact(version) = self {
try container.encode(version, forKey: .exact)
}
}
enum CodingKeys: String, CodingKey {
case exact
}
}
}

View File

@ -11,6 +11,7 @@ public enum Product: String, Codable, Equatable {
case unitTests = "unit_tests" case unitTests = "unit_tests"
case uiTests = "ui_tests" case uiTests = "ui_tests"
case bundle case bundle
case appClip
// Not supported yet // Not supported yet
case appExtension = "app_extension" case appExtension = "app_extension"

View File

@ -148,7 +148,7 @@ public enum DefaultSettings: Codable, Equatable {
var nestedContainer = try container.nestedUnkeyedContainer(forKey: .essential) var nestedContainer = try container.nestedUnkeyedContainer(forKey: .essential)
let excludedKeys = try nestedContainer.decode(Set<String>.self) let excludedKeys = try nestedContainer.decode(Set<String>.self)
self = .essential(excluding: excludedKeys) self = .essential(excluding: excludedKeys)
case .none: case .none?:
self = .none self = .none
default: default:
throw DecodingError.dataCorrupted( throw DecodingError.dataCorrupted(

View File

@ -78,7 +78,7 @@ public extension SettingsDictionary {
merging(["VERSIONING_SYSTEM": "apple-generic"]) merging(["VERSIONING_SYSTEM": "apple-generic"])
} }
/// Sets "VERSION_INFO_PREFIX" to `version`. If `prefix` and `suffix` are not `nil`, they're used as `"VERSION_INFO_PREFIX"` and `"VERSION_INFO_SUFFIX"` respectively. /// 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.
func versionInfo(_ version: String, prefix: String? = nil, suffix: String? = nil) -> SettingsDictionary { func versionInfo(_ version: String, prefix: String? = nil, suffix: String? = nil) -> SettingsDictionary {
var versionSettings: SettingsDictionary = ["VERSION_INFO_STRING": SettingValue(version)] var versionSettings: SettingsDictionary = ["VERSION_INFO_STRING": SettingValue(version)]
versionSettings["VERSION_INFO_PREFIX"] = prefix.map { SettingValue($0) } versionSettings["VERSION_INFO_PREFIX"] = prefix.map { SettingValue($0) }

View File

@ -37,6 +37,9 @@ public struct TargetAction: Codable, Equatable {
/// List of output filelist paths /// List of output filelist paths
public let outputFileListPaths: [Path] public let outputFileListPaths: [Path]
/// Whether to skip running this script in incremental builds, if nothing has changed
public let basedOnDependencyAnalysis: Bool?
public enum CodingKeys: String, CodingKey { public enum CodingKeys: String, CodingKey {
case name case name
case tool case tool
@ -47,6 +50,7 @@ public struct TargetAction: Codable, Equatable {
case inputFileListPaths case inputFileListPaths
case outputPaths case outputPaths
case outputFileListPaths case outputFileListPaths
case basedOnDependencyAnalysis
} }
/// Initializes the target action with its attributes. /// Initializes the target action with its attributes.
@ -61,6 +65,7 @@ public struct TargetAction: Codable, Equatable {
/// - inputFileListPaths: List of input filelist paths. /// - inputFileListPaths: List of input filelist paths.
/// - outputPaths: List of output file paths. /// - outputPaths: List of output file paths.
/// - outputFileListPaths: List of output filelist paths. /// - outputFileListPaths: List of output filelist paths.
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
init(name: String, init(name: String,
tool: String?, tool: String?,
path: Path?, path: Path?,
@ -69,7 +74,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: [Path] = [], inputPaths: [Path] = [],
inputFileListPaths: [Path] = [], inputFileListPaths: [Path] = [],
outputPaths: [Path] = [], outputPaths: [Path] = [],
outputFileListPaths: [Path] = []) outputFileListPaths: [Path] = [],
basedOnDependencyAnalysis: Bool? = nil)
{ {
self.name = name self.name = name
self.path = path self.path = path
@ -80,6 +86,7 @@ public struct TargetAction: Codable, Equatable {
self.inputFileListPaths = inputFileListPaths self.inputFileListPaths = inputFileListPaths
self.outputPaths = outputPaths self.outputPaths = outputPaths
self.outputFileListPaths = outputFileListPaths self.outputFileListPaths = outputFileListPaths
self.basedOnDependencyAnalysis = basedOnDependencyAnalysis
} }
/// Returns a target action that gets executed before the sources and resources build phase. /// Returns a target action that gets executed before the sources and resources build phase.
@ -92,6 +99,7 @@ public struct TargetAction: Codable, Equatable {
/// - inputFileListPaths: List of input filelist paths. /// - inputFileListPaths: List of input filelist paths.
/// - outputPaths: List of output file paths. /// - outputPaths: List of output file paths.
/// - outputFileListPaths: List of output filelist paths. /// - outputFileListPaths: List of output filelist paths.
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
/// - Returns: Target action. /// - Returns: Target action.
public static func pre(tool: String, public static func pre(tool: String,
arguments: String..., arguments: String...,
@ -99,7 +107,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: [Path] = [], inputPaths: [Path] = [],
inputFileListPaths: [Path] = [], inputFileListPaths: [Path] = [],
outputPaths: [Path] = [], outputPaths: [Path] = [],
outputFileListPaths: [Path] = []) -> TargetAction outputFileListPaths: [Path] = [],
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
{ {
TargetAction(name: name, TargetAction(name: name,
tool: tool, tool: tool,
@ -109,7 +118,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: inputPaths, inputPaths: inputPaths,
inputFileListPaths: inputFileListPaths, inputFileListPaths: inputFileListPaths,
outputPaths: outputPaths, outputPaths: outputPaths,
outputFileListPaths: outputFileListPaths) outputFileListPaths: outputFileListPaths,
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
} }
/// Returns a target action that gets executed before the sources and resources build phase. /// Returns a target action that gets executed before the sources and resources build phase.
@ -122,6 +132,7 @@ public struct TargetAction: Codable, Equatable {
/// - inputFileListPaths: List of input filelist paths. /// - inputFileListPaths: List of input filelist paths.
/// - outputPaths: List of output file paths. /// - outputPaths: List of output file paths.
/// - outputFileListPaths: List of output filelist paths. /// - outputFileListPaths: List of output filelist paths.
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
/// - Returns: Target action. /// - Returns: Target action.
public static func pre(tool: String, public static func pre(tool: String,
arguments: [String], arguments: [String],
@ -129,7 +140,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: [Path] = [], inputPaths: [Path] = [],
inputFileListPaths: [Path] = [], inputFileListPaths: [Path] = [],
outputPaths: [Path] = [], outputPaths: [Path] = [],
outputFileListPaths: [Path] = []) -> TargetAction outputFileListPaths: [Path] = [],
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
{ {
TargetAction(name: name, TargetAction(name: name,
tool: tool, tool: tool,
@ -139,7 +151,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: inputPaths, inputPaths: inputPaths,
inputFileListPaths: inputFileListPaths, inputFileListPaths: inputFileListPaths,
outputPaths: outputPaths, outputPaths: outputPaths,
outputFileListPaths: outputFileListPaths) outputFileListPaths: outputFileListPaths,
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
} }
/// Returns a target action that gets executed before the sources and resources build phase. /// Returns a target action that gets executed before the sources and resources build phase.
@ -152,6 +165,7 @@ public struct TargetAction: Codable, Equatable {
/// - inputFileListPaths: List of input filelist paths. /// - inputFileListPaths: List of input filelist paths.
/// - outputPaths: List of output file paths. /// - outputPaths: List of output file paths.
/// - outputFileListPaths: List of output filelist paths. /// - outputFileListPaths: List of output filelist paths.
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
/// - Returns: Target action. /// - Returns: Target action.
public static func pre(path: Path, public static func pre(path: Path,
arguments: String..., arguments: String...,
@ -159,7 +173,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: [Path] = [], inputPaths: [Path] = [],
inputFileListPaths: [Path] = [], inputFileListPaths: [Path] = [],
outputPaths: [Path] = [], outputPaths: [Path] = [],
outputFileListPaths: [Path] = []) -> TargetAction outputFileListPaths: [Path] = [],
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
{ {
TargetAction(name: name, TargetAction(name: name,
tool: nil, tool: nil,
@ -169,7 +184,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: inputPaths, inputPaths: inputPaths,
inputFileListPaths: inputFileListPaths, inputFileListPaths: inputFileListPaths,
outputPaths: outputPaths, outputPaths: outputPaths,
outputFileListPaths: outputFileListPaths) outputFileListPaths: outputFileListPaths,
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
} }
/// Returns a target action that gets executed before the sources and resources build phase. /// Returns a target action that gets executed before the sources and resources build phase.
@ -182,6 +198,7 @@ public struct TargetAction: Codable, Equatable {
/// - inputFileListPaths: List of input filelist paths. /// - inputFileListPaths: List of input filelist paths.
/// - outputPaths: List of output file paths. /// - outputPaths: List of output file paths.
/// - outputFileListPaths: List of output filelist paths. /// - outputFileListPaths: List of output filelist paths.
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
/// - Returns: Target action. /// - Returns: Target action.
public static func pre(path: Path, public static func pre(path: Path,
arguments: [String], arguments: [String],
@ -189,7 +206,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: [Path] = [], inputPaths: [Path] = [],
inputFileListPaths: [Path] = [], inputFileListPaths: [Path] = [],
outputPaths: [Path] = [], outputPaths: [Path] = [],
outputFileListPaths: [Path] = []) -> TargetAction outputFileListPaths: [Path] = [],
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
{ {
TargetAction(name: name, TargetAction(name: name,
tool: nil, tool: nil,
@ -199,7 +217,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: inputPaths, inputPaths: inputPaths,
inputFileListPaths: inputFileListPaths, inputFileListPaths: inputFileListPaths,
outputPaths: outputPaths, outputPaths: outputPaths,
outputFileListPaths: outputFileListPaths) outputFileListPaths: outputFileListPaths,
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
} }
/// Returns a target action that gets executed after the sources and resources build phase. /// Returns a target action that gets executed after the sources and resources build phase.
@ -212,6 +231,7 @@ public struct TargetAction: Codable, Equatable {
/// - inputFileListPaths: List of input filelist paths. /// - inputFileListPaths: List of input filelist paths.
/// - outputPaths: List of output file paths. /// - outputPaths: List of output file paths.
/// - outputFileListPaths: List of output filelist paths. /// - outputFileListPaths: List of output filelist paths.
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
/// - Returns: Target action. /// - Returns: Target action.
public static func post(tool: String, public static func post(tool: String,
arguments: String..., arguments: String...,
@ -219,7 +239,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: [Path] = [], inputPaths: [Path] = [],
inputFileListPaths: [Path] = [], inputFileListPaths: [Path] = [],
outputPaths: [Path] = [], outputPaths: [Path] = [],
outputFileListPaths: [Path] = []) -> TargetAction outputFileListPaths: [Path] = [],
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
{ {
TargetAction(name: name, TargetAction(name: name,
tool: tool, tool: tool,
@ -229,7 +250,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: inputPaths, inputPaths: inputPaths,
inputFileListPaths: inputFileListPaths, inputFileListPaths: inputFileListPaths,
outputPaths: outputPaths, outputPaths: outputPaths,
outputFileListPaths: outputFileListPaths) outputFileListPaths: outputFileListPaths,
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
} }
/// Returns a target action that gets executed after the sources and resources build phase. /// Returns a target action that gets executed after the sources and resources build phase.
@ -242,6 +264,7 @@ public struct TargetAction: Codable, Equatable {
/// - inputFileListPaths: List of input filelist paths. /// - inputFileListPaths: List of input filelist paths.
/// - outputPaths: List of output file paths. /// - outputPaths: List of output file paths.
/// - outputFileListPaths: List of output filelist paths. /// - outputFileListPaths: List of output filelist paths.
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
/// - Returns: Target action. /// - Returns: Target action.
public static func post(tool: String, public static func post(tool: String,
arguments: [String], arguments: [String],
@ -249,7 +272,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: [Path] = [], inputPaths: [Path] = [],
inputFileListPaths: [Path] = [], inputFileListPaths: [Path] = [],
outputPaths: [Path] = [], outputPaths: [Path] = [],
outputFileListPaths: [Path] = []) -> TargetAction outputFileListPaths: [Path] = [],
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
{ {
TargetAction(name: name, TargetAction(name: name,
tool: tool, tool: tool,
@ -259,7 +283,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: inputPaths, inputPaths: inputPaths,
inputFileListPaths: inputFileListPaths, inputFileListPaths: inputFileListPaths,
outputPaths: outputPaths, outputPaths: outputPaths,
outputFileListPaths: outputFileListPaths) outputFileListPaths: outputFileListPaths,
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
} }
/// Returns a target action that gets executed after the sources and resources build phase. /// Returns a target action that gets executed after the sources and resources build phase.
@ -272,6 +297,7 @@ public struct TargetAction: Codable, Equatable {
/// - inputFileListPaths: List of input filelist paths. /// - inputFileListPaths: List of input filelist paths.
/// - outputPaths: List of output file paths. /// - outputPaths: List of output file paths.
/// - outputFileListPaths: List of output filelist paths. /// - outputFileListPaths: List of output filelist paths.
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
/// - Returns: Target action. /// - Returns: Target action.
public static func post(path: Path, public static func post(path: Path,
arguments: String..., arguments: String...,
@ -279,7 +305,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: [Path] = [], inputPaths: [Path] = [],
inputFileListPaths: [Path] = [], inputFileListPaths: [Path] = [],
outputPaths: [Path] = [], outputPaths: [Path] = [],
outputFileListPaths: [Path] = []) -> TargetAction outputFileListPaths: [Path] = [],
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
{ {
TargetAction(name: name, TargetAction(name: name,
tool: nil, tool: nil,
@ -289,7 +316,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: inputPaths, inputPaths: inputPaths,
inputFileListPaths: inputFileListPaths, inputFileListPaths: inputFileListPaths,
outputPaths: outputPaths, outputPaths: outputPaths,
outputFileListPaths: outputFileListPaths) outputFileListPaths: outputFileListPaths,
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
} }
/// Returns a target action that gets executed after the sources and resources build phase. /// Returns a target action that gets executed after the sources and resources build phase.
@ -302,6 +330,7 @@ public struct TargetAction: Codable, Equatable {
/// - inputFileListPaths: List of input filelist paths. /// - inputFileListPaths: List of input filelist paths.
/// - outputPaths: List of output file paths. /// - outputPaths: List of output file paths.
/// - outputFileListPaths: List of output filelist paths. /// - outputFileListPaths: List of output filelist paths.
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
/// - Returns: Target action. /// - Returns: Target action.
public static func post(path: Path, public static func post(path: Path,
arguments: [String], arguments: [String],
@ -309,7 +338,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: [Path] = [], inputPaths: [Path] = [],
inputFileListPaths: [Path] = [], inputFileListPaths: [Path] = [],
outputPaths: [Path] = [], outputPaths: [Path] = [],
outputFileListPaths: [Path] = []) -> TargetAction outputFileListPaths: [Path] = [],
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
{ {
TargetAction(name: name, TargetAction(name: name,
tool: nil, tool: nil,
@ -319,7 +349,8 @@ public struct TargetAction: Codable, Equatable {
inputPaths: inputPaths, inputPaths: inputPaths,
inputFileListPaths: inputFileListPaths, inputFileListPaths: inputFileListPaths,
outputPaths: outputPaths, outputPaths: outputPaths,
outputFileListPaths: outputFileListPaths) outputFileListPaths: outputFileListPaths,
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
} }
// MARK: - Codable // MARK: - Codable
@ -333,6 +364,7 @@ public struct TargetAction: Codable, Equatable {
inputFileListPaths = try container.decodeIfPresent([Path].self, forKey: .inputFileListPaths) ?? [] inputFileListPaths = try container.decodeIfPresent([Path].self, forKey: .inputFileListPaths) ?? []
outputPaths = try container.decodeIfPresent([Path].self, forKey: .outputPaths) ?? [] outputPaths = try container.decodeIfPresent([Path].self, forKey: .outputPaths) ?? []
outputFileListPaths = try container.decodeIfPresent([Path].self, forKey: .outputFileListPaths) ?? [] outputFileListPaths = try container.decodeIfPresent([Path].self, forKey: .outputFileListPaths) ?? []
basedOnDependencyAnalysis = try container.decodeIfPresent(Bool.self, forKey: .basedOnDependencyAnalysis)
if let path = try container.decodeIfPresent(Path.self, forKey: .path) { if let path = try container.decodeIfPresent(Path.self, forKey: .path) {
self.path = path self.path = path
tool = nil tool = nil
@ -352,6 +384,7 @@ public struct TargetAction: Codable, Equatable {
try container.encode(inputFileListPaths, forKey: .inputFileListPaths) try container.encode(inputFileListPaths, forKey: .inputFileListPaths)
try container.encode(outputPaths, forKey: .outputPaths) try container.encode(outputPaths, forKey: .outputPaths)
try container.encode(outputFileListPaths, forKey: .outputFileListPaths) try container.encode(outputFileListPaths, forKey: .outputFileListPaths)
try container.encode(basedOnDependencyAnalysis, forKey: .basedOnDependencyAnalysis)
if let tool = tool { if let tool = tool {
try container.encode(tool, forKey: .tool) try container.encode(tool, forKey: .tool)

View File

@ -1,9 +0,0 @@
import Foundation
struct SimulatorDeviceAndRuntime: Hashable {
/// Device
let device: SimulatorDevice
/// Device's runtime.
let runtime: SimulatorRuntime
}

View File

@ -19,6 +19,12 @@ public protocol BuildGraphInspecting {
/// - graph: Dependency graph. /// - graph: Dependency graph.
func buildableTarget(scheme: Scheme, graph: Graph) -> Target? func buildableTarget(scheme: Scheme, graph: Graph) -> Target?
/// From the list of testable targets of the given scheme, it returns the first one.
/// - Parameters:
/// - scheme: Scheme in which to look up the target.
/// - graph: Dependency graph.
func testableTarget(scheme: Scheme, graph: Graph) -> Target?
/// Given a graph, it returns a list of buildable schemes. /// Given a graph, it returns a list of buildable schemes.
/// - Parameter graph: Dependency graph. /// - Parameter graph: Dependency graph.
func buildableSchemes(graph: Graph) -> [Scheme] func buildableSchemes(graph: Graph) -> [Scheme]
@ -27,6 +33,14 @@ public protocol BuildGraphInspecting {
/// - Parameters: /// - Parameters:
/// - graph: Dependency graph /// - graph: Dependency graph
func buildableEntrySchemes(graph: Graph) -> [Scheme] func buildableEntrySchemes(graph: Graph) -> [Scheme]
/// Given a graph, it returns a list of test schemes (those that include only one test target).
/// - Parameter graph: Dependency graph.
func testSchemes(graph: Graph) -> [Scheme]
/// Given a graph, it returns a list of testable schemes.
/// - Parameter graph: Dependency graph.
func testableSchemes(graph: Graph) -> [Scheme]
} }
public class BuildGraphInspector: BuildGraphInspecting { public class BuildGraphInspector: BuildGraphInspecting {
@ -53,17 +67,26 @@ public class BuildGraphInspector: BuildGraphInspecting {
} }
public func buildableTarget(scheme: Scheme, graph: Graph) -> Target? { public func buildableTarget(scheme: Scheme, graph: Graph) -> Target? {
if scheme.buildAction?.targets.count == 0 { guard
scheme.buildAction?.targets.isEmpty == false,
let buildTarget = scheme.buildAction?.targets.first
else {
return nil return nil
} }
let buildTarget = scheme.buildAction!.targets.first!
return graph.target(path: buildTarget.projectPath, name: buildTarget.name)!.target return graph.target(path: buildTarget.projectPath, name: buildTarget.name)?.target
}
public func testableTarget(scheme: Scheme, graph: Graph) -> Target? {
if scheme.testAction?.targets.count == 0 {
return nil
}
let testTarget = scheme.testAction!.targets.first!
return graph.target(path: testTarget.target.projectPath, name: testTarget.target.name)!.target
} }
public func buildableSchemes(graph: Graph) -> [Scheme] { public func buildableSchemes(graph: Graph) -> [Scheme] {
graph.targets.values.flatMap { graph.schemes
$0.flatMap { $0.project.schemes }
}
.filter { $0.buildAction?.targets.isEmpty == false } .filter { $0.buildAction?.targets.isEmpty == false }
.sorted(by: { $0.name < $1.name }) .sorted(by: { $0.name < $1.name })
} }
@ -76,6 +99,25 @@ public class BuildGraphInspector: BuildGraphInspecting {
.sorted(by: { $0.name < $1.name }) .sorted(by: { $0.name < $1.name })
} }
public func testableSchemes(graph: Graph) -> [Scheme] {
graph.schemes
.filter { $0.testAction?.targets.isEmpty == false }
.sorted(by: { $0.name < $1.name })
}
public func testSchemes(graph: Graph) -> [Scheme] {
graph.targets.values.flatMap { target -> [Scheme] in
target
.filter { $0.target.product == .unitTests || $0.target.product == .uiTests }
.flatMap { target -> [Scheme] in
target.project.schemes
.filter { $0.targetDependencies().map(\.name) == [target.name] }
}
}
.filter { $0.testAction?.targets.isEmpty == false }
.sorted(by: { $0.name < $1.name })
}
public func workspacePath(directory: AbsolutePath) throws -> AbsolutePath? { public func workspacePath(directory: AbsolutePath) throws -> AbsolutePath? {
try directory.glob("**/*.xcworkspace") try directory.glob("**/*.xcworkspace")
.filter { .filter {

View File

@ -53,6 +53,40 @@ public final class XcodeBuildController: XcodeBuildControlling {
return run(command: command) return run(command: command)
} }
public func test(
_ target: XcodeBuildTarget,
scheme: String,
clean: Bool = false,
destination: XcodeBuildDestination,
arguments: [XcodeBuildArgument]
) -> Observable<SystemEvent<XcodeBuildOutput>> {
var command = ["/usr/bin/xcrun", "xcodebuild"]
// Action
if clean {
command.append("clean")
}
command.append("test")
// Scheme
command.append(contentsOf: ["-scheme", scheme])
// Target
command.append(contentsOf: target.xcodebuildArguments)
// Arguments
command.append(contentsOf: arguments.flatMap { $0.arguments })
switch destination {
case let .device(udid):
command.append(contentsOf: ["-destination", "id=\(udid)"])
case .mac:
break
}
return run(command: command)
}
public func archive(_ target: XcodeBuildTarget, public func archive(_ target: XcodeBuildTarget,
scheme: String, scheme: String,
clean: Bool, clean: Bool,
@ -168,29 +202,18 @@ public final class XcodeBuildController: XcodeBuildControlling {
switch event { switch event {
case let .standardError(errorData): case let .standardError(errorData):
guard let line = String(data: errorData, encoding: .utf8) else { return Observable.empty() } guard let line = String(data: errorData, encoding: .utf8) else { return Observable.empty() }
return Observable.create { observer in let output = line.split(separator: "\n").map { line -> SystemEvent<XcodeBuildOutput> in
let lines = line.split(separator: "\n")
lines.map { line in
let formatedOutput = self.parser.parse(line: String(line), colored: colored) let formatedOutput = self.parser.parse(line: String(line), colored: colored)
return SystemEvent.standardError(XcodeBuildOutput(raw: "\(String(line))\n", formatted: formatedOutput.map { "\($0)\n" })) return SystemEvent.standardError(XcodeBuildOutput(raw: "\(String(line))\n", formatted: formatedOutput.map { "\($0)\n" }))
} }
.forEach(observer.onNext) return Observable.from(output)
observer.onCompleted()
return Disposables.create()
}
case let .standardOutput(outputData): case let .standardOutput(outputData):
guard let line = String(data: outputData, encoding: .utf8) else { return Observable.empty() } guard let line = String(data: outputData, encoding: .utf8) else { return Observable.empty() }
let output = line.split(separator: "\n").map { line -> SystemEvent<XcodeBuildOutput> in
return Observable.create { observer in
let lines = line.split(separator: "\n")
lines.map { line in
let formatedOutput = self.parser.parse(line: String(line), colored: colored) let formatedOutput = self.parser.parse(line: String(line), colored: colored)
return SystemEvent.standardOutput(XcodeBuildOutput(raw: "\(String(line))\n", formatted: formatedOutput.map { "\($0)\n" })) return SystemEvent.standardOutput(XcodeBuildOutput(raw: "\(String(line))\n", formatted: formatedOutput.map { "\($0)\n" }))
} }
.forEach(observer.onNext) return Observable.from(output)
observer.onCompleted()
return Disposables.create()
}
} }
} }
} }

View File

@ -43,4 +43,27 @@ public final class MockBuildGraphInspector: BuildGraphInspecting {
return [] return []
} }
} }
public var testableTargetStub: ((Scheme, Graph) -> Target?)?
public func testableTarget(scheme: Scheme, graph: Graph) -> Target? {
if let testableTargetStub = testableTargetStub {
return testableTargetStub(scheme, graph)
} else {
return Target.test()
}
}
public var testableSchemesStub: ((Graph) -> [Scheme])?
public func testableSchemes(graph: Graph) -> [Scheme] {
if let testableSchemesStub = testableSchemesStub {
return testableSchemesStub(graph)
} else {
return []
}
}
public var testSchemesStub: ((Graph) -> [Scheme])?
public func testSchemes(graph: Graph) -> [Scheme] {
testSchemesStub?(graph) ?? []
}
} }

View File

@ -48,8 +48,8 @@ public final class Cache: CacheStoring {
}! }!
} }
public func store(hash: String, xcframeworkPath: AbsolutePath) -> Completable { public func store(hash: String, paths: [AbsolutePath]) -> Completable {
let storages = storageProvider.storages() let storages = storageProvider.storages()
return Completable.zip(storages.map { $0.store(hash: hash, xcframeworkPath: xcframeworkPath) }) return Completable.zip(storages.map { $0.store(hash: hash, paths: paths) })
} }
} }

View File

@ -29,7 +29,7 @@ public final class CacheLocalStorage: CacheStoring {
// MARK: - Init // MARK: - Init
public convenience init() { public convenience init() {
self.init(cacheDirectory: Environment.shared.xcframeworksCacheDirectory) self.init(cacheDirectory: Environment.shared.buildCacheDirectory)
} }
init(cacheDirectory: AbsolutePath) { init(cacheDirectory: AbsolutePath) {
@ -40,14 +40,14 @@ public final class CacheLocalStorage: CacheStoring {
public func exists(hash: String) -> Single<Bool> { public func exists(hash: String) -> Single<Bool> {
Single.create { (completed) -> Disposable in Single.create { (completed) -> Disposable in
completed(.success(FileHandler.shared.glob(self.cacheDirectory, glob: "\(hash)/*").count != 0)) completed(.success(self.lookupFramework(directory: self.cacheDirectory.appending(component: hash)) != nil))
return Disposables.create() return Disposables.create()
} }
} }
public func fetch(hash: String) -> Single<AbsolutePath> { public func fetch(hash: String) -> Single<AbsolutePath> {
Single.create { (completed) -> Disposable in Single.create { (completed) -> Disposable in
if let path = FileHandler.shared.glob(self.cacheDirectory, glob: "\(hash)/*").first { if let path = self.lookupFramework(directory: self.cacheDirectory.appending(component: hash)) {
completed(.success(path)) completed(.success(path))
} else { } else {
completed(.error(CacheLocalStorageError.xcframeworkNotFound(hash: hash))) completed(.error(CacheLocalStorageError.xcframeworkNotFound(hash: hash)))
@ -56,21 +56,22 @@ public final class CacheLocalStorage: CacheStoring {
} }
} }
public func store(hash: String, xcframeworkPath: AbsolutePath) -> Completable { public func store(hash: String, paths: [AbsolutePath]) -> Completable {
let copy = Completable.create { (completed) -> Disposable in let copy = Completable.create { (completed) -> Disposable in
let hashFolder = self.cacheDirectory.appending(component: hash) let hashFolder = self.cacheDirectory.appending(component: hash)
let destinationPath = hashFolder.appending(component: xcframeworkPath.basename)
do { do {
if !FileHandler.shared.exists(hashFolder) { if !FileHandler.shared.exists(hashFolder) {
try FileHandler.shared.createFolder(hashFolder) try FileHandler.shared.createFolder(hashFolder)
} }
try paths.forEach { sourcePath in
let destinationPath = hashFolder.appending(component: sourcePath.basename)
if FileHandler.shared.exists(destinationPath) { if FileHandler.shared.exists(destinationPath) {
try FileHandler.shared.delete(destinationPath) try FileHandler.shared.delete(destinationPath)
} }
try FileHandler.shared.copy(from: xcframeworkPath, to: destinationPath) try FileHandler.shared.copy(from: sourcePath, to: destinationPath)
}
} catch { } catch {
completed(.error(error)) completed(.error(error))
return Disposables.create() return Disposables.create()
@ -84,6 +85,14 @@ public final class CacheLocalStorage: CacheStoring {
// MARK: - Fileprivate // MARK: - Fileprivate
fileprivate func lookupFramework(directory: AbsolutePath) -> AbsolutePath? {
let extensions = ["framework", "xcframework"]
for ext in extensions {
if let filePath = FileHandler.shared.glob(directory, glob: "*.\(ext)").first { return filePath }
}
return nil
}
fileprivate func createCacheDirectory() -> Completable { fileprivate func createCacheDirectory() -> Completable {
Completable.create { (completed) -> Disposable in Completable.create { (completed) -> Disposable in
do { do {

View File

@ -5,18 +5,18 @@ import TuistCore
import TuistSupport import TuistSupport
enum CacheRemoteStorageError: FatalError, Equatable { enum CacheRemoteStorageError: FatalError, Equatable {
case archiveDoesNotContainXCFramework(AbsolutePath) case frameworkNotFound(hash: String)
var type: ErrorType { var type: ErrorType {
switch self { switch self {
case .archiveDoesNotContainXCFramework: return .abort case .frameworkNotFound: return .abort
} }
} }
var description: String { var description: String {
switch self { switch self {
case let .archiveDoesNotContainXCFramework(path): case let .frameworkNotFound(hash):
return "Unzipped archive at path \(path.pathString) does not contain any xcframework." return "The downloaded artifact with hash '\(hash)' has an incorrect format and doesn't contain a xcframework nor a framework."
} }
} }
} }
@ -28,21 +28,20 @@ public final class CacheRemoteStorage: CacheStoring {
private let cloudConfig: Cloud private let cloudConfig: Cloud
private let cloudClient: CloudClienting private let cloudClient: CloudClienting
private let fileClient: FileClienting private let fileClient: FileClienting
private let fileArchiverFactory: FileArchiverManufacturing private let fileArchiverFactory: FileArchivingFactorying
private var fileArchiverMap: [AbsolutePath: FileArchiving] = [:]
// MARK: - Init // MARK: - Init
public convenience init(cloudConfig: Cloud, cloudClient: CloudClienting) { public convenience init(cloudConfig: Cloud, cloudClient: CloudClienting) {
self.init(cloudConfig: cloudConfig, self.init(cloudConfig: cloudConfig,
cloudClient: cloudClient, cloudClient: cloudClient,
fileArchiverFactory: FileArchiverFactory(), fileArchiverFactory: FileArchivingFactory(),
fileClient: FileClient()) fileClient: FileClient())
} }
init(cloudConfig: Cloud, init(cloudConfig: Cloud,
cloudClient: CloudClienting, cloudClient: CloudClienting,
fileArchiverFactory: FileArchiverManufacturing, fileArchiverFactory: FileArchivingFactorying,
fileClient: FileClienting) fileClient: FileClienting)
{ {
self.cloudConfig = cloudConfig self.cloudConfig = cloudConfig
@ -79,7 +78,10 @@ public final class CacheRemoteStorage: CacheStoring {
return cloudClient return cloudClient
.request(resource) .request(resource)
.map { $0.object.data.url } .map { $0.object.data.url }
.flatMap { (url: URL) in self.fileClient.download(url: url) } .flatMap { (url: URL) in
self.fileClient.download(url: url)
.do(onSubscribed: { logger.info("Downloading cache artifact with hash \(hash).") })
}
.flatMap { (filePath: AbsolutePath) in .flatMap { (filePath: AbsolutePath) in
do { do {
let archiveContentPath = try self.unzip(downloadedArchive: filePath, hash: hash) let archiveContentPath = try self.unzip(downloadedArchive: filePath, hash: hash)
@ -93,10 +95,10 @@ public final class CacheRemoteStorage: CacheStoring {
} }
} }
public func store(hash: String, xcframeworkPath: AbsolutePath) -> Completable { public func store(hash: String, paths: [AbsolutePath]) -> Completable {
do { do {
let archiver = fileArchiver(for: xcframeworkPath) let archiver = try fileArchiverFactory.makeFileArchiver(for: paths)
let destinationZipPath = try archiver.zip() let destinationZipPath = try archiver.zip(name: hash)
let resource = try CloudCacheResponse.storeResource( let resource = try CloudCacheResponse.storeResource(
hash: hash, hash: hash,
cloud: cloudConfig, cloud: cloudConfig,
@ -119,26 +121,31 @@ public final class CacheRemoteStorage: CacheStoring {
// MARK: - Private // MARK: - Private
private func xcframeworkPath(in archive: AbsolutePath) throws -> AbsolutePath? { private func frameworkPath(in archive: AbsolutePath) -> AbsolutePath? {
let folderContent = try FileHandler.shared.contentsOfDirectory(archive) if let xcframeworkPath = FileHandler.shared.glob(archive, glob: "*.xcframework").first {
return folderContent.filter { FileHandler.shared.isFolder($0) && $0.extension == "xcframework" }.first return xcframeworkPath
} else if let frameworkPath = FileHandler.shared.glob(archive, glob: "*.framework").first {
return frameworkPath
}
return nil
} }
private func unzip(downloadedArchive: AbsolutePath, hash: String) throws -> AbsolutePath { private func unzip(downloadedArchive: AbsolutePath, hash: String) throws -> AbsolutePath {
let zipPath = try FileHandler.shared.changeExtension(path: downloadedArchive, to: "zip") let zipPath = try FileHandler.shared.changeExtension(path: downloadedArchive, to: "zip")
let archiveDestination = Environment.shared.xcframeworksCacheDirectory.appending(component: hash) let archiveDestination = Environment.shared.buildCacheDirectory.appending(component: hash)
try fileArchiver(for: zipPath).unzip(to: archiveDestination) let fileUnarchiver = try fileArchiverFactory.makeFileUnarchiver(for: zipPath)
guard let xcframework = try xcframeworkPath(in: archiveDestination) else { let unarchivedDirectory = try fileUnarchiver.unzip()
try FileHandler.shared.delete(archiveDestination) defer {
throw CacheRemoteStorageError.archiveDoesNotContainXCFramework(archiveDestination) try? fileUnarchiver.delete()
} }
return xcframework if frameworkPath(in: unarchivedDirectory) == nil {
throw CacheRemoteStorageError.frameworkNotFound(hash: hash)
} }
if !FileHandler.shared.exists(archiveDestination.parentDirectory) {
private func fileArchiver(for path: AbsolutePath) -> FileArchiving { try FileHandler.shared.createFolder(archiveDestination.parentDirectory)
let fileArchiver = fileArchiverMap[path] ?? fileArchiverFactory.makeFileArchiver(for: path) }
fileArchiverMap[path] = fileArchiver try FileHandler.shared.move(from: unarchivedDirectory, to: archiveDestination)
return fileArchiver return frameworkPath(in: archiveDestination)!
} }
private func deleteZipArchiveCompletable(archiver: FileArchiving) -> Completable { private func deleteZipArchiveCompletable(archiver: FileArchiving) -> Completable {
@ -152,12 +159,4 @@ public final class CacheRemoteStorage: CacheStoring {
return Disposables.create {} return Disposables.create {}
}) })
} }
// MARK: - Deinit
deinit {
do {
try fileArchiverMap.values.forEach { fileArchiver in try fileArchiver.delete() }
} catch {}
}
} }

View File

@ -21,6 +21,6 @@ public protocol CacheStoring {
/// It stores the xcframework at the given path in the cache. /// It stores the xcframework at the given path in the cache.
/// - Parameters: /// - Parameters:
/// - hash: Hash of the target the xcframework belongs to. /// - hash: Hash of the target the xcframework belongs to.
/// - xcframeworkPath: Path to the .xcframework. /// - paths: Path to the files that will be stored.
func store(hash: String, xcframeworkPath: AbsolutePath) -> Completable func store(hash: String, paths: [AbsolutePath]) -> Completable
} }

View File

@ -16,7 +16,7 @@ public final class DependenciesContentHasher: DependenciesContentHashing {
self.contentHasher = contentHasher self.contentHasher = contentHasher
} }
// MARK: - HeadersContentHashing // MARK: - DependenciesContentHashing
public func hash(dependencies: [Dependency]) throws -> String { public func hash(dependencies: [Dependency]) throws -> String {
let hashes = dependencies.map { try? hash(dependency: $0) } let hashes = dependencies.map { try? hash(dependency: $0) }

View File

@ -5,7 +5,7 @@ import TuistCore
import TuistSupport import TuistSupport
public protocol GraphContentHashing { public protocol GraphContentHashing {
func contentHashes(for graph: TuistCore.Graph) throws -> [TargetNode: String] func contentHashes(for graph: TuistCore.Graph, cacheOutputType: CacheOutputType) throws -> [TargetNode: String]
} }
/// `GraphContentHasher` /// `GraphContentHasher`
@ -24,7 +24,7 @@ public final class GraphContentHasher: GraphContentHashing {
// MARK: - GraphContentHashing // MARK: - GraphContentHashing
public func contentHashes(for graph: TuistCore.Graph) throws -> [TargetNode: String] { public func contentHashes(for graph: TuistCore.Graph, cacheOutputType: CacheOutputType) throws -> [TargetNode: String] {
var visitedNodes: [TargetNode: Bool] = [:] var visitedNodes: [TargetNode: Bool] = [:]
let hashableTargets = graph.targets.values.flatMap { (targets: [TargetNode]) -> [TargetNode] in let hashableTargets = graph.targets.values.flatMap { (targets: [TargetNode]) -> [TargetNode] in
targets.compactMap { target in targets.compactMap { target in
@ -35,7 +35,8 @@ public final class GraphContentHasher: GraphContentHashing {
} }
} }
let hashes = try hashableTargets.map { let hashes = try hashableTargets.map {
try targetContentHasher.contentHash(for: $0) try targetContentHasher.contentHash(for: $0,
cacheOutputType: cacheOutputType)
} }
return Dictionary(uniqueKeysWithValues: zip(hashableTargets, hashes)) return Dictionary(uniqueKeysWithValues: zip(hashableTargets, hashes))
} }

View File

@ -16,6 +16,8 @@ public final class ResourcesContentHasher: ResourcesContentHashing {
self.contentHasher = contentHasher self.contentHasher = contentHasher
} }
// MARK: - ResourcesContentHashing
public func hash(resources: [FileElement]) throws -> String { public func hash(resources: [FileElement]) throws -> String {
let hashes = try resources.map { try contentHasher.hash(path: $0.path) } let hashes = try resources.map { try contentHasher.hash(path: $0.path) }
return try contentHasher.hash(hashes) return try contentHasher.hash(hashes)

View File

@ -16,10 +16,10 @@ public final class SettingsContentHasher: SettingsContentHashing {
self.contentHasher = contentHasher self.contentHasher = contentHasher
} }
// MARK: - InfoPlistContentHashing // MARK: - SettingsContentHashing
public func hash(settings: Settings) throws -> String { public func hash(settings: Settings) throws -> String {
let baseSettingsHash = hash(settings.base) let baseSettingsHash = try hash(settings.base)
let configurationHash = try hash(settings.configurations) let configurationHash = try hash(settings.configurations)
let defaultSettingsHash = try hash(settings.defaultSettings) let defaultSettingsHash = try hash(settings.defaultSettings)
return try contentHasher.hash([baseSettingsHash, configurationHash, defaultSettingsHash]) return try contentHasher.hash([baseSettingsHash, configurationHash, defaultSettingsHash])
@ -39,12 +39,15 @@ public final class SettingsContentHasher: SettingsContentHashing {
return try contentHasher.hash(configurationHashes) return try contentHasher.hash(configurationHashes)
} }
private func hash(_ settingsDictionary: SettingsDictionary) -> String { private func hash(_ settingsDictionary: SettingsDictionary) throws -> String {
settingsDictionary.map { "\($0):\($1.normalize())" }.joined(separator: "-") let sortedAndNormalizedSettings = settingsDictionary
.sorted(by: { $0.0 < $1.0 })
.map { "\($0):\($1.normalize())" }.joined(separator: "-")
return try contentHasher.hash(sortedAndNormalizedSettings)
} }
private func hash(_ configuration: Configuration) throws -> String { private func hash(_ configuration: Configuration) throws -> String {
var configurationHash = hash(configuration.settings) var configurationHash = try hash(configuration.settings)
if let xcconfigPath = configuration.xcconfig { if let xcconfigPath = configuration.xcconfig {
let xcconfigHash = try contentHasher.hash(path: xcconfigPath) let xcconfigHash = try contentHasher.hash(path: xcconfigPath)
configurationHash += xcconfigHash configurationHash += xcconfigHash

View File

@ -4,7 +4,7 @@ import TuistCore
import TuistSupport import TuistSupport
public protocol TargetContentHashing { public protocol TargetContentHashing {
func contentHash(for target: TargetNode) throws -> String func contentHash(for target: TargetNode, cacheOutputType: CacheOutputType) throws -> String
} }
/// `TargetContentHasher` /// `TargetContentHasher`
@ -14,6 +14,7 @@ public final class TargetContentHasher: TargetContentHashing {
private let coreDataModelsContentHasher: CoreDataModelsContentHashing private let coreDataModelsContentHasher: CoreDataModelsContentHashing
private let sourceFilesContentHasher: SourceFilesContentHashing private let sourceFilesContentHasher: SourceFilesContentHashing
private let targetActionsContentHasher: TargetActionsContentHashing private let targetActionsContentHasher: TargetActionsContentHashing
private let targetScriptsContentHasher: TargetScriptsContentHashing
private let resourcesContentHasher: ResourcesContentHashing private let resourcesContentHasher: ResourcesContentHashing
private let headersContentHasher: HeadersContentHashing private let headersContentHasher: HeadersContentHashing
private let deploymentTargetContentHasher: DeploymentTargetContentHashing private let deploymentTargetContentHasher: DeploymentTargetContentHashing
@ -28,6 +29,7 @@ public final class TargetContentHasher: TargetContentHashing {
contentHasher: contentHasher, contentHasher: contentHasher,
sourceFilesContentHasher: SourceFilesContentHasher(contentHasher: contentHasher), sourceFilesContentHasher: SourceFilesContentHasher(contentHasher: contentHasher),
targetActionsContentHasher: TargetActionsContentHasher(contentHasher: contentHasher), targetActionsContentHasher: TargetActionsContentHasher(contentHasher: contentHasher),
targetScriptsContentHasher: TargetScriptsContentHasher(contentHasher: contentHasher),
coreDataModelsContentHasher: CoreDataModelsContentHasher(contentHasher: contentHasher), coreDataModelsContentHasher: CoreDataModelsContentHasher(contentHasher: contentHasher),
resourcesContentHasher: ResourcesContentHasher(contentHasher: contentHasher), resourcesContentHasher: ResourcesContentHasher(contentHasher: contentHasher),
headersContentHasher: HeadersContentHasher(contentHasher: contentHasher), headersContentHasher: HeadersContentHasher(contentHasher: contentHasher),
@ -42,6 +44,7 @@ public final class TargetContentHasher: TargetContentHashing {
contentHasher: ContentHashing, contentHasher: ContentHashing,
sourceFilesContentHasher: SourceFilesContentHashing, sourceFilesContentHasher: SourceFilesContentHashing,
targetActionsContentHasher: TargetActionsContentHashing, targetActionsContentHasher: TargetActionsContentHashing,
targetScriptsContentHasher: TargetScriptsContentHashing,
coreDataModelsContentHasher: CoreDataModelsContentHashing, coreDataModelsContentHasher: CoreDataModelsContentHashing,
resourcesContentHasher: ResourcesContentHashing, resourcesContentHasher: ResourcesContentHashing,
headersContentHasher: HeadersContentHashing, headersContentHasher: HeadersContentHashing,
@ -54,6 +57,7 @@ public final class TargetContentHasher: TargetContentHashing {
self.sourceFilesContentHasher = sourceFilesContentHasher self.sourceFilesContentHasher = sourceFilesContentHasher
self.coreDataModelsContentHasher = coreDataModelsContentHasher self.coreDataModelsContentHasher = coreDataModelsContentHasher
self.targetActionsContentHasher = targetActionsContentHasher self.targetActionsContentHasher = targetActionsContentHasher
self.targetScriptsContentHasher = targetScriptsContentHasher
self.resourcesContentHasher = resourcesContentHasher self.resourcesContentHasher = resourcesContentHasher
self.headersContentHasher = headersContentHasher self.headersContentHasher = headersContentHasher
self.deploymentTargetContentHasher = deploymentTargetContentHasher self.deploymentTargetContentHasher = deploymentTargetContentHasher
@ -64,12 +68,13 @@ public final class TargetContentHasher: TargetContentHashing {
// MARK: - TargetContentHashing // MARK: - TargetContentHashing
public func contentHash(for targetNode: TargetNode) throws -> String { public func contentHash(for targetNode: TargetNode, cacheOutputType: CacheOutputType) throws -> String {
let target = targetNode.target let target = targetNode.target
let sourcesHash = try sourceFilesContentHasher.hash(sources: target.sources) let sourcesHash = try sourceFilesContentHasher.hash(sources: target.sources)
let resourcesHash = try resourcesContentHasher.hash(resources: target.resources) let resourcesHash = try resourcesContentHasher.hash(resources: target.resources)
let coreDataModelHash = try coreDataModelsContentHasher.hash(coreDataModels: target.coreDataModels) let coreDataModelHash = try coreDataModelsContentHasher.hash(coreDataModels: target.coreDataModels)
let targetActionsHash = try targetActionsContentHasher.hash(targetActions: target.actions) let targetActionsHash = try targetActionsContentHasher.hash(targetActions: target.actions)
let targetScriptsHash = try targetScriptsContentHasher.hash(targetScripts: target.scripts)
let dependenciesHash = try dependenciesContentHasher.hash(dependencies: target.dependencies) let dependenciesHash = try dependenciesContentHasher.hash(dependencies: target.dependencies)
let environmentHash = try contentHasher.hash(target.environment) let environmentHash = try contentHasher.hash(target.environment)
var stringsToHash = [target.name, var stringsToHash = [target.name,
@ -82,6 +87,7 @@ public final class TargetContentHasher: TargetContentHashing {
resourcesHash, resourcesHash,
coreDataModelHash, coreDataModelHash,
targetActionsHash, targetActionsHash,
targetScriptsHash,
environmentHash] environmentHash]
if let headers = target.headers { if let headers = target.headers {
let headersHash = try headersContentHasher.hash(headers: headers) let headersHash = try headersContentHasher.hash(headers: headers)
@ -104,6 +110,7 @@ public final class TargetContentHasher: TargetContentHashing {
stringsToHash.append(settingsHash) stringsToHash.append(settingsHash)
} }
stringsToHash.append(cacheOutputType.description)
return try contentHasher.hash(stringsToHash) return try contentHasher.hash(stringsToHash)
} }
} }

View File

@ -0,0 +1,35 @@
import Foundation
import TSCBasic
import TuistCore
public protocol TargetScriptsContentHashing {
func hash(targetScripts: [TargetScript]) throws -> String
}
/// `TargetScriptsContentHasher`
/// is responsible for computing a unique hash that identifies a list of target scripts
public final class TargetScriptsContentHasher: TargetScriptsContentHashing {
private let contentHasher: ContentHashing
// MARK: - Init
public init(contentHasher: ContentHashing) {
self.contentHasher = contentHasher
}
// MARK: - TargetScriptsContentHashing
/// Returns the hash that uniquely identifies an array of target actions
/// The hash takes into consideration the content of the script to execute, the content of input/output files, the name of the tool to execute, the order, the arguments and its name
public func hash(targetScripts: [TargetScript]) throws -> String {
var stringsToHash: [String] = []
for targetScript in targetScripts {
if targetScript.hashable {
stringsToHash.append(targetScript.name)
stringsToHash.append(targetScript.script)
stringsToHash.append("\(targetScript.showEnvVarsInLog)")
}
}
return try contentHasher.hash(stringsToHash)
}
}

View File

@ -0,0 +1,32 @@
import Foundation
import RxBlocking
import RxSwift
import TSCBasic
import TuistCore
import TuistSupport
public class CacheBuildPhaseProjectMapper: ProjectMapping {
public init() {}
public func map(project: Project) throws -> (Project, [SideEffectDescriptor]) {
let project = project.with(targets: project.targets.map { target in
var target = target
if target.product.isFramework {
target.scripts.append(.init(name: "[Tuist] Create file to locate the built products directory",
script: script(target: target),
showEnvVarsInLog: true,
hashable: false))
}
return target
})
return (project, [])
}
fileprivate func script(target: Target) -> String {
"""
if [ -n "$\(target.targetLocatorBuildPhaseVariable)" ]; then
touch $BUILT_PRODUCTS_DIR/.$\(target.targetLocatorBuildPhaseVariable).tuist
fi
"""
}
}

View File

@ -10,13 +10,13 @@ protocol CacheGraphMutating {
/// to the .xcframeworks in the cache, it mutates the graph to link the enry nodes against the .xcframeworks instead. /// to the .xcframeworks in the cache, it mutates the graph to link the enry nodes against the .xcframeworks instead.
/// - Parameters: /// - Parameters:
/// - graph: Dependency graph. /// - graph: Dependency graph.
/// - xcframeworks: Dictionary that maps targets with the paths to their cached .xcframeworks. /// - precompiledFrameworks: Dictionary that maps targets with the paths to their cached `.framework`s or `.xcframework`s.
/// - source: Contains a list of targets that won't be replaced with their pre-compiled version from the cache. /// - source: Contains a list of targets that won't be replaced with their pre-compiled version from the cache.
func map(graph: Graph, xcframeworks: [TargetNode: AbsolutePath], sources: Set<String>) throws -> Graph func map(graph: Graph, precompiledFrameworks: [TargetNode: AbsolutePath], sources: Set<String>) throws -> Graph
} }
class CacheGraphMutator: CacheGraphMutating { class CacheGraphMutator: CacheGraphMutating {
struct VisitedXCFramework { struct VisitedPrecompiledFramework {
let path: AbsolutePath? let path: AbsolutePath?
} }
@ -25,54 +25,66 @@ class CacheGraphMutator: CacheGraphMutating {
/// Utility to parse an .xcframework from the filesystem and load it into memory. /// Utility to parse an .xcframework from the filesystem and load it into memory.
private let xcframeworkLoader: XCFrameworkNodeLoading private let xcframeworkLoader: XCFrameworkNodeLoading
/// Utility to parse a .framework from the filesystem and load it into memory.
private let frameworkLoader: FrameworkNodeLoading
/// Initializes the graph mapper with its attributes. /// Initializes the graph mapper with its attributes.
/// - Parameter xcframeworkLoader: Utility to parse an .xcframework from the filesystem and load it into memory. /// - Parameter xcframeworkLoader: Utility to parse an .xcframework from the filesystem and load it into memory.
init(xcframeworkLoader: XCFrameworkNodeLoading = XCFrameworkNodeLoader()) { init(frameworkLoader: FrameworkNodeLoading = FrameworkNodeLoader(),
xcframeworkLoader: XCFrameworkNodeLoading = XCFrameworkNodeLoader())
{
self.frameworkLoader = frameworkLoader
self.xcframeworkLoader = xcframeworkLoader self.xcframeworkLoader = xcframeworkLoader
} }
// MARK: - CacheGraphMapping // MARK: - CacheGraphMapping
public func map(graph: Graph, xcframeworks: [TargetNode: AbsolutePath], sources: Set<String>) throws -> Graph { public func map(graph: Graph, precompiledFrameworks: [TargetNode: AbsolutePath], sources: Set<String>) throws -> Graph {
var visitedXCFrameworkPaths: [TargetNode: VisitedXCFramework?] = [:] var visitedPrecompiledFrameworkPaths: [TargetNode: VisitedPrecompiledFramework?] = [:]
var loadedXCFrameworks: [AbsolutePath: XCFrameworkNode] = [:] var loadedPrecompiledNodes: [AbsolutePath: PrecompiledNode] = [:]
let userSpecifiedSourceTargets = graph.targets.flatMap { $0.value }.filter { sources.contains($0.target.name) } let userSpecifiedSourceTargets = graph.targets.flatMap { $0.value }.filter { sources.contains($0.target.name) }
let userSpecifiedSourceTestTargets = userSpecifiedSourceTargets.flatMap { graph.testTargetsDependingOn(path: $0.path, name: $0.name) } let userSpecifiedSourceTestTargets = userSpecifiedSourceTargets.flatMap { graph.testTargetsDependingOn(path: $0.path, name: $0.name) }
var sourceTargets: Set<TargetNode> = Set(userSpecifiedSourceTargets) var sourceTargets: Set<TargetNode> = Set(userSpecifiedSourceTargets)
try (userSpecifiedSourceTargets + userSpecifiedSourceTestTargets) try (userSpecifiedSourceTargets + userSpecifiedSourceTestTargets)
.forEach { try visit(targetNode: $0, .forEach { try visit(targetNode: $0,
xcframeworks: xcframeworks, precompiledFrameworks: precompiledFrameworks,
sources: sources, sources: sources,
sourceTargets: &sourceTargets, sourceTargets: &sourceTargets,
visitedXCFrameworkPaths: &visitedXCFrameworkPaths, visitedPrecompiledFrameworkPaths: &visitedPrecompiledFrameworkPaths,
loadedXCFrameworks: &loadedXCFrameworks) } loadedPrecompiledNodes: &loadedPrecompiledNodes) }
return treeShake(graph: graph, sourceTargets: sourceTargets) // We mark them to be pruned during the tree-shaking
graph.targets.flatMap(\.value).forEach {
if !sourceTargets.contains($0) { $0.prune = true }
}
return graph
} }
fileprivate func visit(targetNode: TargetNode, fileprivate func visit(targetNode: TargetNode,
xcframeworks: [TargetNode: AbsolutePath], precompiledFrameworks: [TargetNode: AbsolutePath],
sources: Set<String>, sources: Set<String>,
sourceTargets: inout Set<TargetNode>, sourceTargets: inout Set<TargetNode>,
visitedXCFrameworkPaths: inout [TargetNode: VisitedXCFramework?], visitedPrecompiledFrameworkPaths: inout [TargetNode: VisitedPrecompiledFramework?],
loadedXCFrameworks: inout [AbsolutePath: XCFrameworkNode]) throws loadedPrecompiledNodes: inout [AbsolutePath: PrecompiledNode]) throws
{ {
sourceTargets.formUnion([targetNode]) sourceTargets.formUnion([targetNode])
targetNode.dependencies = try mapDependencies(targetNode.dependencies, targetNode.dependencies = try mapDependencies(targetNode.dependencies,
xcframeworks: xcframeworks, precompiledFrameworks: precompiledFrameworks,
sources: sources, sources: sources,
sourceTargets: &sourceTargets, sourceTargets: &sourceTargets,
visitedXCFrameworkPaths: &visitedXCFrameworkPaths, visitedPrecompiledFrameworkPaths: &visitedPrecompiledFrameworkPaths,
loadedXCFrameworks: &loadedXCFrameworks) loadedPrecompiledFrameworks: &loadedPrecompiledNodes)
} }
// swiftlint:disable line_length
fileprivate func mapDependencies(_ dependencies: [GraphNode], fileprivate func mapDependencies(_ dependencies: [GraphNode],
xcframeworks: [TargetNode: AbsolutePath], precompiledFrameworks: [TargetNode: AbsolutePath],
sources: Set<String>, sources: Set<String>,
sourceTargets: inout Set<TargetNode>, sourceTargets: inout Set<TargetNode>,
visitedXCFrameworkPaths: inout [TargetNode: VisitedXCFramework?], visitedPrecompiledFrameworkPaths: inout [TargetNode: VisitedPrecompiledFramework?],
loadedXCFrameworks: inout [AbsolutePath: XCFrameworkNode]) throws -> [GraphNode] loadedPrecompiledFrameworks: inout [AbsolutePath: PrecompiledNode]) throws -> [GraphNode]
{ {
var newDependencies: [GraphNode] = [] var newDependencies: [GraphNode] = []
try dependencies.forEach { dependency in try dependencies.forEach { dependency in
@ -82,114 +94,80 @@ class CacheGraphMutator: CacheGraphMutating {
return return
} }
// If the target cannot be replaced with its associated .xcframework we return // If the target cannot be replaced with its associated .(xc)framework we return
guard !sources.contains(targetDependency.target.name), let xcframeworkPath = xcframeworkPath(target: targetDependency, guard !sources.contains(targetDependency.target.name), let precompiledFrameworkPath = precompiledFrameworkPath(target: targetDependency,
xcframeworks: xcframeworks, precompiledFrameworks: precompiledFrameworks,
visitedXCFrameworkPaths: &visitedXCFrameworkPaths) visitedPrecompiledFrameworkPaths: &visitedPrecompiledFrameworkPaths)
else { else {
sourceTargets.formUnion([targetDependency]) sourceTargets.formUnion([targetDependency])
targetDependency.dependencies = try mapDependencies(targetDependency.dependencies, targetDependency.dependencies = try mapDependencies(targetDependency.dependencies,
xcframeworks: xcframeworks, precompiledFrameworks: precompiledFrameworks,
sources: sources, sources: sources,
sourceTargets: &sourceTargets, sourceTargets: &sourceTargets,
visitedXCFrameworkPaths: &visitedXCFrameworkPaths, visitedPrecompiledFrameworkPaths: &visitedPrecompiledFrameworkPaths,
loadedXCFrameworks: &loadedXCFrameworks) loadedPrecompiledFrameworks: &loadedPrecompiledFrameworks)
newDependencies.append(targetDependency) newDependencies.append(targetDependency)
return return
} }
// We load the xcframework // We load the .framework (or fallback on .xcframework)
let xcframework = try self.loadXCFramework(path: xcframeworkPath, loadedXCFrameworks: &loadedXCFrameworks) let precompiledFramework: PrecompiledNode = try loadPrecompiledFramework(path: precompiledFrameworkPath, loadedPrecompiledFrameworks: &loadedPrecompiledFrameworks)
try mapDependencies(targetDependency.dependencies, try mapDependencies(targetDependency.dependencies,
xcframeworks: xcframeworks, precompiledFrameworks: precompiledFrameworks,
sources: sources, sources: sources,
sourceTargets: &sourceTargets, sourceTargets: &sourceTargets,
visitedXCFrameworkPaths: &visitedXCFrameworkPaths, visitedPrecompiledFrameworkPaths: &visitedPrecompiledFrameworkPaths,
loadedXCFrameworks: &loadedXCFrameworks).forEach { dependency in loadedPrecompiledFrameworks: &loadedPrecompiledFrameworks).forEach { dependency in
if let frameworkDependency = dependency as? FrameworkNode { if let frameworkDependency = dependency as? FrameworkNode {
xcframework.add(dependency: XCFrameworkNode.Dependency.framework(frameworkDependency)) precompiledFramework.add(dependency: PrecompiledNode.Dependency.framework(frameworkDependency))
} else if let xcframeworkDependency = dependency as? XCFrameworkNode { } else if let xcframeworkDependency = dependency as? XCFrameworkNode {
xcframework.add(dependency: XCFrameworkNode.Dependency.xcframework(xcframeworkDependency)) precompiledFramework.add(dependency: PrecompiledNode.Dependency.xcframework(xcframeworkDependency))
} else { } else {
// Static dependencies fall into this case. // Static dependencies fall into this case.
// Those are now part of the precompiled xcframework and therefore we don't have to link against them. // Those are now part of the precompiled (xc)framework and therefore we don't have to link against them.
} }
} }
newDependencies.append(xcframework) newDependencies.append(precompiledFramework)
} }
return newDependencies return newDependencies
} }
func treeShake(graph: Graph, sourceTargets: Set<TargetNode>) -> Graph { fileprivate func loadPrecompiledFramework(path: AbsolutePath, loadedPrecompiledFrameworks: inout [AbsolutePath: PrecompiledNode]) throws -> PrecompiledNode {
let targetReferences = Set(sourceTargets.map { TargetReference(projectPath: $0.path, name: $0.name) }) if let cachedFramework = loadedPrecompiledFrameworks[path] {
return cachedFramework
let projects = graph.projects.compactMap { (project) -> Project? in } else if let framework = try? frameworkLoader.load(path: path) {
let targets: [Target] = project.targets.compactMap { (target) -> Target? in loadedPrecompiledFrameworks[path] = framework
guard let targetNode = graph.target(path: project.path, name: target.name) else { return nil } return framework
guard sourceTargets.contains(targetNode) else { return nil }
return target
}
if targets.isEmpty {
return nil
} else { } else {
let schemes: [Scheme] = project.schemes.compactMap { scheme -> Scheme? in
let buildActionTargets = scheme.buildAction?.targets.filter { targetReferences.contains($0) } ?? []
// The scheme contains no buildable targets so we don't include it.
if buildActionTargets.isEmpty { return nil }
let testActionTargets = scheme.testAction?.targets.filter { targetReferences.contains($0.target) } ?? []
var scheme = scheme
var buildAction = scheme.buildAction
var testAction = scheme.testAction
buildAction?.targets = buildActionTargets
testAction?.targets = testActionTargets
scheme.buildAction = buildAction
scheme.testAction = testAction
return scheme
}
return project.with(targets: targets).with(schemes: schemes)
}
}
return graph
.with(projects: projects)
.with(targets: sourceTargets.reduce(into: [AbsolutePath: [TargetNode]]()) { acc, target in
var targets = acc[target.path, default: []]
targets.append(target)
acc[target.path] = targets
})
}
fileprivate func loadXCFramework(path: AbsolutePath, loadedXCFrameworks: inout [AbsolutePath: XCFrameworkNode]) throws -> XCFrameworkNode {
if let cachedXCFramework = loadedXCFrameworks[path] { return cachedXCFramework }
let xcframework = try xcframeworkLoader.load(path: path) let xcframework = try xcframeworkLoader.load(path: path)
loadedXCFrameworks[path] = xcframework loadedPrecompiledFrameworks[path] = xcframework
return xcframework return xcframework
} }
}
fileprivate func xcframeworkPath(target: TargetNode, fileprivate func precompiledFrameworkPath(target: TargetNode,
xcframeworks: [TargetNode: AbsolutePath], precompiledFrameworks: [TargetNode: AbsolutePath],
visitedXCFrameworkPaths: inout [TargetNode: VisitedXCFramework?]) -> AbsolutePath? visitedPrecompiledFrameworkPaths: inout [TargetNode: VisitedPrecompiledFramework?]) -> AbsolutePath?
{ {
// Already visited // Already visited
if let visited = visitedXCFrameworkPaths[target] { return visited?.path } if let visited = visitedPrecompiledFrameworkPaths[target] { return visited?.path }
// The target doesn't have a cached xcframework // The target doesn't have a cached .(xc)framework
if xcframeworks[target] == nil { if precompiledFrameworks[target] == nil {
visitedXCFrameworkPaths[target] = VisitedXCFramework(path: nil) visitedPrecompiledFrameworkPaths[target] = VisitedPrecompiledFramework(path: nil)
return nil return nil
} }
// The target can be replaced // The target can be replaced
else if let path = xcframeworks[target], else if let path = precompiledFrameworks[target],
target.targetDependencies.allSatisfy({ xcframeworkPath(target: $0, xcframeworks: xcframeworks, target.targetDependencies.allSatisfy({ precompiledFrameworkPath(target: $0,
visitedXCFrameworkPaths: &visitedXCFrameworkPaths) != nil }) precompiledFrameworks: precompiledFrameworks,
visitedPrecompiledFrameworkPaths: &visitedPrecompiledFrameworkPaths) != nil })
{ {
visitedXCFrameworkPaths[target] = VisitedXCFramework(path: path) visitedPrecompiledFrameworkPaths[target] = VisitedPrecompiledFramework(path: path)
return path return path
} else { } else {
visitedXCFrameworkPaths[target] = VisitedXCFramework(path: nil) visitedPrecompiledFrameworkPaths[target] = VisitedPrecompiledFramework(path: nil)
return nil return nil
} }
} }

View File

@ -17,7 +17,7 @@ public class CacheMapper: GraphMapping {
/// Cache graph mapper. /// Cache graph mapper.
private let cacheGraphMutator: CacheGraphMutating private let cacheGraphMutator: CacheGraphMutating
/// Configuration object /// Configuration object.
private let config: Config private let config: Config
/// List of targets that will be generated as sources instead of pre-compiled targets from the cache. /// List of targets that will be generated as sources instead of pre-compiled targets from the cache.
@ -26,22 +26,28 @@ public class CacheMapper: GraphMapping {
/// Dispatch queue. /// Dispatch queue.
private let queue: DispatchQueue private let queue: DispatchQueue
/// The type of artifact that the hasher is configured with.
private let cacheOutputType: CacheOutputType
// MARK: - Init // MARK: - Init
public convenience init(config: Config, public convenience init(config: Config,
cacheStorageProvider: CacheStorageProviding, cacheStorageProvider: CacheStorageProviding,
sources: Set<String>) sources: Set<String>,
cacheOutputType: CacheOutputType)
{ {
self.init(config: config, self.init(config: config,
cache: Cache(storageProvider: cacheStorageProvider), cache: Cache(storageProvider: cacheStorageProvider),
graphContentHasher: GraphContentHasher(), graphContentHasher: GraphContentHasher(),
sources: sources) sources: sources,
cacheOutputType: cacheOutputType)
} }
init(config: Config, init(config: Config,
cache: CacheStoring, cache: CacheStoring,
graphContentHasher: GraphContentHashing, graphContentHasher: GraphContentHashing,
sources: Set<String>, sources: Set<String>,
cacheOutputType: CacheOutputType,
cacheGraphMutator: CacheGraphMutating = CacheGraphMutator(), cacheGraphMutator: CacheGraphMutating = CacheGraphMutator(),
queue: DispatchQueue = CacheMapper.dispatchQueue()) queue: DispatchQueue = CacheMapper.dispatchQueue())
{ {
@ -51,6 +57,7 @@ public class CacheMapper: GraphMapping {
self.queue = queue self.queue = queue
self.cacheGraphMutator = cacheGraphMutator self.cacheGraphMutator = cacheGraphMutator
self.sources = sources self.sources = sources
self.cacheOutputType = cacheOutputType
} }
// MARK: - GraphMapping // MARK: - GraphMapping
@ -70,7 +77,8 @@ public class CacheMapper: GraphMapping {
fileprivate func hashes(graph: Graph) -> Single<[TargetNode: String]> { fileprivate func hashes(graph: Graph) -> Single<[TargetNode: String]> {
Single.create { (observer) -> Disposable in Single.create { (observer) -> Disposable in
do { do {
let hashes = try self.graphContentHasher.contentHashes(for: graph) let hashes = try self.graphContentHasher.contentHashes(for: graph,
cacheOutputType: self.cacheOutputType)
observer(.success(hashes)) observer(.success(hashes))
} catch { } catch {
observer(.error(error)) observer(.error(error))
@ -83,7 +91,7 @@ public class CacheMapper: GraphMapping {
fileprivate func map(graph: Graph, hashes: [TargetNode: String], sources: Set<String>) -> Single<Graph> { fileprivate func map(graph: Graph, hashes: [TargetNode: String], sources: Set<String>) -> Single<Graph> {
fetch(hashes: hashes).map { xcframeworkPaths in fetch(hashes: hashes).map { xcframeworkPaths in
try self.cacheGraphMutator.map(graph: graph, try self.cacheGraphMutator.map(graph: graph,
xcframeworks: xcframeworkPaths, precompiledFrameworks: xcframeworkPaths,
sources: sources) sources: sources)
} }
} }

View File

@ -0,0 +1,72 @@
import Foundation
import TSCBasic
import TuistCore
public final class CacheTreeShakingGraphMapper: GraphMapping {
public init() {}
public func map(graph: Graph) throws -> (Graph, [SideEffectDescriptor]) {
let sourceTargets: Set<TargetReference> = graph.targets.reduce(into: Set<TargetReference>()) { acc, next in
acc.formUnion(next.value.filter { !$0.prune }.map { TargetReference(projectPath: $0.path, name: $0.name) })
}
// If the number of source targets matches the number of targets in the graph there's nothing to be pruned.
if sourceTargets.count == graph.targets.flatMap(\.value).count { return (graph, []) }
let projects = graph.projects.compactMap { (project) -> Project? in
let targets = self.treeShake(targets: project.targets,
path: project.path,
graph: graph,
sourceTargets: sourceTargets)
// If the project has no targets we remove the project.
if targets.isEmpty {
return nil
} else {
let schemes = self.treeShake(schemes: project.schemes,
sourceTargets: sourceTargets)
return project.with(targets: targets).with(schemes: schemes)
}
}
let graph = graph
.with(projects: projects)
.with(targets: sourceTargets.reduce(into: [AbsolutePath: [TargetNode]]()) { acc, targetReference in
var targets = acc[targetReference.projectPath, default: []]
if let target = graph.target(path: targetReference.projectPath, name: targetReference.name) {
targets.append(target)
}
acc[targetReference.projectPath] = targets
})
return (graph, [])
}
fileprivate func treeShake(targets: [Target], path: AbsolutePath, graph: Graph, sourceTargets: Set<TargetReference>) -> [Target] {
targets.compactMap { (target) -> Target? in
guard let targetNode = graph.target(path: path, name: target.name) else { return nil }
let targetReference = TargetReference(projectPath: targetNode.path, name: targetNode.name)
guard sourceTargets.contains(targetReference) else { return nil }
return target
}
}
fileprivate func treeShake(schemes: [Scheme], sourceTargets: Set<TargetReference>) -> [Scheme] {
schemes.compactMap { scheme -> Scheme? in
let buildActionTargets = scheme.buildAction?.targets.filter { sourceTargets.contains($0) } ?? []
// The scheme contains no buildable targets so we don't include it.
if buildActionTargets.isEmpty { return nil }
let testActionTargets = scheme.testAction?.targets.filter { sourceTargets.contains($0.target) } ?? []
var scheme = scheme
var buildAction = scheme.buildAction
var testAction = scheme.testAction
buildAction?.targets = buildActionTargets
testAction?.targets = testActionTargets
scheme.buildAction = buildAction
scheme.testAction = testAction
return scheme
}
}
}

View File

@ -0,0 +1,23 @@
import TSCBasic
import TuistCore
public protocol CacheArtifactBuilding {
/// Returns the type of artifact that the concrete builder processes.
var cacheOutputType: CacheOutputType { get }
/// Builds a given target and outputs the cacheable artifact into the given directory.
///
/// - Parameters:
/// - workspacePath: Path to the generated .xcworkspace that contains the given target.
/// - target: Target whose artifact will be generated.
/// - into: The directory into which the output artifacts will be copied.
func build(workspacePath: AbsolutePath, target: Target, into outputDirectory: AbsolutePath) throws
/// Builds a given target and outputs the cacheable artifact into the given directory.
///
/// - Parameters:
/// - projectPath: Path to the generated .xcodeproj that contains the given target.
/// - target: Target whose .(xc)framework will be generated.
/// - into: The directory into which the output artifacts will be copied.
func build(projectPath: AbsolutePath, target: Target, into outputDirectory: AbsolutePath) throws
}

View File

@ -0,0 +1,24 @@
import TuistSupport
enum CacheBinaryBuilderError: FatalError {
case nonFrameworkTargetForXCFramework(String)
case nonFrameworkTargetForFramework(String)
/// Error type.
var type: ErrorType {
switch self {
case .nonFrameworkTargetForXCFramework: return .abort
case .nonFrameworkTargetForFramework: return .abort
}
}
/// Error description.
var description: String {
switch self {
case let .nonFrameworkTargetForXCFramework(name):
return "Can't generate an .xcframework from the target '\(name)' because it's not a framework target"
case let .nonFrameworkTargetForFramework(name):
return "Can't generate a .framework from the target '\(name)' because it's not a framework target"
}
}
}

View File

@ -0,0 +1,209 @@
import Foundation
import RxBlocking
import RxSwift
import TSCBasic
import TuistCore
import TuistSupport
public enum CacheFrameworkBuilderError: FatalError {
case builtProductsDirectoryNotFound(targetName: String)
case frameworkNotFound(name: String, derivedDataPath: AbsolutePath)
case deviceNotFound(platform: String)
public var description: String {
switch self {
case let .builtProductsDirectoryNotFound(targetName):
return "Couldn't find the built products directory for target '\(targetName)'."
case let .frameworkNotFound(name, derivedDataPath):
return "Couldn't find framework '\(name)' in the derived data directory: \(derivedDataPath.pathString)"
case let .deviceNotFound(platform):
return "Couldn't find an available device for platform: '\(platform)'"
}
}
public var type: ErrorType {
switch self {
case .builtProductsDirectoryNotFound: return .bug
case .frameworkNotFound: return .bug
case .deviceNotFound: return .bug
}
}
}
public final class CacheFrameworkBuilder: CacheArtifactBuilding {
// MARK: - Attributes
/// Xcode build controller instance to run xcodebuild commands.
private let xcodeBuildController: XcodeBuildControlling
/// Simulator controller.
private let simulatorController: SimulatorControlling
/// Developer's environment.
private let developerEnvironment: DeveloperEnvironmenting
// MARK: - Init
/// Initialzies the builder.
/// - Parameters:
/// - xcodeBuildController: Xcode build controller.
/// - simulatorController: Simulator controller.
/// - developerEnvironment: Developer environment.
public init(xcodeBuildController: XcodeBuildControlling,
simulatorController: SimulatorControlling = SimulatorController(),
developerEnvironment: DeveloperEnvironmenting = DeveloperEnvironment.shared)
{
self.xcodeBuildController = xcodeBuildController
self.simulatorController = simulatorController
self.developerEnvironment = developerEnvironment
}
// MARK: - ArtifactBuilding
/// Returns the type of artifact that the concrete builder processes
public var cacheOutputType: CacheOutputType = .framework
public func build(workspacePath: AbsolutePath,
target: Target,
into outputDirectory: AbsolutePath) throws
{
try build(.workspace(workspacePath),
target: target,
into: outputDirectory)
}
public func build(projectPath: AbsolutePath,
target: Target,
into outputDirectory: AbsolutePath) throws
{
try build(.project(projectPath),
target: target,
into: outputDirectory)
}
// MARK: - Fileprivate
fileprivate func build(_ projectTarget: XcodeBuildTarget,
target: Target,
into outputDirectory: AbsolutePath) throws
{
guard target.product.isFramework else {
throw CacheBinaryBuilderError.nonFrameworkTargetForFramework(target.name)
}
let scheme = target.name.spm_shellEscaped()
// Create temporary directories
let builtProductsDirFingerprint = String.random()
logger.notice("Building .framework for \(target.name)...", metadata: .section)
let sdk = self.sdk(target: target)
let configuration = "Debug" // TODO: Is it available?
let arguments = try self.arguments(target: target,
sdk: sdk,
configuration: configuration,
builtProductsDirFingerprint: builtProductsDirFingerprint)
try xcodebuild(
projectTarget: projectTarget,
scheme: scheme,
target: target,
arguments: arguments
)
try exportFrameworksAndDSYMs(into: outputDirectory,
target: target,
builtProductsDirFingerprint: builtProductsDirFingerprint)
}
fileprivate func arguments(target: Target,
sdk: String,
configuration: String,
builtProductsDirFingerprint: String) throws -> [XcodeBuildArgument]
{
try destination(target: target)
.map { (destination: String) -> [XcodeBuildArgument] in
[
.sdk(sdk),
.configuration(configuration),
.buildSetting("DEBUG_INFORMATION_FORMAT", "dwarf-with-dsym"),
.buildSetting("GCC_GENERATE_DEBUGGING_SYMBOLS", "YES"),
.buildSetting(target.targetLocatorBuildPhaseVariable, builtProductsDirFingerprint),
.destination(destination),
]
}
.toBlocking()
.single()
}
/// https://www.mokacoding.com/blog/xcodebuild-destination-options/
/// https://www.mokacoding.com/blog/how-to-always-run-latest-simulator-cli/
fileprivate func destination(target: Target) -> Single<String> {
var platform: Platform!
switch target.platform {
case .iOS: platform = .iOS
case .watchOS: platform = .watchOS
case .tvOS: platform = .tvOS
case .macOS: return .just("platform=OS X,arch=x86_64")
}
return simulatorController.devicesAndRuntimes()
.map { (simulatorsAndRuntimes) -> [SimulatorDevice] in
simulatorsAndRuntimes
.filter { $0.runtime.isAvailable && $0.runtime.name.contains(platform.caseValue) }
.map { $0.device }
}
.flatMap { (devices) -> Single<String> in
if let device = devices.first {
let destination = "platform=\(platform.caseValue) Simulator,name=\(device.name),OS=latest"
return .just(destination)
} else {
return .error(CacheFrameworkBuilderError.deviceNotFound(platform: target.platform.caseValue))
}
}
}
fileprivate func sdk(target: Target) -> String {
if target.platform == .macOS {
return target.platform.xcodeDeviceSDK
} else {
return target.platform.xcodeSimulatorSDK!
}
}
fileprivate func xcodebuild(projectTarget: XcodeBuildTarget,
scheme: String,
target: Target,
arguments: [XcodeBuildArgument]) throws
{
_ = try xcodeBuildController.build(projectTarget,
scheme: scheme,
clean: false,
arguments: arguments)
.printFormattedOutput()
.do(onSubscribed: {
logger.notice("Building \(target.name) as .framework...", metadata: .subsection)
})
.ignoreElements()
.toBlocking()
.last()
}
fileprivate func exportFrameworksAndDSYMs(into outputDirectory: AbsolutePath,
target: Target,
builtProductsDirFingerprint: String) throws
{
let globPattern = "**/.\(builtProductsDirFingerprint).tuist"
let derivedDataPath = developerEnvironment.derivedDataDirectory
guard let directory = FileHandler.shared.glob(derivedDataPath, glob: globPattern).first?.parentDirectory else {
throw CacheFrameworkBuilderError.builtProductsDirectoryNotFound(targetName: target.name)
}
guard let framework = FileHandler.shared.glob(directory, glob: target.productNameWithExtension).first else {
throw CacheFrameworkBuilderError.frameworkNotFound(name: target.productNameWithExtension, derivedDataPath: derivedDataPath)
}
let dsyms = FileHandler.shared.glob(directory, glob: "\(target.productNameWithExtension).dSYM")
try FileHandler.shared.copy(from: framework, to: outputDirectory.appending(component: framework.basename))
try dsyms.forEach { dsym in
try FileHandler.shared.copy(from: dsym, to: outputDirectory.appending(component: dsym.basename))
}
}
}

View File

@ -0,0 +1,159 @@
import Foundation
import RxBlocking
import RxSwift
import TSCBasic
import TuistCore
import TuistSupport
public final class CacheXCFrameworkBuilder: CacheArtifactBuilding {
// MARK: - Attributes
/// Xcode build controller instance to run xcodebuild commands.
private let xcodeBuildController: XcodeBuildControlling
// MARK: - Init
/// Initializes the builder.
/// - Parameter xcodeBuildController: Xcode build controller instance to run xcodebuild commands.
public init(xcodeBuildController: XcodeBuildControlling) {
self.xcodeBuildController = xcodeBuildController
}
// MARK: - ArtifactBuilding
/// Returns the type of artifact that the concrete builder processes
public var cacheOutputType: CacheOutputType = .xcframework
public func build(workspacePath: AbsolutePath,
target: Target,
into outputDirectory: AbsolutePath) throws
{
try build(.workspace(workspacePath),
target: target,
into: outputDirectory)
}
public func build(projectPath: AbsolutePath,
target: Target,
into outputDirectory: AbsolutePath) throws
{
try build(.project(projectPath),
target: target,
into: outputDirectory)
}
// MARK: - Fileprivate
// swiftlint:disable:next function_body_length
fileprivate func build(_ projectTarget: XcodeBuildTarget,
target: Target,
into outputDirectory: AbsolutePath) throws
{
guard target.product.isFramework else {
throw CacheBinaryBuilderError.nonFrameworkTargetForXCFramework(target.name)
}
let scheme = target.name.spm_shellEscaped()
// Create temporary directories
return try FileHandler.shared.inTemporaryDirectory { temporaryDirectory in
logger.notice("Building .xcframework for \(target.name)...", metadata: .section)
// Build for the simulator
var simulatorArchivePath: AbsolutePath?
if target.platform.hasSimulators {
simulatorArchivePath = temporaryDirectory.appending(component: "simulator.xcarchive")
try simulatorBuild(
projectTarget: projectTarget,
scheme: scheme,
target: target,
archivePath: simulatorArchivePath!
)
}
// Build for the device - if required
let deviceArchivePath = temporaryDirectory.appending(component: "device.xcarchive")
try deviceBuild(
projectTarget: projectTarget,
scheme: scheme,
target: target,
archivePath: deviceArchivePath
)
// Build the xcframework
var frameworkpaths = [AbsolutePath]()
if let simulatorArchivePath = simulatorArchivePath {
frameworkpaths.append(frameworkPath(fromArchivePath: simulatorArchivePath, productName: target.productName))
}
frameworkpaths.append(frameworkPath(fromArchivePath: deviceArchivePath, productName: target.productName))
let xcframeworkPath = outputDirectory.appending(component: "\(target.productName).xcframework")
try buildXCFramework(frameworks: frameworkpaths, output: xcframeworkPath, target: target)
try FileHandler.shared.move(from: xcframeworkPath, to: outputDirectory.appending(component: xcframeworkPath.basename))
}
}
fileprivate func buildXCFramework(frameworks: [AbsolutePath], output: AbsolutePath, target: Target) throws {
_ = try xcodeBuildController.createXCFramework(frameworks: frameworks, output: output)
.do(onSubscribed: {
logger.notice("Exporting xcframework for \(target.platform.caseValue)", metadata: .subsection)
})
.toBlocking()
.single()
}
fileprivate func deviceBuild(projectTarget: XcodeBuildTarget,
scheme: String,
target: Target,
archivePath: AbsolutePath) throws
{
// Without the BUILD_LIBRARY_FOR_DISTRIBUTION argument xcodebuild doesn't generate the .swiftinterface file
_ = try xcodeBuildController.archive(projectTarget,
scheme: scheme,
clean: false,
archivePath: archivePath,
arguments: [
.sdk(target.platform.xcodeDeviceSDK),
.buildSetting("SKIP_INSTALL", "NO"),
.buildSetting("BUILD_LIBRARY_FOR_DISTRIBUTION", "YES"),
])
.printFormattedOutput()
.do(onSubscribed: {
logger.notice("Building \(target.name) for device...", metadata: .subsection)
})
.ignoreElements()
.toBlocking()
.last()
}
fileprivate func simulatorBuild(projectTarget: XcodeBuildTarget,
scheme: String,
target: Target,
archivePath: AbsolutePath) throws
{
// Without the BUILD_LIBRARY_FOR_DISTRIBUTION argument xcodebuild doesn't generate the .swiftinterface file
_ = try xcodeBuildController.archive(projectTarget,
scheme: scheme,
clean: false,
archivePath: archivePath,
arguments: [
.sdk(target.platform.xcodeSimulatorSDK!),
.buildSetting("SKIP_INSTALL", "NO"),
.buildSetting("BUILD_LIBRARY_FOR_DISTRIBUTION", "YES"),
])
.printFormattedOutput()
.do(onSubscribed: {
logger.notice("Building \(target.name) for simulator...", metadata: .subsection)
})
.ignoreElements()
.toBlocking()
.last()
}
/// Returns the path to the framework inside the archive.
/// - Parameters:
/// - archivePath: Path to the .xcarchive.
/// - productName: Product name.
fileprivate func frameworkPath(fromArchivePath archivePath: AbsolutePath, productName: String) -> AbsolutePath {
archivePath.appending(RelativePath("Products/Library/Frameworks/\(productName).framework"))
}
}

View File

@ -1,189 +0,0 @@
import Foundation
import RxSwift
import TSCBasic
import TuistCore
import TuistSupport
enum XCFrameworkBuilderError: FatalError {
case nonFrameworkTarget(String)
/// Error type.
var type: ErrorType {
switch self {
case .nonFrameworkTarget: return .abort
}
}
/// Error description.
var description: String {
switch self {
case let .nonFrameworkTarget(name):
return "Can't generate an .xcframework from the target '\(name)' because it's not a framework target"
}
}
}
public protocol XCFrameworkBuilding {
/// Returns an observable build an xcframework for the given target.
/// The target must have framework as product.
///
/// - Parameters:
/// - workspacePath: Path to the generated .xcworkspace that contains the given target.
/// - target: Target whose .xcframework will be generated.
/// - includeDeviceArch: Define whether the .xcframework will also contain the target built for devices (it only contains the target built for simulators by default).
/// - Returns: Path to the compiled .xcframework.
func build(workspacePath: AbsolutePath, target: Target, includeDeviceArch: Bool) throws -> Observable<AbsolutePath>
/// Returns an observable to build an xcframework for the given target.
/// The target must have framework as product.
///
/// - Parameters:
/// - projectPath: Path to the generated .xcodeproj that contains the given target.
/// - target: Target whose .xcframework will be generated.
/// - includeDeviceArch: Define whether the .xcframework will also contain the target built for devices (it only contains the target built for simulators by default).
/// - Returns: Path to the compiled .xcframework.
func build(projectPath: AbsolutePath, target: Target, includeDeviceArch: Bool) throws -> Observable<AbsolutePath>
}
public final class XCFrameworkBuilder: XCFrameworkBuilding {
// MARK: - Attributes
/// Xcode build controller instance to run xcodebuild commands.
private let xcodeBuildController: XcodeBuildControlling
// MARK: - Init
/// Initializes the builder.
/// - Parameter xcodeBuildController: Xcode build controller instance to run xcodebuild commands.
public init(xcodeBuildController: XcodeBuildControlling) {
self.xcodeBuildController = xcodeBuildController
}
// MARK: - XCFrameworkBuilding
public func build(workspacePath: AbsolutePath, target: Target, includeDeviceArch: Bool) throws -> Observable<AbsolutePath> {
try build(.workspace(workspacePath), target: target, includeDeviceArch: includeDeviceArch)
}
public func build(projectPath: AbsolutePath, target: Target, includeDeviceArch: Bool) throws -> Observable<AbsolutePath> {
try build(.project(projectPath), target: target, includeDeviceArch: includeDeviceArch)
}
// MARK: - Fileprivate
fileprivate func deviceBuild(projectTarget: XcodeBuildTarget,
scheme: String,
target: Target,
deviceArchivePath: AbsolutePath) -> Observable<SystemEvent<XcodeBuildOutput>>
{
// Without the BUILD_LIBRARY_FOR_DISTRIBUTION argument xcodebuild doesn't generate the .swiftinterface file
xcodeBuildController.archive(projectTarget,
scheme: scheme,
clean: false,
archivePath: deviceArchivePath,
arguments: [
.sdk(target.platform.xcodeDeviceSDK),
.buildSetting("SKIP_INSTALL", "NO"),
.buildSetting("BUILD_LIBRARY_FOR_DISTRIBUTION", "YES"),
])
.printFormattedOutput()
.do(onSubscribed: {
logger.notice("Building \(target.name) for device...", metadata: .subsection)
})
}
fileprivate func simulatorBuild(projectTarget: XcodeBuildTarget,
scheme: String,
target: Target,
simulatorArchivePath: AbsolutePath) -> Observable<SystemEvent<XcodeBuildOutput>>
{
// Without the BUILD_LIBRARY_FOR_DISTRIBUTION argument xcodebuild doesn't generate the .swiftinterface file
xcodeBuildController.archive(projectTarget,
scheme: scheme,
clean: false,
archivePath: simulatorArchivePath,
arguments: [
.sdk(target.platform.xcodeSimulatorSDK!),
.buildSetting("SKIP_INSTALL", "NO"),
.buildSetting("BUILD_LIBRARY_FOR_DISTRIBUTION", "YES"),
])
.printFormattedOutput()
.do(onSubscribed: {
logger.notice("Building \(target.name) for simulator...", metadata: .subsection)
})
}
// swiftlint:disable:next function_body_length
fileprivate func build(_ projectTarget: XcodeBuildTarget, target: Target, includeDeviceArch: Bool) throws -> Observable<AbsolutePath> {
guard target.product.isFramework else {
throw XCFrameworkBuilderError.nonFrameworkTarget(target.name)
}
let scheme = target.name.spm_shellEscaped()
// Create temporary directories
return try withTemporaryDirectories { outputDirectory, temporaryPath in
logger.notice("Building .xcframework for \(target.name)...", metadata: .section)
// Build for the simulator
var simulatorArchiveObservable: Observable<SystemEvent<XcodeBuildOutput>>
var simulatorArchivePath: AbsolutePath?
if target.platform.hasSimulators {
simulatorArchivePath = temporaryPath.appending(component: "simulator.xcarchive")
simulatorArchiveObservable = simulatorBuild(
projectTarget: projectTarget,
scheme: scheme,
target: target,
simulatorArchivePath: simulatorArchivePath!
)
} else {
simulatorArchiveObservable = Observable.empty()
}
// Build for the device - if required
let deviceArchivePath = temporaryPath.appending(component: "device.xcarchive")
var deviceArchiveObservable: Observable<SystemEvent<XcodeBuildOutput>>
if includeDeviceArch {
deviceArchiveObservable = deviceBuild(
projectTarget: projectTarget,
scheme: scheme,
target: target,
deviceArchivePath: deviceArchivePath
)
} else {
deviceArchiveObservable = Observable.empty()
}
// Build the xcframework
var frameworkpaths: [AbsolutePath] = [AbsolutePath]()
if let simulatorArchivePath = simulatorArchivePath {
frameworkpaths.append(frameworkPath(fromArchivePath: simulatorArchivePath, productName: target.productName))
}
if includeDeviceArch {
frameworkpaths.append(frameworkPath(fromArchivePath: deviceArchivePath, productName: target.productName))
}
let xcframeworkPath = outputDirectory.appending(component: "\(target.productName).xcframework")
let xcframeworkObservable = xcodeBuildController.createXCFramework(frameworks: frameworkpaths, output: xcframeworkPath)
.do(onSubscribed: {
logger.notice("Exporting xcframework for \(target.platform.caseValue)", metadata: .subsection)
})
return deviceArchiveObservable
.concat(simulatorArchiveObservable)
.concat(xcframeworkObservable)
.ignoreElements()
.andThen(Observable.just(xcframeworkPath))
.do(afterCompleted: {
try FileHandler.shared.delete(temporaryPath)
})
}
}
/// Returns the path to the framework inside the archive.
/// - Parameters:
/// - archivePath: Path to the .xcarchive.
/// - productName: Product name.
fileprivate func frameworkPath(fromArchivePath archivePath: AbsolutePath, productName: String) -> AbsolutePath {
archivePath.appending(RelativePath("Products/Library/Frameworks/\(productName).framework"))
}
}

View File

@ -0,0 +1,52 @@
import Foundation
import RxSwift
import TSCBasic
import TuistCache
import TuistCore
import TuistSupportTesting
public final class MockCacheArtifactBuilder: CacheArtifactBuilding {
public init() {}
public var invokedCacheOutputTypeGetter = false
public var invokedCacheOutputTypeGetterCount = 0
public var stubbedCacheOutputType: CacheOutputType!
public var cacheOutputType: CacheOutputType {
invokedCacheOutputTypeGetter = true
invokedCacheOutputTypeGetterCount += 1
return stubbedCacheOutputType
}
public var invokedBuildWorkspacePath = false
public var invokedBuildWorkspacePathCount = 0
public var invokedBuildWorkspacePathParameters: (workspacePath: AbsolutePath, target: Target, outputDirectory: AbsolutePath)?
public var invokedBuildWorkspacePathParametersList = [(workspacePath: AbsolutePath, target: Target, outputDirectory: AbsolutePath)]()
public var stubbedBuildWorkspacePathError: Error?
public func build(workspacePath: AbsolutePath, target: Target, into outputDirectory: AbsolutePath) throws {
invokedBuildWorkspacePath = true
invokedBuildWorkspacePathCount += 1
invokedBuildWorkspacePathParameters = (workspacePath, target, outputDirectory)
invokedBuildWorkspacePathParametersList.append((workspacePath, target, outputDirectory))
if let error = stubbedBuildWorkspacePathError {
throw error
}
}
public var invokedBuildProjectPath = false
public var invokedBuildProjectPathCount = 0
public var invokedBuildProjectPathParameters: (projectPath: AbsolutePath, target: Target, outputDirectory: AbsolutePath)?
public var invokedBuildProjectPathParametersList = [(projectPath: AbsolutePath, target: Target, outputDirectory: AbsolutePath)]()
public var stubbedBuildProjectPathError: Error?
public func build(projectPath: AbsolutePath, target: Target, into outputDirectory: AbsolutePath) throws {
invokedBuildProjectPath = true
invokedBuildProjectPathCount += 1
invokedBuildProjectPathParameters = (projectPath, target, outputDirectory)
invokedBuildProjectPathParametersList.append((projectPath, target, outputDirectory))
if let error = stubbedBuildProjectPathError {
throw error
}
}
}

View File

@ -30,10 +30,10 @@ public final class MockCacheStorage: CacheStoring {
} }
} }
var storeStub: ((_ hash: String, _ xcframeworkPath: AbsolutePath) -> Void)? var storeStub: ((_ hash: String, _ paths: [AbsolutePath]) -> Void)?
public func store(hash: String, xcframeworkPath: AbsolutePath) -> Completable { public func store(hash: String, paths: [AbsolutePath]) -> Completable {
if let storeStub = storeStub { if let storeStub = storeStub {
storeStub(hash, xcframeworkPath) storeStub(hash, paths)
} }
return Completable.empty() return Completable.empty()
} }

View File

@ -1,42 +0,0 @@
import Foundation
import RxSwift
import TSCBasic
import TuistCache
import TuistCore
public final class MockXCFrameworkBuilder: XCFrameworkBuilding {
public var buildProjectArgs: [(projectPath: AbsolutePath, target: Target, includeDeviceArch: Bool)] = []
public var buildWorkspaceArgs: [(workspacePath: AbsolutePath, target: Target, includeDeviceArch: Bool)] = []
public var buildProjectStub: ((AbsolutePath, Target) -> Result<AbsolutePath, Error>)?
public var buildWorkspaceStub: ((AbsolutePath, Target) -> Result<AbsolutePath, Error>)?
public init() {}
public func build(projectPath: AbsolutePath, target: Target, includeDeviceArch: Bool) throws -> Observable<AbsolutePath> {
buildProjectArgs.append((projectPath: projectPath, target: target, includeDeviceArch: includeDeviceArch))
if let buildProjectStub = buildProjectStub {
switch buildProjectStub(projectPath, target) {
case let .failure(error):
return Observable.error(error)
case let .success(path):
return Observable.just(path)
}
} else {
return Observable.just(AbsolutePath.root)
}
}
public func build(workspacePath: AbsolutePath, target: Target, includeDeviceArch: Bool) throws -> Observable<AbsolutePath> {
buildWorkspaceArgs.append((workspacePath: workspacePath, target: target, includeDeviceArch: includeDeviceArch))
if let buildWorkspaceStub = buildWorkspaceStub {
switch buildWorkspaceStub(workspacePath, target) {
case let .failure(error):
return Observable.error(error)
case let .success(path):
return Observable.just(path)
}
} else {
return Observable.just(AbsolutePath.root)
}
}
}

View File

@ -3,23 +3,23 @@ import TuistCore
@testable import TuistCache @testable import TuistCache
public final class MockGraphContentHasher: GraphContentHashing { public final class MockGraphContentHasher: GraphContentHashing {
public init() {}
public var invokedContentHashes = false public var invokedContentHashes = false
public var invokedContentHashesCount = 0 public var invokedContentHashesCount = 0
public var invokedContentHashesParameters: (graph: TuistCore.Graph, Void)? public var invokedContentHashesParameters: (graph: TuistCore.Graph, cacheOutputType: CacheOutputType)?
public var invokedContentHashesParametersList = [(graph: TuistCore.Graph, Void)]() public var invokedContentHashesParametersList = [(graph: TuistCore.Graph, cacheOutputType: CacheOutputType)]()
public var stubbedContentHashesError: Error? public var stubbedContentHashesError: Error?
public var contentHashesStub: [TargetNode: String]! = [:] public var stubbedContentHashesResult: [TargetNode: String]! = [:]
public func contentHashes(for graph: TuistCore.Graph) throws -> [TargetNode: String] { public init() {}
public func contentHashes(for graph: TuistCore.Graph, cacheOutputType: CacheOutputType) throws -> [TargetNode: String] {
invokedContentHashes = true invokedContentHashes = true
invokedContentHashesCount += 1 invokedContentHashesCount += 1
invokedContentHashesParameters = (graph, ()) invokedContentHashesParameters = (graph, cacheOutputType)
invokedContentHashesParametersList.append((graph, ())) invokedContentHashesParametersList.append((graph, cacheOutputType))
if let error = stubbedContentHashesError { if let error = stubbedContentHashesError {
throw error throw error
} }
return contentHashesStub return stubbedContentHashesResult
} }
} }

View File

@ -3,6 +3,11 @@ import RxSwift
import TSCBasic import TSCBasic
import TuistSupport import TuistSupport
public enum XcodeBuildDestination: Equatable {
case device(String)
case mac
}
public protocol XcodeBuildControlling { public protocol XcodeBuildControlling {
/// Returns an observable to build the given project using xcodebuild. /// Returns an observable to build the given project using xcodebuild.
/// - Parameters: /// - Parameters:
@ -15,6 +20,20 @@ public protocol XcodeBuildControlling {
clean: Bool, clean: Bool,
arguments: [XcodeBuildArgument]) -> Observable<SystemEvent<XcodeBuildOutput>> arguments: [XcodeBuildArgument]) -> Observable<SystemEvent<XcodeBuildOutput>>
/// Returns an observable to test the given project using xcodebuild.
/// - Parameters:
/// - target: The project or workspace to be built.
/// - scheme: The scheme of the project that should be built.
/// - clean: True if xcodebuild should clean the project before building.
/// - arguments: Extra xcodebuild arguments.
func test(
_ target: XcodeBuildTarget,
scheme: String,
clean: Bool,
destination: XcodeBuildDestination,
arguments: [XcodeBuildArgument]
) -> Observable<SystemEvent<XcodeBuildOutput>>
/// Returns an observable that archives the given project using xcodebuild. /// Returns an observable that archives the given project using xcodebuild.
/// - Parameters: /// - Parameters:
/// - target: The project or workspace to be archived. /// - target: The project or workspace to be archived.

View File

@ -0,0 +1,19 @@
import Foundation
/// An enum that represents the type of output that the caching feature can work with.
public enum CacheOutputType: CustomStringConvertible {
/// Frameworks built for the simulator.
case framework
/// XCFrameworks built for the simulator and device.
case xcframework
public var description: String {
switch self {
case .framework:
return "framework"
case .xcframework:
return "xcframework"
}
}
}

View File

@ -33,7 +33,10 @@ public class Graph: Encodable, Equatable {
/// The entry nodes of the graph. /// The entry nodes of the graph.
public let entryNodes: [GraphNode] public let entryNodes: [GraphNode]
/// Dictionary whose keys are paths to directories where projects are defined, and the values are the representation of the projects. /// Workspace of the graph
public let workspace: Workspace
/// Projects of the graph
public let projects: [Project] public let projects: [Project]
/// Dictionary whose keys are paths to directories where projects are defined, and the values are the CocoaPods nodes define in them. /// Dictionary whose keys are paths to directories where projects are defined, and the values are the CocoaPods nodes define in them.
@ -54,31 +57,48 @@ public class Graph: Encodable, Equatable {
/// Dictionary whose keys are path to directories where projects are defined, and the values are target nodes defined in them. /// Dictionary whose keys are path to directories where projects are defined, and the values are target nodes defined in them.
public let targets: [AbsolutePath: [TargetNode]] public let targets: [AbsolutePath: [TargetNode]]
/// Schemes of the graph
public var schemes: [Scheme] {
projects.flatMap(\.schemes) + workspace.schemes
}
// MARK: - Init // MARK: - Init
convenience init(name: String, entryPath: AbsolutePath, cache: GraphLoaderCaching, entryNodes: [GraphNode]) { convenience init(
self.init(name: name, name: String,
entryPath: AbsolutePath,
cache: GraphLoaderCaching,
entryNodes: [GraphNode],
workspace: Workspace
) {
self.init(
name: name,
entryPath: entryPath, entryPath: entryPath,
entryNodes: entryNodes, entryNodes: entryNodes,
workspace: workspace,
projects: Array(cache.projects.values), projects: Array(cache.projects.values),
cocoapods: Array(cache.cocoapodsNodes.values), cocoapods: Array(cache.cocoapodsNodes.values),
packages: Array(cache.packages.flatMap { $0.value }), packages: Array(cache.packages.flatMap { $0.value }),
precompiled: Array(cache.precompiledNodes.values), precompiled: Array(cache.precompiledNodes.values),
targets: cache.targetNodes.mapValues { Array($0.values) }) targets: cache.targetNodes.mapValues { Array($0.values) }
)
} }
public init(name: String, public init(
name: String,
entryPath: AbsolutePath, entryPath: AbsolutePath,
entryNodes: [GraphNode], entryNodes: [GraphNode],
workspace: Workspace,
projects: [Project], projects: [Project],
cocoapods: [CocoaPodsNode], cocoapods: [CocoaPodsNode],
packages: [PackageNode], packages: [PackageNode],
precompiled: [PrecompiledNode], precompiled: [PrecompiledNode],
targets: [AbsolutePath: [TargetNode]]) targets: [AbsolutePath: [TargetNode]]
{ ) {
self.name = name self.name = name
self.entryPath = entryPath self.entryPath = entryPath
self.entryNodes = entryNodes self.entryNodes = entryNodes
self.workspace = workspace
self.projects = projects self.projects = projects
self.cocoapods = cocoapods self.cocoapods = cocoapods
self.packages = packages self.packages = packages
@ -186,7 +206,7 @@ public class Graph: Encodable, Equatable {
/// - Parameters: /// - Parameters:
/// - path: Path to the directory where the project that defines the target is located. /// - path: Path to the directory where the project that defines the target is located.
/// - name: Name of the target. /// - name: Name of the target.
public func linkableDependencies(path: AbsolutePath, name: String) -> [GraphDependencyReference] { public func linkableDependencies(path: AbsolutePath, name: String) throws -> [GraphDependencyReference] {
guard let targetNode = findTargetNode(path: path, name: name) else { guard let targetNode = findTargetNode(path: path, name: name) else {
return [] return []
} }
@ -205,6 +225,13 @@ public class Graph: Encodable, Equatable {
references = references.union(transitiveSystemLibraries) references = references.union(transitiveSystemLibraries)
} }
if targetNode.target.isAppClip {
let path = try SDKNode.appClip(status: .required).path
references.insert(GraphDependencyReference.sdk(path: path,
status: .required,
source: .system))
}
let directSystemLibrariesAndFrameworks = targetNode.sdkDependencies.map { let directSystemLibrariesAndFrameworks = targetNode.sdkDependencies.map {
GraphDependencyReference.sdk(path: $0.path, status: $0.status, source: $0.source) GraphDependencyReference.sdk(path: $0.path, status: $0.status, source: $0.source)
} }
@ -357,7 +384,7 @@ public class Graph: Encodable, Equatable {
if let hostApp = hostApplication(for: targetNode) { if let hostApp = hostApplication(for: targetNode) {
references.subtract(try embeddableFrameworks(path: hostApp.path, name: hostApp.name)) references.subtract(try embeddableFrameworks(path: hostApp.path, name: hostApp.name))
} else { } else {
references.subtract(precompiledFrameworks) references = []
} }
} }
@ -387,7 +414,7 @@ public class Graph: Encodable, Equatable {
/// - Parameter project: Project whose dependency references will be returned. /// - Parameter project: Project whose dependency references will be returned.
public func allDependencyReferences(for project: Project) throws -> [GraphDependencyReference] { public func allDependencyReferences(for project: Project) throws -> [GraphDependencyReference] {
let linkableDependencies = try project.targets.flatMap { let linkableDependencies = try project.targets.flatMap {
self.linkableDependencies(path: project.path, name: $0.name) try self.linkableDependencies(path: project.path, name: $0.name)
} }
let embeddableDependencies = try project.targets.flatMap { let embeddableDependencies = try project.targets.flatMap {
@ -419,6 +446,14 @@ public class Graph: Encodable, Equatable {
.filter { validProducts.contains($0.target.product) } .filter { validProducts.contains($0.target.product) }
} }
public func appClipsDependency(path: AbsolutePath, name: String) -> TargetNode? {
guard let targetNode = findTargetNode(path: path, name: name) else {
return nil
}
return targetNode.targetDependencies.first { $0.target.product == .appClip }
}
/// Depth-first search (DFS) is an algorithm for traversing graph data structures. It starts at a source node /// Depth-first search (DFS) is an algorithm for traversing graph data structures. It starts at a source node
/// and explores as far as possible along each branch before backtracking. /// and explores as far as possible along each branch before backtracking.
/// ///
@ -475,7 +510,7 @@ public class Graph: Encodable, Equatable {
stack.push(child) stack.push(child)
} }
} else if let frameworkNode = node as? FrameworkNode { } else if let frameworkNode = node as? FrameworkNode {
for child in frameworkNode.dependencies where !visited.contains(child) { for child in frameworkNode.dependencies.map(\.node) where !visited.contains(child) {
stack.push(child) stack.push(child)
} }
} }
@ -531,31 +566,60 @@ public class Graph: Encodable, Equatable {
} ?? nil } ?? nil
} }
/// - Returns: Host application for a given `targetNode`, if it exists
public func hostApplication(for targetNode: TargetNode) -> TargetNode? {
targetDependencies(path: targetNode.path, name: targetNode.name)
.first(where: { $0.target.product == .app })
}
/// Returns a copy of the graph with the given projects set. /// Returns a copy of the graph with the given projects set.
/// - Parameter projects: Projects to be set to the copy. /// - Parameter projects: Projects to be set to the copy.
public func with(projects: [Project]) -> Graph { public func with(projects: [Project]) -> Graph {
Graph(name: name, Graph(
name: name,
entryPath: entryPath, entryPath: entryPath,
entryNodes: entryNodes, entryNodes: entryNodes,
workspace: workspace,
projects: projects, projects: projects,
cocoapods: cocoapods, cocoapods: cocoapods,
packages: packages, packages: packages,
precompiled: precompiled, precompiled: precompiled,
targets: targets) targets: targets
)
} }
/// Returns a copy of the graph with the given targets. /// Returns a copy of the graph with the given targets.
/// - Parameter targets: Targets to be set to the copy. /// - Parameter targets: Targets to be set to the copy.
/// - Returns: New graph with the given targets. /// - Returns: New graph with the given targets.
public func with(targets: [AbsolutePath: [TargetNode]]) -> Graph { public func with(targets: [AbsolutePath: [TargetNode]]) -> Graph {
Graph(name: name, Graph(
name: name,
entryPath: entryPath, entryPath: entryPath,
entryNodes: entryNodes, entryNodes: entryNodes,
workspace: workspace,
projects: projects, projects: projects,
cocoapods: cocoapods, cocoapods: cocoapods,
packages: packages, packages: packages,
precompiled: precompiled, precompiled: precompiled,
targets: targets) targets: targets
)
}
/// Returns a copy of the graph with a given workspace.
/// - Parameter workspace: Workspace to be set to the copy.
/// - Returns: New graph with a given workspace.
public func with(workspace: Workspace) -> Graph {
Graph(
name: name,
entryPath: entryPath,
entryNodes: entryNodes,
workspace: workspace,
projects: projects,
cocoapods: cocoapods,
packages: packages,
precompiled: precompiled,
targets: targets
)
} }
public func forEach(closure: (GraphNode) -> Void) { public func forEach(closure: (GraphNode) -> Void) {
@ -587,8 +651,8 @@ public class Graph: Encodable, Equatable {
stack.push(child) stack.push(child)
} }
} else if let frameworkNode = node as? FrameworkNode { } else if let frameworkNode = node as? FrameworkNode {
for child in frameworkNode.dependencies where !visited.contains(child) { for child in frameworkNode.dependencies where !visited.contains(child.node) {
stack.push(child) stack.push(child.node)
} }
} }
} }
@ -600,11 +664,6 @@ public class Graph: Encodable, Equatable {
.product(target: targetNode.target.name, productName: targetNode.target.productNameWithExtension) .product(target: targetNode.target.name, productName: targetNode.target.productNameWithExtension)
} }
fileprivate func hostApplication(for targetNode: TargetNode) -> TargetNode? {
targetDependencies(path: targetNode.path, name: targetNode.name)
.first(where: { $0.target.product == .app })
}
fileprivate func isStaticLibrary(targetNode: TargetNode) -> Bool { fileprivate func isStaticLibrary(targetNode: TargetNode) -> Bool {
targetNode.target.product.isStatic targetNode.target.product.isStatic
} }

View File

@ -9,7 +9,7 @@ public protocol GraphLoading: AnyObject {
/// Loads the graph for the workspace in the given directory. /// Loads the graph for the workspace in the given directory.
/// - Parameter path: Path to the directory that contains the workspace. /// - Parameter path: Path to the directory that contains the workspace.
func loadWorkspace(path: AbsolutePath) throws -> (Graph, Workspace) func loadWorkspace(path: AbsolutePath) throws -> Graph
/// Loads the configuration. /// Loads the configuration.
/// ///
@ -64,15 +64,19 @@ public class GraphLoader: GraphLoading {
let entryNodes: [GraphNode] = try project.targets.map { target in let entryNodes: [GraphNode] = try project.targets.map { target in
try self.loadTarget(name: target.name, path: path, graphLoaderCache: graphLoaderCache, graphCircularDetector: graphCircularDetector) try self.loadTarget(name: target.name, path: path, graphLoaderCache: graphLoaderCache, graphCircularDetector: graphCircularDetector)
} }
let workspace = Workspace(path: project.path, name: project.name, projects: [project.path])
let graph = Graph(name: project.name, let graph = Graph(
name: project.name,
entryPath: path, entryPath: path,
cache: graphLoaderCache, cache: graphLoaderCache,
entryNodes: entryNodes) entryNodes: entryNodes,
workspace: workspace
)
return (graph, project) return (graph, project)
} }
public func loadWorkspace(path: AbsolutePath) throws -> (Graph, Workspace) { public func loadWorkspace(path: AbsolutePath) throws -> Graph {
let graphLoaderCache = GraphLoaderCache() let graphLoaderCache = GraphLoaderCache()
let graphCircularDetector = GraphCircularDetector() let graphCircularDetector = GraphCircularDetector()
let workspace = try modelLoader.loadWorkspace(at: path) let workspace = try modelLoader.loadWorkspace(at: path)
@ -92,11 +96,14 @@ public class GraphLoader: GraphLoading {
} }
} }
let graph = Graph(name: workspace.name, let graph = Graph(
name: workspace.name,
entryPath: path, entryPath: path,
cache: graphLoaderCache, cache: graphLoaderCache,
entryNodes: entryNodes) entryNodes: entryNodes,
return (graph, workspace) workspace: workspace
)
return graph
} }
public func loadConfig(path: AbsolutePath) throws -> Config { public func loadConfig(path: AbsolutePath) throws -> Config {

View File

@ -34,4 +34,8 @@ public final class GraphTraverser: GraphTraversing {
public func directStaticDependencies(path: AbsolutePath, name: String) -> [GraphDependencyReference] { public func directStaticDependencies(path: AbsolutePath, name: String) -> [GraphDependencyReference] {
graph.staticDependencies(path: path, name: name) graph.staticDependencies(path: path, name: name)
} }
public func appClipsDependency(path: AbsolutePath, name: String) -> Target? {
graph.appClipsDependency(path: path, name: name).map { $0.target }
}
} }

View File

@ -24,10 +24,16 @@ public class TargetNodeGraphMapper: GraphMapping {
map(node: $0, mappedCache: &mappedCache, cache: cache) map(node: $0, mappedCache: &mappedCache, cache: cache)
} }
return (Graph(name: graph.name, return (
Graph(
name: graph.name,
entryPath: graph.entryPath, entryPath: graph.entryPath,
cache: cache, cache: cache,
entryNodes: updatedNodes), []) entryNodes: updatedNodes,
workspace: graph.workspace
),
[]
)
} }
// MARK: - Private // MARK: - Private

View File

@ -0,0 +1,51 @@
import Foundation
public struct WorkspaceWithProjects {
public var workspace: Workspace
public var projects: [Project]
public init(workspace: Workspace, projects: [Project]) {
self.workspace = workspace
self.projects = projects
}
}
public protocol WorkspaceMapping {
func map(workspace: WorkspaceWithProjects) throws -> (WorkspaceWithProjects, [SideEffectDescriptor])
}
public final class SequentialWorkspaceMapper: WorkspaceMapping {
let mappers: [WorkspaceMapping]
public init(mappers: [WorkspaceMapping]) {
self.mappers = mappers
}
public func map(workspace: WorkspaceWithProjects) throws -> (WorkspaceWithProjects, [SideEffectDescriptor]) {
var results = (workspace: workspace, sideEffects: [SideEffectDescriptor]())
results = try mappers.reduce(into: results) { results, mapper in
let (updatedWorkspace, sideEffects) = try mapper.map(workspace: results.workspace)
results.workspace = updatedWorkspace
results.sideEffects.append(contentsOf: sideEffects)
}
return results
}
}
public final class ProjectWorkspaceMapper: WorkspaceMapping {
private let mapper: ProjectMapping
public init(mapper: ProjectMapping) {
self.mapper = mapper
}
public func map(workspace: WorkspaceWithProjects) throws -> (WorkspaceWithProjects, [SideEffectDescriptor]) {
var results = (projects: [Project](), sideEffects: [SideEffectDescriptor]())
results = try workspace.projects.reduce(into: results) { results, project in
let (updatedProject, sideEffects) = try mapper.map(project: project)
results.projects.append(updatedProject)
results.sideEffects.append(contentsOf: sideEffects)
}
let updatedWorkspace = WorkspaceWithProjects(workspace: workspace.workspace,
projects: results.projects)
return (updatedWorkspace, results.sideEffects)
}
}

View File

@ -15,9 +15,6 @@ public class FrameworkNode: PrecompiledNode {
/// The architectures supported by the binary. /// The architectures supported by the binary.
public let architectures: [BinaryArchitecture] public let architectures: [BinaryArchitecture]
/// Framework dependencies.
public let dependencies: [FrameworkNode]
/// Returns the type of product. /// Returns the type of product.
public var product: Product { public var product: Product {
if linking == .static { if linking == .static {
@ -42,14 +39,13 @@ public class FrameworkNode: PrecompiledNode {
bcsymbolmapPaths: [AbsolutePath], bcsymbolmapPaths: [AbsolutePath],
linking: BinaryLinking, linking: BinaryLinking,
architectures: [BinaryArchitecture] = [], architectures: [BinaryArchitecture] = [],
dependencies: [FrameworkNode] = []) dependencies: [Dependency] = [])
{ {
self.dsymPath = dsymPath self.dsymPath = dsymPath
self.bcsymbolmapPaths = bcsymbolmapPaths self.bcsymbolmapPaths = bcsymbolmapPaths
self.linking = linking self.linking = linking
self.architectures = architectures self.architectures = architectures
self.dependencies = dependencies super.init(path: path, dependencies: dependencies)
super.init(path: path)
} }
override public func encode(to encoder: Encoder) throws { override public func encode(to encoder: Encoder) throws {

View File

@ -2,7 +2,7 @@ import Foundation
import TSCBasic import TSCBasic
import TuistSupport import TuistSupport
public class GraphNode: Equatable, Hashable, Encodable, CustomStringConvertible { public class GraphNode: Equatable, Hashable, Encodable, CustomStringConvertible, CustomDebugStringConvertible {
// MARK: - Attributes // MARK: - Attributes
/// The path to the node. /// The path to the node.
@ -14,6 +14,9 @@ public class GraphNode: Equatable, Hashable, Encodable, CustomStringConvertible
/// The description of the node. /// The description of the node.
public var description: String { name } public var description: String { name }
/// The debug description of the node.
public var debugDescription: String { name }
// MARK: - Init // MARK: - Init
public init(path: AbsolutePath, name: String) { public init(path: AbsolutePath, name: String) {

View File

@ -3,11 +3,37 @@ import TSCBasic
import TuistSupport import TuistSupport
public class PrecompiledNode: GraphNode { public class PrecompiledNode: GraphNode {
public init(path: AbsolutePath) { /// It represents a dependency of a precompiled node, which can be either a framework, or another .xcframework.
public enum Dependency: Equatable, Hashable {
case framework(FrameworkNode)
case xcframework(XCFrameworkNode)
/// Path to the dependency.
public var path: AbsolutePath {
switch self {
case let .framework(framework): return framework.path
case let .xcframework(xcframework): return xcframework.path
}
}
/// Returns the node that represents the dependency.
public var node: PrecompiledNode {
switch self {
case let .framework(framework): return framework
case let .xcframework(xcframework): return xcframework
}
}
}
/// List of other precompiled artifacts this precompiled node depends on.
public private(set) var dependencies: [Dependency]
public init(path: AbsolutePath, dependencies: [Dependency] = []) {
/// Returns the name of the precompiled node removing the extension /// Returns the name of the precompiled node removing the extension
/// Alamofire.framework -> Alamofire /// Alamofire.framework -> Alamofire
/// libAlamofire.a -> libAlamofire /// libAlamofire.a -> libAlamofire
let name = String(path.components.last!.split(separator: ".").first!) let name = String(path.components.last!.split(separator: ".").first!)
self.dependencies = dependencies
super.init(path: path, name: name) super.init(path: path, name: name)
} }
@ -29,4 +55,25 @@ public class PrecompiledNode: GraphNode {
case product case product
case type case type
} }
/// Adds a new dependency to the xcframework node.
/// - Parameter dependency: Dependency to be added.
public func add(dependency: Dependency) {
dependencies.append(dependency)
}
// MARK: - CustomDebugStringConvertible
override public var debugDescription: String {
if dependencies.isEmpty {
return name
}
var dependenciesDescriptions: [String] = []
let uniqueDependencies = Set<Dependency>(dependencies)
uniqueDependencies.forEach { dependency in
dependenciesDescriptions.append(dependency.node.description)
}
return "\(name) --> [\(dependenciesDescriptions.joined(separator: ", "))]"
}
} }

View File

@ -51,6 +51,14 @@ public class SDKNode: GraphNode {
try! SDKNode(name: "XCTest.framework", platform: platform, status: status, source: .system) try! SDKNode(name: "XCTest.framework", platform: platform, status: status, source: .system)
} }
/// Creates an instace of SDKNode that represents the AppClip framework.
/// - Parameters:
/// - status: SDK status
/// - Returns: Initialized SDK node.
public static func appClip(status: SDKStatus) throws -> SDKNode {
try SDKNode(name: "AppClip.framework", platform: .iOS, status: status, source: .system)
}
static func path(name: String, platform: Platform, source _: SDKSource, type: Type) throws -> AbsolutePath { static func path(name: String, platform: Platform, source _: SDKSource, type: Type) throws -> AbsolutePath {
let sdkRootPath: AbsolutePath let sdkRootPath: AbsolutePath
if name == SDKNode.xctestFrameworkName { if name == SDKNode.xctestFrameworkName {

View File

@ -9,6 +9,9 @@ public class TargetNode: GraphNode {
public let target: Target public let target: Target
public var dependencies: [GraphNode] public var dependencies: [GraphNode]
/// When true it indicates that the target should be stripped from the graph when tree-shaking the project.
public var prune: Bool = false
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case path case path
case name case name
@ -23,11 +26,13 @@ public class TargetNode: GraphNode {
public init(project: Project, public init(project: Project,
target: Target, target: Target,
dependencies: [GraphNode]) dependencies: [GraphNode],
prune: Bool = false)
{ {
self.project = project self.project = project
self.target = target self.target = target
self.dependencies = dependencies self.dependencies = dependencies
self.prune = prune
super.init(path: project.path, name: target.name) super.init(path: project.path, name: target.name)
} }

View File

@ -3,28 +3,6 @@ import TSCBasic
import TuistSupport import TuistSupport
public class XCFrameworkNode: PrecompiledNode { public class XCFrameworkNode: PrecompiledNode {
/// It represents a dependency of an .xcframework which can be either a framework, or another .xcframework.
public enum Dependency: Equatable {
case framework(FrameworkNode)
case xcframework(XCFrameworkNode)
/// Path to the dependency.
public var path: AbsolutePath {
switch self {
case let .framework(framework): return framework.path
case let .xcframework(xcframework): return xcframework.path
}
}
/// Returns the node that represents the dependency.
public var node: PrecompiledNode {
switch self {
case let .framework(framework): return framework
case let .xcframework(xcframework): return xcframework
}
}
}
/// Coding keys. /// Coding keys.
enum XCFrameworkNodeCodingKeys: String, CodingKey { enum XCFrameworkNodeCodingKeys: String, CodingKey {
case linking case linking
@ -43,9 +21,6 @@ public class XCFrameworkNode: PrecompiledNode {
/// Returns the type of linking /// Returns the type of linking
public let linking: BinaryLinking public let linking: BinaryLinking
/// List of other .xcframeworks this xcframework depends on.
public private(set) var dependencies: [Dependency]
/// Path to the binary. /// Path to the binary.
override public var binaryPath: AbsolutePath { primaryBinaryPath } override public var binaryPath: AbsolutePath { primaryBinaryPath }
@ -65,8 +40,7 @@ public class XCFrameworkNode: PrecompiledNode {
self.infoPlist = infoPlist self.infoPlist = infoPlist
self.linking = linking self.linking = linking
self.primaryBinaryPath = primaryBinaryPath self.primaryBinaryPath = primaryBinaryPath
self.dependencies = dependencies super.init(path: path, dependencies: dependencies)
super.init(path: path)
} }
override public func encode(to encoder: Encoder) throws { override public func encode(to encoder: Encoder) throws {
@ -77,10 +51,4 @@ public class XCFrameworkNode: PrecompiledNode {
try container.encode("xcframework", forKey: .type) try container.encode("xcframework", forKey: .type)
try container.encode(infoPlist, forKey: .infoPlist) try container.encode(infoPlist, forKey: .infoPlist)
} }
/// Adds a new dependency to the xcframework node.
/// - Parameter dependency: Dependency to be added.
public func add(dependency: Dependency) {
dependencies.append(dependency)
}
} }

View File

@ -41,4 +41,10 @@ public protocol GraphTraversing {
/// - path: Path to the directory where the project that defines the target is located. /// - path: Path to the directory where the project that defines the target is located.
/// - name: Name of the target. /// - name: Name of the target.
func directStaticDependencies(path: AbsolutePath, name: String) -> [GraphDependencyReference] func directStaticDependencies(path: AbsolutePath, name: String) -> [GraphDependencyReference]
/// Given a project directory and a target name, it returns an appClips dependency.
/// - Parameters:
/// - path: Path to the directory that contains the project.
/// - name: Target name.
func appClipsDependency(path: AbsolutePath, name: String) -> Target?
} }

View File

@ -2,7 +2,7 @@ import Foundation
import TSCBasic import TSCBasic
import TuistSupport import TuistSupport
protocol FrameworkMetadataProviding: PrecompiledMetadataProviding { public protocol FrameworkMetadataProviding: PrecompiledMetadataProviding {
/// Given the path to a framework, it returns the path to its dSYMs if they exist /// Given the path to a framework, it returns the path to its dSYMs if they exist
/// in the same framework directory. /// in the same framework directory.
/// - Parameter frameworkPath: Path to the .framework directory. /// - Parameter frameworkPath: Path to the .framework directory.
@ -18,14 +18,14 @@ protocol FrameworkMetadataProviding: PrecompiledMetadataProviding {
func product(frameworkPath: AbsolutePath) throws -> Product func product(frameworkPath: AbsolutePath) throws -> Product
} }
final class FrameworkMetadataProvider: PrecompiledMetadataProvider, FrameworkMetadataProviding { public final class FrameworkMetadataProvider: PrecompiledMetadataProvider, FrameworkMetadataProviding {
func dsymPath(frameworkPath: AbsolutePath) -> AbsolutePath? { public func dsymPath(frameworkPath: AbsolutePath) -> AbsolutePath? {
let path = AbsolutePath("\(frameworkPath.pathString).dSYM") let path = AbsolutePath("\(frameworkPath.pathString).dSYM")
if FileHandler.shared.exists(path) { return path } if FileHandler.shared.exists(path) { return path }
return nil return nil
} }
func bcsymbolmapPaths(frameworkPath: AbsolutePath) throws -> [AbsolutePath] { public func bcsymbolmapPaths(frameworkPath: AbsolutePath) throws -> [AbsolutePath] {
let binaryPath = FrameworkNode.binaryPath(frameworkPath: frameworkPath) let binaryPath = FrameworkNode.binaryPath(frameworkPath: frameworkPath)
let uuids = try self.uuids(binaryPath: binaryPath) let uuids = try self.uuids(binaryPath: binaryPath)
return uuids return uuids
@ -34,7 +34,7 @@ final class FrameworkMetadataProvider: PrecompiledMetadataProvider, FrameworkMet
.sorted() .sorted()
} }
func product(frameworkPath: AbsolutePath) throws -> Product { public func product(frameworkPath: AbsolutePath) throws -> Product {
let binaryPath = FrameworkNode.binaryPath(frameworkPath: frameworkPath) let binaryPath = FrameworkNode.binaryPath(frameworkPath: frameworkPath)
switch try linking(binaryPath: binaryPath) { switch try linking(binaryPath: binaryPath) {
case .dynamic: case .dynamic:

View File

@ -31,7 +31,7 @@ enum PrecompiledMetadataProviderError: FatalError, Equatable {
} }
} }
protocol PrecompiledMetadataProviding { public protocol PrecompiledMetadataProviding {
/// It returns the supported architectures of the binary at the given path. /// It returns the supported architectures of the binary at the given path.
/// - Parameter binaryPath: Binary path. /// - Parameter binaryPath: Binary path.
func architectures(binaryPath: AbsolutePath) throws -> [BinaryArchitecture] func architectures(binaryPath: AbsolutePath) throws -> [BinaryArchitecture]
@ -46,10 +46,10 @@ protocol PrecompiledMetadataProviding {
func uuids(binaryPath: AbsolutePath) throws -> Set<UUID> func uuids(binaryPath: AbsolutePath) throws -> Set<UUID>
} }
class PrecompiledMetadataProvider: PrecompiledMetadataProviding { public class PrecompiledMetadataProvider: PrecompiledMetadataProviding {
public init() {} public init() {}
func architectures(binaryPath: AbsolutePath) throws -> [BinaryArchitecture] { public func architectures(binaryPath: AbsolutePath) throws -> [BinaryArchitecture] {
let result = try System.shared.capture("/usr/bin/lipo", "-info", binaryPath.pathString).spm_chuzzle() ?? "" let result = try System.shared.capture("/usr/bin/lipo", "-info", binaryPath.pathString).spm_chuzzle() ?? ""
let regexes = [ let regexes = [
// Non-fat file: path is architecture: x86_64 // Non-fat file: path is architecture: x86_64
@ -70,12 +70,12 @@ class PrecompiledMetadataProvider: PrecompiledMetadataProviding {
return architectures return architectures
} }
func linking(binaryPath: AbsolutePath) throws -> BinaryLinking { public func linking(binaryPath: AbsolutePath) throws -> BinaryLinking {
let result = try System.shared.capture("/usr/bin/file", binaryPath.pathString).spm_chuzzle() ?? "" let result = try System.shared.capture("/usr/bin/file", binaryPath.pathString).spm_chuzzle() ?? ""
return result.contains("dynamically linked") ? .dynamic : .static return result.contains("dynamically linked") ? .dynamic : .static
} }
func uuids(binaryPath: AbsolutePath) throws -> Set<UUID> { public func uuids(binaryPath: AbsolutePath) throws -> Set<UUID> {
let output = try System.shared.capture(["/usr/bin/xcrun", "dwarfdump", "--uuid", binaryPath.pathString]) let output = try System.shared.capture(["/usr/bin/xcrun", "dwarfdump", "--uuid", binaryPath.pathString])
// UUIDs are letters, decimals, or hyphens. // UUIDs are letters, decimals, or hyphens.
var uuidCharacterSet = CharacterSet() var uuidCharacterSet = CharacterSet()

View File

@ -13,3 +13,11 @@ public enum BinaryArchitecture: String, Codable {
public enum BinaryLinking: String, Codable { public enum BinaryLinking: String, Codable {
case `static`, dynamic case `static`, dynamic
} }
public extension Sequence where Element == BinaryArchitecture {
/// Returns true if all the architectures are only for simulator.
var onlySimulator: Bool {
let simulatorArchitectures: [BinaryArchitecture] = [.x8664, .i386]
return allSatisfy { simulatorArchitectures.contains($0) }
}
}

View File

@ -10,8 +10,10 @@ public struct Config: Equatable, Hashable {
public enum GenerationOption: Hashable, Equatable { public enum GenerationOption: Hashable, Equatable {
case xcodeProjectName(String) case xcodeProjectName(String)
case organizationName(String) case organizationName(String)
case developmentRegion(String)
case disableAutogeneratedSchemes case disableAutogeneratedSchemes
case disableSynthesizedResourceAccessors case disableSynthesizedResourceAccessors
case disableShowEnvironmentVarsInScriptPhases
} }
/// Generation options. /// Generation options.
@ -23,9 +25,12 @@ public struct Config: Equatable, Hashable {
/// Cloud configuration. /// Cloud configuration.
public let cloud: Cloud? public let cloud: Cloud?
/// The path of the config file.
public let path: AbsolutePath?
/// Returns the default Tuist configuration. /// Returns the default Tuist configuration.
public static var `default`: Config { public static var `default`: Config {
Config(compatibleXcodeVersions: .all, cloud: nil, generationOptions: []) Config(compatibleXcodeVersions: .all, cloud: nil, generationOptions: [], path: nil)
} }
/// Initializes the tuist cofiguration. /// Initializes the tuist cofiguration.
@ -34,13 +39,16 @@ public struct Config: Equatable, Hashable {
/// - compatibleXcodeVersions: List of Xcode versions the project or set of projects is compatible with. /// - compatibleXcodeVersions: List of Xcode versions the project or set of projects is compatible with.
/// - cloud: Cloud configuration. /// - cloud: Cloud configuration.
/// - generationOptions: Generation options. /// - generationOptions: Generation options.
/// - path: The path of the config file.
public init(compatibleXcodeVersions: CompatibleXcodeVersions, public init(compatibleXcodeVersions: CompatibleXcodeVersions,
cloud: Cloud?, cloud: Cloud?,
generationOptions: [GenerationOption]) generationOptions: [GenerationOption],
path: AbsolutePath?)
{ {
self.compatibleXcodeVersions = compatibleXcodeVersions self.compatibleXcodeVersions = compatibleXcodeVersions
self.cloud = cloud self.cloud = cloud
self.generationOptions = generationOptions self.generationOptions = generationOptions
self.path = path
} }
// MARK: - Hashable // MARK: - Hashable

View File

@ -7,15 +7,18 @@ public struct ExecutionAction: Equatable {
public let title: String public let title: String
public let scriptText: String public let scriptText: String
public let target: TargetReference? public let target: TargetReference?
public let showEnvVarsInLog: Bool
// MARK: - Init // MARK: - Init
public init(title: String, public init(title: String,
scriptText: String, scriptText: String,
target: TargetReference?) target: TargetReference?,
showEnvVarsInLog: Bool = true)
{ {
self.title = title self.title = title
self.scriptText = scriptText self.scriptText = scriptText
self.target = target self.target = target
self.showEnvVarsInLog = showEnvVarsInLog
} }
} }

View File

@ -19,6 +19,7 @@ public enum Product: String, CustomStringConvertible, CaseIterable, Encodable {
// case messagesApplication = "messages_application" // case messagesApplication = "messages_application"
case messagesExtension = "messages_extension" case messagesExtension = "messages_extension"
case stickerPackExtension = "sticker_pack_extension" case stickerPackExtension = "sticker_pack_extension"
case appClip
public var caseValue: String { public var caseValue: String {
switch self { switch self {
@ -56,6 +57,8 @@ public enum Product: String, CustomStringConvertible, CaseIterable, Encodable {
return "messagesExtension" return "messagesExtension"
case .stickerPackExtension: case .stickerPackExtension:
return "stickerPackExtension" return "stickerPackExtension"
case .appClip:
return "appClip"
} }
} }
@ -95,13 +98,15 @@ public enum Product: String, CustomStringConvertible, CaseIterable, Encodable {
return "iMessage extension" return "iMessage extension"
case .stickerPackExtension: case .stickerPackExtension:
return "sticker pack extension" return "sticker pack extension"
case .appClip:
return "appClip"
} }
} }
/// Returns true if the target can be ran. /// Returns true if the target can be ran.
public var runnable: Bool { public var runnable: Bool {
switch self { switch self {
case .app: case .app, .appClip:
return true return true
default: default:
return false return false
@ -126,6 +131,7 @@ public enum Product: String, CustomStringConvertible, CaseIterable, Encodable {
base.append(.stickerPackExtension) base.append(.stickerPackExtension)
// base.append(.messagesApplication) // base.append(.messagesApplication)
base.append(.messagesExtension) base.append(.messagesExtension)
base.append(.appClip)
} }
if platform == .tvOS { if platform == .tvOS {
@ -195,6 +201,12 @@ public enum Product: String, CustomStringConvertible, CaseIterable, Encodable {
return .messagesExtension return .messagesExtension
case .stickerPackExtension: case .stickerPackExtension:
return .stickerPack return .stickerPack
case .appClip:
return .onDemandInstallCapableApplication
} }
} }
public func canHostTests() -> Bool {
[.app, .appClip].contains(self)
}
} }

View File

@ -9,6 +9,7 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug
lhs.xcodeProjPath == rhs.xcodeProjPath && lhs.xcodeProjPath == rhs.xcodeProjPath &&
lhs.name == rhs.name && lhs.name == rhs.name &&
lhs.organizationName == rhs.organizationName && lhs.organizationName == rhs.organizationName &&
lhs.developmentRegion == rhs.developmentRegion &&
lhs.targets == rhs.targets && lhs.targets == rhs.targets &&
lhs.packages == rhs.packages && lhs.packages == rhs.packages &&
lhs.schemes == rhs.schemes && lhs.schemes == rhs.schemes &&
@ -34,6 +35,9 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug
/// Organization name. /// Organization name.
public var organizationName: String? public var organizationName: String?
/// Development region code e.g. `en`.
public var developmentRegion: String?
/// Project targets. /// Project targets.
public var targets: [Target] public var targets: [Target]
@ -62,6 +66,7 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug
/// - xcodeProjPath: Path to the Xcode project that will be generated. /// - xcodeProjPath: Path to the Xcode project that will be generated.
/// - name: Project name. /// - name: Project name.
/// - organizationName: Organization name. /// - organizationName: Organization name.
/// - developmentRegion: Development region.
/// - settings: The settings to apply at the project level /// - settings: The settings to apply at the project level
/// - filesGroup: The root group to place project files within /// - filesGroup: The root group to place project files within
/// - targets: The project targets /// - targets: The project targets
@ -72,6 +77,7 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug
xcodeProjPath: AbsolutePath, xcodeProjPath: AbsolutePath,
name: String, name: String,
organizationName: String?, organizationName: String?,
developmentRegion: String?,
settings: Settings, settings: Settings,
filesGroup: ProjectGroup, filesGroup: ProjectGroup,
targets: [Target], targets: [Target],
@ -84,6 +90,7 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug
self.xcodeProjPath = xcodeProjPath self.xcodeProjPath = xcodeProjPath
self.name = name self.name = name
self.organizationName = organizationName self.organizationName = organizationName
self.developmentRegion = developmentRegion
self.targets = targets self.targets = targets
self.packages = packages self.packages = packages
self.schemes = schemes self.schemes = schemes
@ -155,6 +162,7 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug
xcodeProjPath: xcodeProjPath, xcodeProjPath: xcodeProjPath,
name: name, name: name,
organizationName: organizationName, organizationName: organizationName,
developmentRegion: developmentRegion,
settings: settings, settings: settings,
filesGroup: filesGroup, filesGroup: filesGroup,
targets: targets, targets: targets,
@ -171,6 +179,7 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug
xcodeProjPath: xcodeProjPath, xcodeProjPath: xcodeProjPath,
name: name, name: name,
organizationName: organizationName, organizationName: organizationName,
developmentRegion: developmentRegion,
settings: settings, settings: settings,
filesGroup: filesGroup, filesGroup: filesGroup,
targets: targets, targets: targets,

View File

@ -47,6 +47,7 @@ public struct Target: Equatable, Hashable, Comparable {
public var environment: [String: String] public var environment: [String: String]
public var launchArguments: [String: Bool] public var launchArguments: [String: Bool]
public var filesGroup: ProjectGroup public var filesGroup: ProjectGroup
public var scripts: [TargetScript]
// MARK: - Init // MARK: - Init
@ -67,7 +68,8 @@ public struct Target: Equatable, Hashable, Comparable {
environment: [String: String] = [:], environment: [String: String] = [:],
launchArguments: [String: Bool] = [:], launchArguments: [String: Bool] = [:],
filesGroup: ProjectGroup, filesGroup: ProjectGroup,
dependencies: [Dependency] = []) dependencies: [Dependency] = [],
scripts: [TargetScript] = [])
{ {
self.name = name self.name = name
self.product = product self.product = product
@ -87,6 +89,7 @@ public struct Target: Equatable, Hashable, Comparable {
self.launchArguments = launchArguments self.launchArguments = launchArguments
self.filesGroup = filesGroup self.filesGroup = filesGroup
self.dependencies = dependencies self.dependencies = dependencies
self.scripts = scripts
} }
/// Target can be included in the link phase of other targets /// Target can be included in the link phase of other targets
@ -116,6 +119,17 @@ public struct Target: Equatable, Hashable, Comparable {
].contains(product) ].contains(product)
} }
/// It returns the name of the variable that should be used to create an empty file
/// in the $BUILT_PRODUCTS_DIR directory that is used after builds to reliably locate the
/// directories where the products have been exported into.
public var targetLocatorBuildPhaseVariable: String {
let upperCasedSnakeCasedProductName = productName
.camelCaseToSnakeCase()
.components(separatedBy: .whitespaces).joined(separator: "_")
.uppercased()
return "\(upperCasedSnakeCasedProductName)_LOCATE_HASH"
}
/// Returns the product name including the extension. /// Returns the product name including the extension.
public var productNameWithExtension: String { public var productNameWithExtension: String {
switch product { switch product {
@ -126,6 +140,16 @@ public struct Target: Equatable, Hashable, Comparable {
} }
} }
/// Returns true if the target supports having a headers build phase..
public var shouldIncludeHeadersBuildPhase: Bool {
switch product {
case .framework, .staticFramework, .staticLibrary, .dynamicLibrary:
return true
default:
return false
}
}
/// Returns true if the target supports having sources. /// Returns true if the target supports having sources.
public var supportsSources: Bool { public var supportsSources: Bool {
switch (platform, product) { switch (platform, product) {
@ -146,6 +170,14 @@ public struct Target: Equatable, Hashable, Comparable {
} }
} }
/// Returns true if the target is an AppClip
public var isAppClip: Bool {
if case .appClip = product {
return true
}
return false
}
/// Returns true if the file at the given path is a resource. /// Returns true if the file at the given path is a resource.
/// - Parameter path: Path to the file to be checked. /// - Parameter path: Path to the file to be checked.
public static func isResource(path: AbsolutePath) -> Bool { public static func isResource(path: AbsolutePath) -> Bool {
@ -263,8 +295,8 @@ extension Sequence where Element == Target {
filter { $0.product.testsBundle } filter { $0.product.testsBundle }
} }
/// Filters and returns only the targets that are apps. /// Filters and returns only the targets that are apps and app clips.
var apps: [Target] { var apps: [Target] {
filter { $0.product == .app } filter { $0.product == .app || $0.product == .appClip }
} }
} }

View File

@ -40,6 +40,12 @@ public struct TargetAction: Equatable {
/// List of output filelist paths /// List of output filelist paths
public let outputFileListPaths: [AbsolutePath] public let outputFileListPaths: [AbsolutePath]
/// Show environment variables in the logs
public var showEnvVarsInLog: Bool
/// Whether to skip running this script in incremental builds, if nothing has changed
public let basedOnDependencyAnalysis: Bool?
/// Initializes a new target action with its attributes. /// Initializes a new target action with its attributes.
/// ///
/// - Parameters: /// - Parameters:
@ -52,6 +58,8 @@ public struct TargetAction: Equatable {
/// - inputFileListPaths: List of input filelist paths /// - inputFileListPaths: List of input filelist paths
/// - outputPaths: List of output file paths /// - outputPaths: List of output file paths
/// - outputFileListPaths: List of output filelist paths /// - outputFileListPaths: List of output filelist paths
/// - showEnvVarsInLog: Show environment variables in the logs
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
public init(name: String, public init(name: String,
order: Order, order: Order,
tool: String? = nil, tool: String? = nil,
@ -60,7 +68,9 @@ public struct TargetAction: Equatable {
inputPaths: [AbsolutePath] = [], inputPaths: [AbsolutePath] = [],
inputFileListPaths: [AbsolutePath] = [], inputFileListPaths: [AbsolutePath] = [],
outputPaths: [AbsolutePath] = [], outputPaths: [AbsolutePath] = [],
outputFileListPaths: [AbsolutePath] = []) outputFileListPaths: [AbsolutePath] = [],
showEnvVarsInLog: Bool = true,
basedOnDependencyAnalysis: Bool? = nil)
{ {
self.name = name self.name = name
self.order = order self.order = order
@ -71,6 +81,8 @@ public struct TargetAction: Equatable {
self.inputFileListPaths = inputFileListPaths self.inputFileListPaths = inputFileListPaths
self.outputPaths = outputPaths self.outputPaths = outputPaths
self.outputFileListPaths = outputFileListPaths self.outputFileListPaths = outputFileListPaths
self.showEnvVarsInLog = showEnvVarsInLog
self.basedOnDependencyAnalysis = basedOnDependencyAnalysis
} }
/// Returns the shell script that should be used in the target build phase. /// Returns the shell script that should be used in the target build phase.

View File

@ -0,0 +1,34 @@
import Foundation
import TSCBasic
import TuistSupport
/// It represents a target build phase.
public struct TargetScript: Equatable {
/// The name of the build phase.
public let name: String
/// Script.
public let script: String
/// Whether we want the build phase to show the environment variables in the logs.
public let showEnvVarsInLog: Bool
/// Whether the script should be hashed for caching purposes.
public let hashable: Bool
/// Initializes the target script.
/// - Parameter name: The name of the build phase.
/// - Parameter script: Script.
/// - Parameter showEnvVarsInLog: Whether we want the build phase to show the environment variables in the logs.
/// - Parameter hashable: Whether the script should be hashed for caching purposes.
public init(name: String,
script: String,
showEnvVarsInLog: Bool,
hashable: Bool)
{
self.name = name
self.script = script
self.showEnvVarsInLog = showEnvVarsInLog
self.hashable = hashable
}
}

View File

@ -1,7 +1,7 @@
import Foundation import Foundation
import TSCBasic import TSCBasic
public struct TestableTarget: Equatable { public struct TestableTarget: Equatable, Hashable {
public let target: TargetReference public let target: TargetReference
public let isSkipped: Bool public let isSkipped: Bool
public let isParallelizable: Bool public let isParallelizable: Bool

View File

@ -5,11 +5,11 @@ import TuistSupport
public struct Workspace: Equatable { public struct Workspace: Equatable {
// MARK: - Attributes // MARK: - Attributes
public let path: AbsolutePath public var path: AbsolutePath
public let name: String public var name: String
public let projects: [AbsolutePath] public var projects: [AbsolutePath]
public let schemes: [Scheme] public var schemes: [Scheme]
public let additionalFiles: [FileElement] public var additionalFiles: [FileElement]
// MARK: - Init // MARK: - Init
@ -23,6 +23,12 @@ public struct Workspace: Equatable {
} }
extension Workspace { extension Workspace {
public func with(name: String) -> Workspace {
var copy = self
copy.name = name
return copy
}
public func adding(files: [AbsolutePath]) -> Workspace { public func adding(files: [AbsolutePath]) -> Workspace {
Workspace(path: path, Workspace(path: path,
name: name, name: name,

View File

@ -28,17 +28,17 @@ public protocol FrameworkNodeLoading {
func load(path: AbsolutePath) throws -> FrameworkNode func load(path: AbsolutePath) throws -> FrameworkNode
} }
final class FrameworkNodeLoader: FrameworkNodeLoading { public final class FrameworkNodeLoader: FrameworkNodeLoading {
/// Framework metadata provider. /// Framework metadata provider.
fileprivate let frameworkMetadataProvider: FrameworkMetadataProviding fileprivate let frameworkMetadataProvider: FrameworkMetadataProviding
/// Initializes the loader with its attributes. /// Initializes the loader with its attributes.
/// - Parameter frameworkMetadataProvider: Framework metadata provider. /// - Parameter frameworkMetadataProvider: Framework metadata provider.
init(frameworkMetadataProvider: FrameworkMetadataProviding = FrameworkMetadataProvider()) { public init(frameworkMetadataProvider: FrameworkMetadataProviding = FrameworkMetadataProvider()) {
self.frameworkMetadataProvider = frameworkMetadataProvider self.frameworkMetadataProvider = frameworkMetadataProvider
} }
func load(path: AbsolutePath) throws -> FrameworkNode { public func load(path: AbsolutePath) throws -> FrameworkNode {
guard FileHandler.shared.exists(path) else { guard FileHandler.shared.exists(path) else {
throw FrameworkNodeLoaderError.frameworkNotFound(path) throw FrameworkNodeLoaderError.frameworkNotFound(path)
} }

View File

@ -1,38 +1,62 @@
import Foundation import Foundation
import RxSwift import RxSwift
import struct TSCUtility.Version
import TuistSupport import TuistSupport
protocol SimulatorControlling { public protocol SimulatorControlling {
/// Returns the list of simulator devices that are available in the system. /// Returns the list of simulator devices that are available in the system.
func devices() -> Single<[SimulatorDevice]> func devices() -> Single<[SimulatorDevice]>
/// Returns the list of simulator runtimes that are available in the system. /// Returns the list of simulator runtimes that are available in the system.
func runtimes() -> Single<[SimulatorRuntime]> func runtimes() -> Single<[SimulatorRuntime]>
/// Returns the list of simulator devices and runtimes. /// - Parameters:
/// - platform: Optionally filter by platform
/// - deviceName: Optionally filter by device name
/// - Returns: the list of simulator devices and runtimes.
func devicesAndRuntimes() -> Single<[SimulatorDeviceAndRuntime]> func devicesAndRuntimes() -> Single<[SimulatorDeviceAndRuntime]>
/// Finds first available device defined by given parameters
/// - Parameters:
/// - platform: Given platform
/// - version: Specific version, ignored if nil
/// - minVersion: Minimum version of the OS
/// - deviceName: Specific device name (eg. iPhone X)
func findAvailableDevice(
platform: Platform,
version: Version?,
minVersion: Version?,
deviceName: String?
) -> Single<SimulatorDevice>
} }
enum SimulatorControllerError: FatalError { public enum SimulatorControllerError: FatalError {
case simctlError(String) case simctlError(String)
case deviceNotFound(Platform, Version?, String?, [SimulatorDeviceAndRuntime])
var type: ErrorType { public var type: ErrorType {
switch self { switch self {
case .simctlError: return .abort case .simctlError, .deviceNotFound:
return .abort
} }
} }
var description: String { public var description: String {
switch self { switch self {
case let .simctlError(output): return output case let .simctlError(output):
return output
case let .deviceNotFound(platform, version, deviceName, devices):
return "Could not find a suitable device for \(platform.caseValue)\(version.map { " \($0)" } ?? "")\(deviceName.map { ", device name \($0)" } ?? ""). Did find \(devices.map { "\($0.device.name) (\($0.runtime.description))" }.joined(separator: ", "))"
} }
} }
} }
final class SimulatorController: SimulatorControlling { public final class SimulatorController: SimulatorControlling {
private let jsonDecoder: JSONDecoder = JSONDecoder() private let jsonDecoder = JSONDecoder()
func devices() -> Single<[SimulatorDevice]> { public init() {}
public func devices() -> Single<[SimulatorDevice]> {
System.shared.observable(["/usr/bin/xcrun", "simctl", "list", "devices", "--json"]) System.shared.observable(["/usr/bin/xcrun", "simctl", "list", "devices", "--json"])
.mapToString() .mapToString()
.collectOutput() .collectOutput()
@ -63,9 +87,8 @@ final class SimulatorController: SimulatorControlling {
} }
} }
func runtimes() -> Single<[SimulatorRuntime]> { public func runtimes() -> Single<[SimulatorRuntime]> {
System.shared.observable(["/usr/bin/xcrun", "simctl", "list", "runtimes", "--json"]) System.shared.observable(["/usr/bin/xcrun", "simctl", "list", "runtimes", "--json"])
.debug()
.mapToString() .mapToString()
.collectOutput() .collectOutput()
.asSingle() .asSingle()
@ -88,7 +111,7 @@ final class SimulatorController: SimulatorControlling {
} }
} }
func devicesAndRuntimes() -> Single<[SimulatorDeviceAndRuntime]> { public func devicesAndRuntimes() -> Single<[SimulatorDeviceAndRuntime]> {
runtimes() runtimes()
.flatMap { (runtimes) -> Single<([SimulatorDevice], [SimulatorRuntime])> in .flatMap { (runtimes) -> Single<([SimulatorDevice], [SimulatorRuntime])> in
self.devices().map { ($0, runtimes) } self.devices().map { ($0, runtimes) }
@ -100,4 +123,36 @@ final class SimulatorController: SimulatorControlling {
} }
} }
} }
public func findAvailableDevice(
platform: Platform,
version: Version?,
minVersion: Version?,
deviceName: String?
) -> Single<SimulatorDevice> {
devicesAndRuntimes()
.flatMap { devicesAndRuntimes in
let availableDevices = devicesAndRuntimes
.filter { simulatorDeviceAndRuntime in
let nameComponents = simulatorDeviceAndRuntime.runtime.name.components(separatedBy: " ")
guard nameComponents.first == platform.caseValue else { return false }
let deviceVersion = nameComponents.last?.version()
if let version = version {
guard deviceVersion == version else { return false }
} else if let minVersion = minVersion, let deviceVersion = deviceVersion {
guard deviceVersion >= minVersion else { return false }
}
if let deviceName = deviceName {
guard simulatorDeviceAndRuntime.device.name == deviceName else { return false }
}
return true
}
.map(\.device)
guard
let device = availableDevices.first(where: { !$0.isShutdown }) ?? availableDevices.first
else { return .error(SimulatorControllerError.deviceNotFound(platform, version, deviceName, devicesAndRuntimes)) }
return .just(device)
}
}
} }

View File

@ -2,44 +2,45 @@ import Foundation
import TSCBasic import TSCBasic
/// It represents a simulator device. Devices are obtained using Xcode's CLI simctl /// It represents a simulator device. Devices are obtained using Xcode's CLI simctl
struct SimulatorDevice: Decodable, Hashable, CustomStringConvertible { public struct SimulatorDevice: Decodable, Hashable, CustomStringConvertible {
/// Device data path. /// Device data path.
let dataPath: AbsolutePath public let dataPath: AbsolutePath
/// Device log path. /// Device log path.
let logPath: AbsolutePath public let logPath: AbsolutePath
/// Device unique identifier (3A8C9673-C1FD-4E33-8EFA-AEEBF43161CC) /// Device unique identifier (3A8C9673-C1FD-4E33-8EFA-AEEBF43161CC)
let udid: String public let udid: String
/// Whether the device is available or not. /// Whether the device is available or not.
let isAvailable: Bool public let isAvailable: Bool
/// Device type identifier (e.g. com.apple.CoreSimulator.SimDeviceType.iPad-Air--3rd-generation-) /// Device type identifier (e.g. com.apple.CoreSimulator.SimDeviceType.iPad-Air--3rd-generation-)
let deviceTypeIdentifier: String? public let deviceTypeIdentifier: String?
/// Device state (e.g. Shutdown) /// Device state (e.g. Shutdown)
let state: String public let state: String
/// Returns true if the device is shutdown. /// Returns true if the device is shutdown.
var isShutdown: Bool { public var isShutdown: Bool {
state == "Shutdown" state == "Shutdown"
} }
/// Device name (e.g. iPad Air (3rd generation)) /// Device name (e.g. iPad Air (3rd generation))
let name: String public let name: String
/// If the device is not available, this provides a description of the error. /// If the device is not available, this provides a description of the error.
let availabilityError: String? public let availabilityError: String?
/// Device runtime identifier (e.g. com.apple.CoreSimulator.SimRuntime.iOS-13-5) /// Device runtime identifier (e.g. com.apple.CoreSimulator.SimRuntime.iOS-13-5)
let runtimeIdentifier: String public let runtimeIdentifier: String
var description: String { public var description: String {
name name
} }
public init(dataPath: AbsolutePath, public init(
dataPath: AbsolutePath,
logPath: AbsolutePath, logPath: AbsolutePath,
udid: String, udid: String,
isAvailable: Bool, isAvailable: Bool,
@ -47,8 +48,8 @@ struct SimulatorDevice: Decodable, Hashable, CustomStringConvertible {
state: String, state: String,
name: String, name: String,
availabilityError: String?, availabilityError: String?,
runtimeIdentifier: String) runtimeIdentifier: String
{ ) {
self.dataPath = dataPath self.dataPath = dataPath
self.logPath = logPath self.logPath = logPath
self.udid = udid self.udid = udid

View File

@ -0,0 +1,9 @@
import Foundation
public struct SimulatorDeviceAndRuntime: Hashable {
/// Device
public let device: SimulatorDevice
/// Device's runtime.
public let runtime: SimulatorRuntime
}

View File

@ -3,36 +3,37 @@ import TSCBasic
/// It represents a runtime that is available in the system. The list of available runtimes is obtained /// It represents a runtime that is available in the system. The list of available runtimes is obtained
/// using Xcode's simctl cli tool. /// using Xcode's simctl cli tool.
struct SimulatorRuntime: Decodable, Hashable, CustomStringConvertible { public struct SimulatorRuntime: Decodable, Hashable, CustomStringConvertible {
/// Runtime bundle path (e.g. /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime) /// Runtime bundle path (e.g. /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime)
let bundlePath: AbsolutePath public let bundlePath: AbsolutePath
/// Runtime build version (e.g. 17F61) /// Runtime build version (e.g. 17F61)
let buildVersion: String public let buildVersion: String
/// Runtime root (e.g. /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes\/iOS.simruntime\Contents/Resources/RuntimeRoot) /// Runtime root (e.g. /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes\/iOS.simruntime\Contents/Resources/RuntimeRoot)
let runtimeRoot: AbsolutePath public let runtimeRoot: AbsolutePath
/// Runtime identifier (e.g. com.apple.CoreSimulator.SimRuntime.iOS-13-5) /// Runtime identifier (e.g. com.apple.CoreSimulator.SimRuntime.iOS-13-5)
let identifier: String public let identifier: String
/// Runtime version (e.g. 13.5) /// Runtime version (e.g. 13.5)
let version: SimulatorRuntimeVersion public let version: SimulatorRuntimeVersion
// True if the runtime is available. // True if the runtime is available.
let isAvailable: Bool public let isAvailable: Bool
// Name of the runtime (e.g. iOS 13.5) // Name of the runtime (e.g. iOS 13.5)
let name: String public let name: String
init(bundlePath: AbsolutePath, public init(
bundlePath: AbsolutePath,
buildVersion: String, buildVersion: String,
runtimeRoot: AbsolutePath, runtimeRoot: AbsolutePath,
identifier: String, identifier: String,
version: SimulatorRuntimeVersion, version: SimulatorRuntimeVersion,
isAvailable: Bool, isAvailable: Bool,
name: String) name: String
{ ) {
self.bundlePath = bundlePath self.bundlePath = bundlePath
self.buildVersion = buildVersion self.buildVersion = buildVersion
self.runtimeRoot = runtimeRoot self.runtimeRoot = runtimeRoot
@ -52,7 +53,7 @@ struct SimulatorRuntime: Decodable, Hashable, CustomStringConvertible {
case name case name
} }
var description: String { public var description: String {
name name
} }
} }

View File

@ -1,11 +1,11 @@
import Foundation import Foundation
struct SimulatorRuntimeVersion: CustomStringConvertible, Hashable, ExpressibleByStringLiteral, Comparable, Decodable { public struct SimulatorRuntimeVersion: CustomStringConvertible, Hashable, ExpressibleByStringLiteral, Comparable, Decodable {
// MARK: - Attributes // MARK: - Attributes
let major: Int public let major: Int
let minor: Int? public let minor: Int?
let patch: Int? public let patch: Int?
// MARK: - Constructors // MARK: - Constructors
@ -15,7 +15,7 @@ struct SimulatorRuntimeVersion: CustomStringConvertible, Hashable, ExpressibleBy
self.patch = patch self.patch = patch
} }
init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer() let container = try decoder.singleValueContainer()
self.init(stringLiteral: try container.decode(String.self)) self.init(stringLiteral: try container.decode(String.self))
} }
@ -30,7 +30,7 @@ struct SimulatorRuntimeVersion: CustomStringConvertible, Hashable, ExpressibleBy
// MARK: - CustomStringConvertible // MARK: - CustomStringConvertible
var description: String { public var description: String {
var version = "\(major)" var version = "\(major)"
if let minor = minor { if let minor = minor {
version.append(".\(minor)") version.append(".\(minor)")
@ -47,7 +47,7 @@ struct SimulatorRuntimeVersion: CustomStringConvertible, Hashable, ExpressibleBy
// MARK: - Equatable // MARK: - Equatable
static func == (lhs: SimulatorRuntimeVersion, rhs: SimulatorRuntimeVersion) -> Bool { public static func == (lhs: SimulatorRuntimeVersion, rhs: SimulatorRuntimeVersion) -> Bool {
lhs.major == rhs.major && lhs.major == rhs.major &&
lhs.minor == rhs.minor && lhs.minor == rhs.minor &&
lhs.patch == rhs.patch lhs.patch == rhs.patch
@ -55,7 +55,7 @@ struct SimulatorRuntimeVersion: CustomStringConvertible, Hashable, ExpressibleBy
// MARK: - Comparable // MARK: - Comparable
static func < (lhs: SimulatorRuntimeVersion, rhs: SimulatorRuntimeVersion) -> Bool { public static func < (lhs: SimulatorRuntimeVersion, rhs: SimulatorRuntimeVersion) -> Bool {
let lhs = lhs.flattened() let lhs = lhs.flattened()
let rhs = rhs.flattened() let rhs = rhs.flattened()
@ -76,7 +76,7 @@ struct SimulatorRuntimeVersion: CustomStringConvertible, Hashable, ExpressibleBy
// MARK: - ExpressibleByStringLiteral // MARK: - ExpressibleByStringLiteral
init(stringLiteral value: String) { public init(stringLiteral value: String) {
let components = value.split(separator: ".") let components = value.split(separator: ".")
// Major // Major

View File

@ -72,7 +72,7 @@ public struct ValueGraph: Equatable {
if let targetNode = node as? TargetNode { if let targetNode = node as? TargetNode {
targetNode.dependencies.forEach { nodeDependencies.formUnion([self.dependency(from: $0)]) } targetNode.dependencies.forEach { nodeDependencies.formUnion([self.dependency(from: $0)]) }
} else if let frameworkNode = node as? FrameworkNode { } else if let frameworkNode = node as? FrameworkNode {
frameworkNode.dependencies.forEach { nodeDependencies.formUnion([self.dependency(from: $0)]) } frameworkNode.dependencies.forEach { nodeDependencies.formUnion([self.dependency(from: $0.node)]) }
} else if let xcframeworkNode = node as? XCFrameworkNode { } else if let xcframeworkNode = node as? XCFrameworkNode {
xcframeworkNode.dependencies.forEach { nodeDependencies.formUnion([self.dependency(from: $0.node)]) } xcframeworkNode.dependencies.forEach { nodeDependencies.formUnion([self.dependency(from: $0.node)]) }
} }

View File

@ -71,6 +71,11 @@ public class ValueGraphTraverser: GraphTraversing {
.sorted() .sorted()
} }
public func appClipsDependency(path: AbsolutePath, name: String) -> Target? {
directTargetDependencies(path: path, name: name)
.first { $0.product == .appClip }
}
public func directStaticDependencies(path: AbsolutePath, name: String) -> [GraphDependencyReference] { public func directStaticDependencies(path: AbsolutePath, name: String) -> [GraphDependencyReference] {
graph.dependencies[.target(name: name, path: path)]? graph.dependencies[.target(name: name, path: path)]?
.compactMap { (dependency: ValueGraphDependency) -> (path: AbsolutePath, name: String)? in .compactMap { (dependency: ValueGraphDependency) -> (path: AbsolutePath, name: String)? in

View File

@ -19,6 +19,21 @@ final class MockXcodeBuildController: XcodeBuildControlling {
} }
} }
var testStub: ((XcodeBuildTarget, String, Bool, XcodeBuildDestination, [XcodeBuildArgument]) -> Observable<SystemEvent<XcodeBuildOutput>>)?
func test(
_ target: XcodeBuildTarget,
scheme: String,
clean: Bool,
destination: XcodeBuildDestination,
arguments: [XcodeBuildArgument]
) -> Observable<SystemEvent<XcodeBuildOutput>> {
if let testStub = testStub {
return testStub(target, scheme, clean, destination, arguments)
} else {
return Observable.error(TestError("\(String(describing: MockXcodeBuildController.self)) received an unexpected call to test"))
}
}
var archiveStub: ((XcodeBuildTarget, String, Bool, AbsolutePath, [XcodeBuildArgument]) -> Observable<SystemEvent<XcodeBuildOutput>>)? var archiveStub: ((XcodeBuildTarget, String, Bool, AbsolutePath, [XcodeBuildArgument]) -> Observable<SystemEvent<XcodeBuildOutput>>)?
func archive(_ target: XcodeBuildTarget, func archive(_ target: XcodeBuildTarget,
scheme: String, scheme: String,

View File

@ -9,7 +9,7 @@ public extension FrameworkNode {
bcsymbolmapPaths: [AbsolutePath] = [], bcsymbolmapPaths: [AbsolutePath] = [],
linking: BinaryLinking = .dynamic, linking: BinaryLinking = .dynamic,
architectures: [BinaryArchitecture] = [], architectures: [BinaryArchitecture] = [],
dependencies: [FrameworkNode] = []) -> FrameworkNode dependencies: [PrecompiledNode.Dependency] = []) -> FrameworkNode
{ {
FrameworkNode(path: path, FrameworkNode(path: path,
dsymPath: dsymPath, dsymPath: dsymPath,

View File

@ -3,23 +3,28 @@ import TSCBasic
@testable import TuistCore @testable import TuistCore
public extension Graph { public extension Graph {
static func test(name: String = "test", static func test(
name: String = "test",
entryPath: AbsolutePath = AbsolutePath("/test/graph"), entryPath: AbsolutePath = AbsolutePath("/test/graph"),
entryNodes: [GraphNode] = [], entryNodes: [GraphNode] = [],
workspace: Workspace = Workspace.test(path: "/test/graph"),
projects: [Project] = [], projects: [Project] = [],
cocoapods: [CocoaPodsNode] = [], cocoapods: [CocoaPodsNode] = [],
packages: [PackageNode] = [], packages: [PackageNode] = [],
precompiled: [PrecompiledNode] = [], precompiled: [PrecompiledNode] = [],
targets: [AbsolutePath: [TargetNode]] = [:]) -> Graph targets: [AbsolutePath: [TargetNode]] = [:]
{ ) -> Graph {
Graph(name: name, Graph(
name: name,
entryPath: entryPath, entryPath: entryPath,
entryNodes: entryNodes, entryNodes: entryNodes,
workspace: workspace,
projects: projects, projects: projects,
cocoapods: cocoapods, cocoapods: cocoapods,
packages: packages, packages: packages,
precompiled: precompiled, precompiled: precompiled,
targets: targets) targets: targets
)
} }
/// Creates a test dependency graph for targets within a single project /// Creates a test dependency graph for targets within a single project

View File

@ -10,9 +10,9 @@ public final class MockGraphLoader: GraphLoading {
return try loadProjectStub?(path) ?? (Graph.test(), Project.test()) return try loadProjectStub?(path) ?? (Graph.test(), Project.test())
} }
public var loadWorkspaceStub: ((AbsolutePath) throws -> (Graph, Workspace))? public var loadWorkspaceStub: ((AbsolutePath) throws -> (Graph))?
public func loadWorkspace(path: AbsolutePath) throws -> (Graph, Workspace) { public func loadWorkspace(path: AbsolutePath) throws -> (Graph) {
return try loadWorkspaceStub?(path) ?? (Graph.test(), Workspace.test()) return try loadWorkspaceStub?(path) ?? Graph.test()
} }
public var loadConfigStub: ((AbsolutePath) throws -> (Config))? public var loadConfigStub: ((AbsolutePath) throws -> (Config))?

View File

@ -6,10 +6,12 @@ import TSCBasic
public extension TargetNode { public extension TargetNode {
static func test(project: Project = .test(), static func test(project: Project = .test(),
target: Target = .test(), target: Target = .test(),
dependencies: [GraphNode] = []) -> TargetNode dependencies: [GraphNode] = [],
prune: Bool = false) -> TargetNode
{ {
TargetNode(project: project, TargetNode(project: project,
target: target, target: target,
dependencies: dependencies) dependencies: dependencies,
prune: prune)
} }
} }

View File

@ -8,7 +8,7 @@ public extension XCFrameworkNode {
infoPlist: XCFrameworkInfoPlist = .test(), infoPlist: XCFrameworkInfoPlist = .test(),
primaryBinaryPath: AbsolutePath = "/MyFramework/MyFramework.xcframework/binary", primaryBinaryPath: AbsolutePath = "/MyFramework/MyFramework.xcframework/binary",
linking: BinaryLinking = .dynamic, linking: BinaryLinking = .dynamic,
dependencies: [XCFrameworkNode.Dependency] = []) -> XCFrameworkNode dependencies: [PrecompiledNode.Dependency] = []) -> XCFrameworkNode
{ {
XCFrameworkNode(path: path, XCFrameworkNode(path: path,
infoPlist: infoPlist, infoPlist: infoPlist,

View File

@ -100,4 +100,8 @@ final class MockGraphTraverser: GraphTraversing {
invokedDirectStaticDependenciesParametersList.append((path, name)) invokedDirectStaticDependenciesParametersList.append((path, name))
return stubbedDirectStaticDependenciesResult return stubbedDirectStaticDependenciesResult
} }
func appClipsDependency(path _: AbsolutePath, name _: String) -> Target? {
nil
}
} }

View File

@ -5,10 +5,12 @@ import TSCBasic
public extension Config { public extension Config {
static func test(compatibleXcodeVersions: CompatibleXcodeVersions = .all, static func test(compatibleXcodeVersions: CompatibleXcodeVersions = .all,
cloud: Cloud? = Cloud.test(), cloud: Cloud? = Cloud.test(),
generationOptions: [GenerationOption] = []) -> Config generationOptions: [GenerationOption] = [],
path: AbsolutePath? = nil) -> Config
{ {
Config(compatibleXcodeVersions: compatibleXcodeVersions, Config(compatibleXcodeVersions: compatibleXcodeVersions,
cloud: cloud, cloud: cloud,
generationOptions: generationOptions) generationOptions: generationOptions,
path: path)
} }
} }

View File

@ -8,6 +8,7 @@ public extension Project {
xcodeProjPath: AbsolutePath = AbsolutePath("/Project/Project.xcodeproj"), xcodeProjPath: AbsolutePath = AbsolutePath("/Project/Project.xcodeproj"),
name: String = "Project", name: String = "Project",
organizationName: String? = nil, organizationName: String? = nil,
developmentRegion: String? = nil,
settings: Settings = Settings.test(), settings: Settings = Settings.test(),
filesGroup: ProjectGroup = .group(name: "Project"), filesGroup: ProjectGroup = .group(name: "Project"),
targets: [Target] = [Target.test()], targets: [Target] = [Target.test()],
@ -20,6 +21,7 @@ public extension Project {
xcodeProjPath: xcodeProjPath, xcodeProjPath: xcodeProjPath,
name: name, name: name,
organizationName: organizationName, organizationName: organizationName,
developmentRegion: developmentRegion,
settings: settings, settings: settings,
filesGroup: filesGroup, filesGroup: filesGroup,
targets: targets, targets: targets,
@ -33,6 +35,7 @@ public extension Project {
xcodeProjPath: AbsolutePath = AbsolutePath("/test/text.xcodeproj"), xcodeProjPath: AbsolutePath = AbsolutePath("/test/text.xcodeproj"),
name: String = "Project", name: String = "Project",
organizationName: String? = nil, organizationName: String? = nil,
developmentRegion: String? = nil,
settings: Settings = .default, settings: Settings = .default,
filesGroup: ProjectGroup = .group(name: "Project"), filesGroup: ProjectGroup = .group(name: "Project"),
targets: [Target] = [], targets: [Target] = [],
@ -45,6 +48,7 @@ public extension Project {
xcodeProjPath: xcodeProjPath, xcodeProjPath: xcodeProjPath,
name: name, name: name,
organizationName: organizationName, organizationName: organizationName,
developmentRegion: developmentRegion,
settings: settings, settings: settings,
filesGroup: filesGroup, filesGroup: filesGroup,
targets: targets, targets: targets,

View File

@ -21,7 +21,8 @@ public extension Target {
actions: [TargetAction] = [], actions: [TargetAction] = [],
environment: [String: String] = [:], environment: [String: String] = [:],
filesGroup: ProjectGroup = .group(name: "Project"), filesGroup: ProjectGroup = .group(name: "Project"),
dependencies: [Dependency] = []) -> Target dependencies: [Dependency] = [],
scripts: [TargetScript] = []) -> Target
{ {
Target(name: name, Target(name: name,
platform: platform, platform: platform,
@ -39,7 +40,8 @@ public extension Target {
actions: actions, actions: actions,
environment: environment, environment: environment,
filesGroup: filesGroup, filesGroup: filesGroup,
dependencies: dependencies) dependencies: dependencies,
scripts: scripts)
} }
/// Creates a bare bones Target with as little data as possible /// Creates a bare bones Target with as little data as possible
@ -59,7 +61,8 @@ public extension Target {
actions: [TargetAction] = [], actions: [TargetAction] = [],
environment: [String: String] = [:], environment: [String: String] = [:],
filesGroup: ProjectGroup = .group(name: "Project"), filesGroup: ProjectGroup = .group(name: "Project"),
dependencies: [Dependency] = []) -> Target dependencies: [Dependency] = [],
scripts: [TargetScript] = []) -> Target
{ {
Target(name: name, Target(name: name,
platform: platform, platform: platform,
@ -77,6 +80,7 @@ public extension Target {
actions: actions, actions: actions,
environment: environment, environment: environment,
filesGroup: filesGroup, filesGroup: filesGroup,
dependencies: dependencies) dependencies: dependencies,
scripts: scripts)
} }
} }

View File

@ -0,0 +1,13 @@
import Foundation
import TSCBasic
@testable import TuistCore
public extension TargetScript {
static func test(name: String = "Test",
script: String = "",
showEnvVarsInLog: Bool = false,
hashable: Bool = false) -> TargetScript
{
TargetScript(name: name, script: script, showEnvVarsInLog: showEnvVarsInLog, hashable: hashable)
}
}

View File

@ -3,14 +3,19 @@ import TSCBasic
@testable import TuistCore @testable import TuistCore
public extension Workspace { public extension Workspace {
static func test(path: AbsolutePath = AbsolutePath("/"), static func test(
path: AbsolutePath = AbsolutePath("/"),
name: String = "test", name: String = "test",
projects: [AbsolutePath] = [], projects: [AbsolutePath] = [],
additionalFiles: [FileElement] = []) -> Workspace schemes: [Scheme] = [],
{ additionalFiles: [FileElement] = []
Workspace(path: path, ) -> Workspace {
Workspace(
path: path,
name: name, name: name,
projects: projects, projects: projects,
additionalFiles: additionalFiles) schemes: schemes,
additionalFiles: additionalFiles
)
} }
} }

View File

@ -1,8 +1,10 @@
import Foundation import Foundation
import RxSwift import RxSwift
import struct TSCUtility.Version
import TuistCore
import TuistSupport import TuistSupport
@testable import TuistAutomation @testable import TuistCore
@testable import TuistSupportTesting @testable import TuistSupportTesting
public final class MockSimulatorController: SimulatorControlling { public final class MockSimulatorController: SimulatorControlling {
@ -43,4 +45,9 @@ public final class MockSimulatorController: SimulatorControlling {
return .error(TestError("call to non-stubbed method runtimesAndDevices")) return .error(TestError("call to non-stubbed method runtimesAndDevices"))
} }
} }
public var findAvailableDeviceStub: ((Platform, Version?, Version?, String?) -> Single<SimulatorDevice>)?
public func findAvailableDevice(platform: Platform, version: Version?, minVersion: Version?, deviceName: String?) -> Single<SimulatorDevice> {
findAvailableDeviceStub?(platform, version, minVersion, deviceName) ?? .just(SimulatorDevice.test())
}
} }

View File

@ -1,6 +1,6 @@
import Foundation import Foundation
import TSCBasic import TSCBasic
@testable import TuistAutomation @testable import TuistCore
extension SimulatorDevice { extension SimulatorDevice {
static func test(dataPath: AbsolutePath = "/Library/Developer/CoreSimulator/Devices/3A8C9673-C1FD-4E33-8EFA-AEEBF43161CC/data", static func test(dataPath: AbsolutePath = "/Library/Developer/CoreSimulator/Devices/3A8C9673-C1FD-4E33-8EFA-AEEBF43161CC/data",

View File

@ -1,6 +1,6 @@
import Foundation import Foundation
import TSCBasic import TSCBasic
@testable import TuistAutomation @testable import TuistCore
extension SimulatorDeviceAndRuntime { extension SimulatorDeviceAndRuntime {
static func test(device: SimulatorDevice = .test(), static func test(device: SimulatorDevice = .test(),

View File

@ -1,6 +1,6 @@
import Foundation import Foundation
import TSCBasic import TSCBasic
@testable import TuistAutomation @testable import TuistCore
extension SimulatorRuntime { extension SimulatorRuntime {
// swiftlint:disable:next line_length // swiftlint:disable:next line_length

View File

@ -3,8 +3,6 @@ import TSCBasic
@testable import TuistCore @testable import TuistCore
public final class MockRootDirectoryLocator: RootDirectoryLocating { public final class MockRootDirectoryLocator: RootDirectoryLocating {
public init() {}
public var locateArgs: [AbsolutePath] = [] public var locateArgs: [AbsolutePath] = []
public var locateStub: AbsolutePath? public var locateStub: AbsolutePath?

Some files were not shown because too many files have changed in this diff Show More