Merge branch 'main' into all-contributors/add-laxmorek
This commit is contained in:
commit
e2a11e7975
|
@ -10,6 +10,88 @@
|
|||
"name": "kalkwarf",
|
||||
"avatar_url": "https://avatars1.githubusercontent.com/u/1033839?v=4",
|
||||
"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": [
|
||||
"ideas"
|
||||
]
|
||||
|
@ -23,6 +105,33 @@
|
|||
"code",
|
||||
"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,
|
||||
|
|
|
@ -4,7 +4,7 @@ name: Checks
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
paths:
|
||||
- Sources/**/*
|
||||
- Tests/**/*
|
||||
|
@ -21,15 +21,18 @@ jobs:
|
|||
swiftformat:
|
||||
name: SwiftFormat
|
||||
runs-on: macOS-latest
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['12.1']
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Select Xcode 11.5
|
||||
run: sudo xcode-select -switch /Applications/Xcode_11.5.app
|
||||
- name: Select Xcode
|
||||
run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app
|
||||
- uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '2.x'
|
||||
- name: Install Bundler 2.0.2
|
||||
run: gem install bundler --version 2.0.2
|
||||
- name: Install Bundler 2.1.4
|
||||
run: gem install bundler --version 2.1.4
|
||||
- name: Install Bundler dependencies
|
||||
run: bundle install
|
||||
- name: Run swiftformat
|
||||
|
@ -37,15 +40,18 @@ jobs:
|
|||
swiftlint:
|
||||
name: Swiftlint
|
||||
runs-on: macOS-latest
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['12.1']
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Select Xcode 11.5
|
||||
run: sudo xcode-select -switch /Applications/Xcode_11.5.app
|
||||
- name: Select Xcode
|
||||
run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app
|
||||
- uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '2.x'
|
||||
- name: Install Bundler 2.0.2
|
||||
run: gem install bundler --version 2.0.2
|
||||
- name: Install Bundler 2.1.4
|
||||
run: gem install bundler --version 2.1.4
|
||||
- name: Install Bundler dependencies
|
||||
run: bundle install
|
||||
- name: Run swiftlint
|
||||
|
|
|
@ -16,10 +16,13 @@ jobs:
|
|||
test:
|
||||
name: Test
|
||||
runs-on: macOS-latest
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['12.1']
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Select Xcode 11.5
|
||||
run: sudo xcode-select -switch /Applications/Xcode_11.5.app
|
||||
- name: Select Xcode
|
||||
run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app
|
||||
- name: Build Package
|
||||
working-directory: ./tools/fixturegen
|
||||
run: swift build
|
||||
|
|
|
@ -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 }}
|
|
@ -3,7 +3,7 @@ name: Tuist
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
paths:
|
||||
- Sources/**/*
|
||||
- Tests/**/*
|
||||
|
@ -28,11 +28,18 @@ jobs:
|
|||
runs-on: macOS-latest
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['11.5', '12_beta']
|
||||
xcode: ['11.5', '12.1']
|
||||
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 }}-spm-${{ hashFiles('**/Package.resolved') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-spm-
|
||||
- name: Run tests
|
||||
run: |
|
||||
rm -rf .coverage
|
||||
|
@ -43,11 +50,17 @@ jobs:
|
|||
runs-on: macOS-latest
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['11.5']
|
||||
xcode: ['11.5', '12.1']
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Select Xcode
|
||||
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
|
||||
run: swift build -c release --product tuist
|
||||
- name: Build Tuistenv for release
|
||||
|
@ -57,7 +70,7 @@ jobs:
|
|||
runs-on: macOS-latest
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['11.5']
|
||||
xcode: ['11.5', '12.1']
|
||||
feature:
|
||||
[
|
||||
'generate-1',
|
||||
|
@ -65,34 +78,90 @@ jobs:
|
|||
'generate-3',
|
||||
'generate-4',
|
||||
'generate-5',
|
||||
'generate-6',
|
||||
'init',
|
||||
'lint-project',
|
||||
'lint-code',
|
||||
'scaffold',
|
||||
'up',
|
||||
'build',
|
||||
'test',
|
||||
]
|
||||
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.0.2
|
||||
run: gem install bundler --version 2.0.2
|
||||
- name: Install Bundler dependencies
|
||||
run: bundle install
|
||||
- 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
|
||||
cache_acceptance_tests:
|
||||
name: Cache Acceptance tests (${{ matrix.feature }})
|
||||
xcode_12_acceptance_tests:
|
||||
name: Xcode 12 Acceptance tests (${{ matrix.feature }})
|
||||
runs-on: macOS-latest
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['12_beta']
|
||||
feature: ['cache']
|
||||
xcode: ['12.1']
|
||||
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:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Select Xcode
|
||||
|
@ -100,28 +169,18 @@ jobs:
|
|||
- uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '2.x'
|
||||
- name: Install Bundler 2.0.2
|
||||
run: gem install bundler --version 2.0.2
|
||||
- name: Install Bundler dependencies
|
||||
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
|
||||
- name: Install Bundler 2.1.4
|
||||
run: gem install bundler --version 2.1.4
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
ruby-version: '2.x'
|
||||
- name: Install Bundler 2.0.2
|
||||
run: gem install bundler --version 2.0.2
|
||||
- name: Install Bundler dependencies
|
||||
run: bundle install
|
||||
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: Package build and upload it to GCS
|
||||
run: |
|
||||
bundle exec rake package_commit
|
||||
|
|
|
@ -14,10 +14,13 @@ jobs:
|
|||
test:
|
||||
name: Build
|
||||
runs-on: macOS-latest
|
||||
strategy:
|
||||
matrix:
|
||||
xcode: ['12.1']
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Select Xcode 11.5
|
||||
run: sudo xcode-select -switch /Applications/Xcode_11.5.app
|
||||
- name: Select Xcode
|
||||
run: sudo xcode-select -switch /Applications/Xcode_${{ matrix.xcode }}.app
|
||||
- name: Build Package
|
||||
working-directory: ./tools/tuistbench
|
||||
run: swift build
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
1.0.0-beta.4
|
81
CHANGELOG.md
81
CHANGELOG.md
|
@ -4,6 +4,87 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
|
|||
|
||||
## 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
|
||||
|
||||
### Fixed
|
||||
|
|
12
Gemfile
12
Gemfile
|
@ -2,16 +2,16 @@
|
|||
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "cucumber", "~> 5.1"
|
||||
gem "cucumber", "~> 5.2"
|
||||
gem "rake", "~> 13.0"
|
||||
gem "byebug", "~> 11.1"
|
||||
gem "minitest", "~> 5.14"
|
||||
gem "simctl", "~> 1.6"
|
||||
gem "rubocop", "~> 0.90.0"
|
||||
gem "rubocop", "~> 1.0.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 "cocoapods", "~> 1.9"
|
||||
gem "xcodeproj", "~> 1.18"
|
||||
gem "cocoapods", "~> 1.10"
|
||||
gem "xcodeproj", "~> 1.19"
|
||||
gem "highline", "~> 2.0"
|
||||
gem "zip", "~> 2.0.2"
|
||||
gem "rubyzip", "~> 2.3.0"
|
||||
|
|
81
Gemfile.lock
81
Gemfile.lock
|
@ -2,14 +2,14 @@ GEM
|
|||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.2)
|
||||
activesupport (4.2.11.3)
|
||||
i18n (~> 0.7)
|
||||
activesupport (5.2.4.4)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
algoliasearch (1.27.2)
|
||||
algoliasearch (1.27.4)
|
||||
httpclient (~> 2.8, >= 2.8.3)
|
||||
json (>= 1.5.1)
|
||||
ast (2.4.1)
|
||||
|
@ -17,15 +17,14 @@ GEM
|
|||
builder (3.2.4)
|
||||
byebug (11.1.3)
|
||||
claide (1.0.3)
|
||||
cocoapods (1.9.3)
|
||||
activesupport (>= 4.0.2, < 5)
|
||||
cocoapods (1.10.0)
|
||||
addressable (~> 2.6)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.9.3)
|
||||
cocoapods-core (= 1.10.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-search (>= 1.0.0, < 2.0)
|
||||
cocoapods-stats (>= 1.0.0, < 2.0)
|
||||
cocoapods-trunk (>= 1.4.0, < 2.0)
|
||||
cocoapods-try (>= 1.1.0, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
|
@ -35,21 +34,22 @@ GEM
|
|||
molinillo (~> 0.6.6)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (~> 1.4)
|
||||
xcodeproj (>= 1.14.0, < 2.0)
|
||||
cocoapods-core (1.9.3)
|
||||
activesupport (>= 4.0.2, < 6)
|
||||
xcodeproj (>= 1.19.0, < 2.0)
|
||||
cocoapods-core (1.10.0)
|
||||
activesupport (> 5.0, < 6)
|
||||
addressable (~> 2.6)
|
||||
algoliasearch (~> 1.0)
|
||||
concurrent-ruby (~> 1.1)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
netrc (~> 0.11)
|
||||
public_suffix
|
||||
typhoeus (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.4)
|
||||
cocoapods-downloader (1.3.0)
|
||||
cocoapods-downloader (1.4.0)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.0)
|
||||
cocoapods-stats (1.1.0)
|
||||
cocoapods-trunk (1.5.0)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
|
@ -57,14 +57,14 @@ GEM
|
|||
colored2 (3.1.2)
|
||||
colorize (0.8.1)
|
||||
concurrent-ruby (1.1.7)
|
||||
cucumber (5.1.1)
|
||||
cucumber (5.2.0)
|
||||
builder (~> 3.2, >= 3.2.4)
|
||||
cucumber-core (~> 8.0, >= 8.0.1)
|
||||
cucumber-create-meta (~> 2.0, >= 2.0.2)
|
||||
cucumber-cucumber-expressions (~> 10.3, >= 10.3.0)
|
||||
cucumber-gherkin (~> 15.0, >= 15.0.2)
|
||||
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)
|
||||
diff-lcs (~> 1.4, >= 1.4.4)
|
||||
multi_test (~> 0.1, >= 0.1.2)
|
||||
|
@ -81,7 +81,7 @@ GEM
|
|||
cucumber-messages (~> 13.0, >= 13.0.1)
|
||||
cucumber-html-formatter (9.0.0)
|
||||
cucumber-messages (~> 13.0, >= 13.0.1)
|
||||
cucumber-messages (13.0.1)
|
||||
cucumber-messages (13.1.0)
|
||||
protobuf-cucumber (~> 3.10, >= 3.10.8)
|
||||
cucumber-tag-expressions (2.0.4)
|
||||
cucumber-wire (4.0.1)
|
||||
|
@ -105,13 +105,14 @@ GEM
|
|||
fourflusher (2.3.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
google-api-client (0.43.0)
|
||||
google-api-client (0.46.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 0.9)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
rexml
|
||||
signet (~> 0.12)
|
||||
google-cloud-core (1.5.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
|
@ -119,14 +120,14 @@ GEM
|
|||
google-cloud-env (1.3.3)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-errors (1.0.1)
|
||||
google-cloud-storage (1.28.0)
|
||||
google-cloud-storage (1.29.1)
|
||||
addressable (~> 2.5)
|
||||
digest-crc (~> 0.4)
|
||||
google-api-client (~> 0.33)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (~> 0.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (0.13.1)
|
||||
googleauth (0.14.0)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
|
@ -135,9 +136,9 @@ GEM
|
|||
signet (~> 0.14)
|
||||
highline (2.0.3)
|
||||
httpclient (2.8.3)
|
||||
i18n (0.9.5)
|
||||
i18n (1.8.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.3.0)
|
||||
json (2.3.1)
|
||||
jwt (2.2.2)
|
||||
memoist (0.16.2)
|
||||
middleware (0.1.0)
|
||||
|
@ -153,36 +154,37 @@ GEM
|
|||
netrc (0.11.0)
|
||||
os (1.1.1)
|
||||
parallel (1.19.2)
|
||||
parser (2.7.1.4)
|
||||
parser (2.7.2.0)
|
||||
ast (~> 2.4.1)
|
||||
protobuf-cucumber (3.10.8)
|
||||
activesupport (>= 3.2)
|
||||
middleware
|
||||
thor
|
||||
thread_safe
|
||||
public_suffix (4.0.5)
|
||||
public_suffix (4.0.6)
|
||||
rainbow (3.0.0)
|
||||
rake (13.0.1)
|
||||
regexp_parser (1.7.1)
|
||||
regexp_parser (1.8.2)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.4)
|
||||
rubocop (0.90.0)
|
||||
rubocop (1.0.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.7.1.1)
|
||||
parser (>= 2.7.1.5)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.7)
|
||||
regexp_parser (>= 1.8)
|
||||
rexml
|
||||
rubocop-ast (>= 0.3.0, < 1.0)
|
||||
rubocop-ast (>= 0.6.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 2.0)
|
||||
rubocop-ast (0.3.0)
|
||||
parser (>= 2.7.1.4)
|
||||
rubocop-ast (1.0.1)
|
||||
parser (>= 2.7.1.5)
|
||||
ruby-macho (1.4.0)
|
||||
ruby-progressbar (1.10.1)
|
||||
rubyzip (2.3.0)
|
||||
signet (0.14.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
|
@ -201,31 +203,30 @@ GEM
|
|||
thread_safe (~> 0.1)
|
||||
uber (0.1.0)
|
||||
unicode-display_width (1.7.0)
|
||||
xcodeproj (1.18.0)
|
||||
xcodeproj (1.19.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
zip (2.0.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
byebug (~> 11.1)
|
||||
cocoapods (~> 1.9)
|
||||
cocoapods (~> 1.10)
|
||||
colorize (~> 0.8.1)
|
||||
cucumber (~> 5.1)
|
||||
cucumber (~> 5.2)
|
||||
encrypted-environment (~> 0.2.0)
|
||||
google-cloud-storage (~> 1.28)
|
||||
google-cloud-storage (~> 1.29)
|
||||
highline (~> 2.0)
|
||||
minitest (~> 5.14)
|
||||
rake (~> 13.0)
|
||||
rubocop (~> 0.90.0)
|
||||
rubocop (~> 1.0.0)
|
||||
rubyzip (~> 2.3.0)
|
||||
simctl (~> 1.6)
|
||||
xcodeproj (~> 1.18)
|
||||
zip (~> 2.0.2)
|
||||
xcodeproj (~> 1.19)
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.2
|
||||
2.1.4
|
||||
|
|
|
@ -150,8 +150,8 @@
|
|||
"repositoryURL": "https://github.com/apple/swift-argument-parser",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "7255fd547f70468e19abbac5f7964f1ef309ad92",
|
||||
"version": "0.2.1"
|
||||
"revision": "92646c0cdbaca076c8d3d0207891785b3379cbff",
|
||||
"version": "0.3.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -168,8 +168,8 @@
|
|||
"repositoryURL": "https://github.com/apple/swift-tools-support-core",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "f39fc6c12266697b1585589090f0004903974685",
|
||||
"version": "0.1.10"
|
||||
"revision": "243beea77d20db46647a3de4765c96e2c801c7c7",
|
||||
"version": "0.1.12"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -177,8 +177,8 @@
|
|||
"repositoryURL": "https://github.com/httpswift/swifter.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "8b5afb48ae64d4f729f0489ddcfe09c62b9c3687",
|
||||
"version": "1.4.7"
|
||||
"revision": "9483a5d459b45c3ffd059f7b55f9638e268632fd",
|
||||
"version": "1.5.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -195,8 +195,8 @@
|
|||
"repositoryURL": "https://github.com/thii/xcbeautify.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "77bf4eae0f090dbf7cbcabb68fcaf0e7ff9826db",
|
||||
"version": "0.8.0"
|
||||
"revision": "fd7b0b6972809eead52b9016b383cf6d467e00b0",
|
||||
"version": "0.8.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -204,8 +204,8 @@
|
|||
"repositoryURL": "https://github.com/tuist/XcodeProj",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "81bb2bb333eafa68f8ecd8187a4bb56d51e78e97",
|
||||
"version": "7.14.0"
|
||||
"revision": "2ae8e322ddbed99655802533441bc52f9ae76f24",
|
||||
"version": "7.17.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -231,8 +231,8 @@
|
|||
"repositoryURL": "https://github.com/marmelroy/Zip.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "80b1c3005ee25b4c7ce46c4029ac3347e8d5e37e",
|
||||
"version": "2.0.0"
|
||||
"revision": "bd19d974e8a38cc8d3a88c90c8a107386c3b8ccf",
|
||||
"version": "2.1.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -27,19 +27,19 @@ let package = Package(
|
|||
targets: ["TuistGenerator"]),
|
||||
],
|
||||
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/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/apple/swift-log.git", .upToNextMajor(from: "1.2.0")),
|
||||
.package(url: "https://github.com/thii/xcbeautify.git", .upToNextMajor(from: "0.8.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.1")),
|
||||
.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/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/apple/swift-tools-support-core", .upToNextMinor(from: "0.1.1")),
|
||||
.package(url: "https://github.com/apple/swift-argument-parser", .upToNextMajor(from: "0.0.6")),
|
||||
.package(url: "https://github.com/marmelroy/Zip.git", .upToNextMinor(from: "2.0.0")),
|
||||
.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.12")),
|
||||
.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.1.1")),
|
||||
.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/StencilSwiftKit.git", .branch("stable")),
|
||||
|
@ -75,11 +75,11 @@ let package = Package(
|
|||
),
|
||||
.target(
|
||||
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(
|
||||
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(
|
||||
name: "TuistKitIntegrationTests",
|
||||
|
@ -151,7 +151,7 @@ let package = Package(
|
|||
),
|
||||
.target(
|
||||
name: "TuistCacheTesting",
|
||||
dependencies: ["TuistCache", "SwiftToolsSupport-auto", "TuistCore", "RxTest", "RxSwift"]
|
||||
dependencies: ["TuistCache", "SwiftToolsSupport-auto", "TuistCore", "RxTest", "RxSwift", "TuistSupportTesting"]
|
||||
),
|
||||
.target(
|
||||
name: "TuistCloud",
|
||||
|
@ -229,6 +229,22 @@ let package = Package(
|
|||
name: "TuistSigningIntegrationTests",
|
||||
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(
|
||||
name: "TuistMigration",
|
||||
dependencies: ["TuistCore", "TuistSupport", "XcodeProj", "SwiftToolsSupport-auto"]
|
||||
|
|
26
README.md
26
README.md
|
@ -1,8 +1,6 @@
|
|||
<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"/>
|
||||
<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://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>
|
||||
|
@ -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/github/license/tuist/tuist?style=flat-square" alt="License">
|
||||
<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>
|
||||
|
||||
## 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/).
|
||||
|
||||
## 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 👩💻
|
||||
|
||||
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 -->
|
||||
<table>
|
||||
<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="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>
|
||||
</table>
|
||||
|
||||
|
|
14
RELEASE.md
14
RELEASE.md
|
@ -2,19 +2,9 @@
|
|||
|
||||
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`
|
||||
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:
|
||||
1. Determine the new version:
|
||||
|
||||
- Major if there's been a breaking change.
|
||||
- Minor by default.
|
||||
- Patch if it's a hotfix release.
|
||||
|
||||
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.
|
||||
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)*.
|
23
Rakefile
23
Rakefile
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
SWIFTDOC_VERSION = "1.0.0-beta.4".freeze
|
||||
SWIFTLINT_VERSION = "0.40.2".freeze
|
||||
|
||||
require 'rubygems'
|
||||
require 'cucumber'
|
||||
|
@ -21,7 +22,7 @@ end
|
|||
|
||||
desc("Updates swift-doc binary with the latest version available.")
|
||||
task :swift_doc_update do
|
||||
root_dir = Dir.pwd.strip
|
||||
root_dir = File.expand_path(__dir__)
|
||||
Dir.mktmpdir do |temporary_dir|
|
||||
Dir.chdir(temporary_dir) do
|
||||
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")
|
||||
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
|
||||
|
||||
desc("Formats the code style")
|
||||
|
@ -61,6 +75,11 @@ task :style_ruby_correct do
|
|||
system("bundle", "exec", "rubocop", "-a")
|
||||
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")
|
||||
task :release do
|
||||
decrypt_secrets
|
||||
|
|
|
@ -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.
|
||||
/// - 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
|
||||
/// - 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 {
|
||||
case xcodeProjectName(TemplateString)
|
||||
case organizationName(String)
|
||||
case developmentRegion(String)
|
||||
case disableAutogeneratedSchemes
|
||||
case disableSynthesizedResourceAccessors
|
||||
case disableShowEnvironmentVarsInScriptPhases
|
||||
}
|
||||
|
||||
/// Generation options.
|
||||
|
@ -26,7 +30,7 @@ public struct Config: Codable, Equatable {
|
|||
/// Cloud configuration.
|
||||
public let cloud: Cloud?
|
||||
|
||||
/// Initializes the tuist cofiguration.
|
||||
/// Initializes the tuist configuration.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - compatibleXcodeVersions: List of Xcode versions the project is compatible with.
|
||||
|
@ -45,7 +49,12 @@ public struct Config: Codable, Equatable {
|
|||
|
||||
extension Config.GenerationOptions {
|
||||
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 {
|
||||
|
@ -63,6 +72,12 @@ extension Config.GenerationOptions {
|
|||
self = .organizationName(organizationName)
|
||||
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) {
|
||||
self = .disableAutogeneratedSchemes
|
||||
return
|
||||
|
@ -71,6 +86,11 @@ extension Config.GenerationOptions {
|
|||
self = .disableSynthesizedResourceAccessors
|
||||
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"))
|
||||
}
|
||||
|
||||
|
@ -84,10 +104,15 @@ extension Config.GenerationOptions {
|
|||
case let .organizationName(name):
|
||||
var associatedValues = container.nestedUnkeyedContainer(forKey: .organizationName)
|
||||
try associatedValues.encode(name)
|
||||
case let .developmentRegion(developmentRegion):
|
||||
var associatedValues = container.nestedUnkeyedContainer(forKey: .developmentRegion)
|
||||
try associatedValues.encode(developmentRegion)
|
||||
case .disableAutogeneratedSchemes:
|
||||
try container.encode(true, forKey: .disableAutogeneratedSchemes)
|
||||
case .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
|
||||
case let (.organizationName(lhs), .organizationName(rhs)):
|
||||
return lhs == rhs
|
||||
case let (.developmentRegion(lhs), .developmentRegion(rhs)):
|
||||
return lhs == rhs
|
||||
case (.disableAutogeneratedSchemes, .disableAutogeneratedSchemes):
|
||||
return true
|
||||
case (.disableSynthesizedResourceAccessors, .disableSynthesizedResourceAccessors):
|
||||
return true
|
||||
case (.disableShowEnvironmentVarsInScriptPhases, .disableShowEnvironmentVarsInScriptPhases):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ public enum Product: String, Codable, Equatable {
|
|||
case unitTests = "unit_tests"
|
||||
case uiTests = "ui_tests"
|
||||
case bundle
|
||||
case appClip
|
||||
|
||||
// Not supported yet
|
||||
case appExtension = "app_extension"
|
||||
|
|
|
@ -148,7 +148,7 @@ public enum DefaultSettings: Codable, Equatable {
|
|||
var nestedContainer = try container.nestedUnkeyedContainer(forKey: .essential)
|
||||
let excludedKeys = try nestedContainer.decode(Set<String>.self)
|
||||
self = .essential(excluding: excludedKeys)
|
||||
case .none:
|
||||
case .none?:
|
||||
self = .none
|
||||
default:
|
||||
throw DecodingError.dataCorrupted(
|
||||
|
|
|
@ -78,7 +78,7 @@ public extension SettingsDictionary {
|
|||
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 {
|
||||
var versionSettings: SettingsDictionary = ["VERSION_INFO_STRING": SettingValue(version)]
|
||||
versionSettings["VERSION_INFO_PREFIX"] = prefix.map { SettingValue($0) }
|
||||
|
|
|
@ -37,6 +37,9 @@ public struct TargetAction: Codable, Equatable {
|
|||
/// List of output filelist paths
|
||||
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 {
|
||||
case name
|
||||
case tool
|
||||
|
@ -47,6 +50,7 @@ public struct TargetAction: Codable, Equatable {
|
|||
case inputFileListPaths
|
||||
case outputPaths
|
||||
case outputFileListPaths
|
||||
case basedOnDependencyAnalysis
|
||||
}
|
||||
|
||||
/// Initializes the target action with its attributes.
|
||||
|
@ -61,6 +65,7 @@ public struct TargetAction: Codable, Equatable {
|
|||
/// - inputFileListPaths: List of input filelist paths.
|
||||
/// - outputPaths: List of output file paths.
|
||||
/// - outputFileListPaths: List of output filelist paths.
|
||||
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
|
||||
init(name: String,
|
||||
tool: String?,
|
||||
path: Path?,
|
||||
|
@ -69,7 +74,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: [Path] = [],
|
||||
inputFileListPaths: [Path] = [],
|
||||
outputPaths: [Path] = [],
|
||||
outputFileListPaths: [Path] = [])
|
||||
outputFileListPaths: [Path] = [],
|
||||
basedOnDependencyAnalysis: Bool? = nil)
|
||||
{
|
||||
self.name = name
|
||||
self.path = path
|
||||
|
@ -80,6 +86,7 @@ public struct TargetAction: Codable, Equatable {
|
|||
self.inputFileListPaths = inputFileListPaths
|
||||
self.outputPaths = outputPaths
|
||||
self.outputFileListPaths = outputFileListPaths
|
||||
self.basedOnDependencyAnalysis = basedOnDependencyAnalysis
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// - outputPaths: List of output file paths.
|
||||
/// - outputFileListPaths: List of output filelist paths.
|
||||
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
|
||||
/// - Returns: Target action.
|
||||
public static func pre(tool: String,
|
||||
arguments: String...,
|
||||
|
@ -99,7 +107,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: [Path] = [],
|
||||
inputFileListPaths: [Path] = [],
|
||||
outputPaths: [Path] = [],
|
||||
outputFileListPaths: [Path] = []) -> TargetAction
|
||||
outputFileListPaths: [Path] = [],
|
||||
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
|
||||
{
|
||||
TargetAction(name: name,
|
||||
tool: tool,
|
||||
|
@ -109,7 +118,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: inputPaths,
|
||||
inputFileListPaths: inputFileListPaths,
|
||||
outputPaths: outputPaths,
|
||||
outputFileListPaths: outputFileListPaths)
|
||||
outputFileListPaths: outputFileListPaths,
|
||||
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// - outputPaths: List of output file paths.
|
||||
/// - outputFileListPaths: List of output filelist paths.
|
||||
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
|
||||
/// - Returns: Target action.
|
||||
public static func pre(tool: String,
|
||||
arguments: [String],
|
||||
|
@ -129,7 +140,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: [Path] = [],
|
||||
inputFileListPaths: [Path] = [],
|
||||
outputPaths: [Path] = [],
|
||||
outputFileListPaths: [Path] = []) -> TargetAction
|
||||
outputFileListPaths: [Path] = [],
|
||||
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
|
||||
{
|
||||
TargetAction(name: name,
|
||||
tool: tool,
|
||||
|
@ -139,7 +151,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: inputPaths,
|
||||
inputFileListPaths: inputFileListPaths,
|
||||
outputPaths: outputPaths,
|
||||
outputFileListPaths: outputFileListPaths)
|
||||
outputFileListPaths: outputFileListPaths,
|
||||
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// - outputPaths: List of output file paths.
|
||||
/// - outputFileListPaths: List of output filelist paths.
|
||||
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
|
||||
/// - Returns: Target action.
|
||||
public static func pre(path: Path,
|
||||
arguments: String...,
|
||||
|
@ -159,7 +173,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: [Path] = [],
|
||||
inputFileListPaths: [Path] = [],
|
||||
outputPaths: [Path] = [],
|
||||
outputFileListPaths: [Path] = []) -> TargetAction
|
||||
outputFileListPaths: [Path] = [],
|
||||
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
|
||||
{
|
||||
TargetAction(name: name,
|
||||
tool: nil,
|
||||
|
@ -169,7 +184,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: inputPaths,
|
||||
inputFileListPaths: inputFileListPaths,
|
||||
outputPaths: outputPaths,
|
||||
outputFileListPaths: outputFileListPaths)
|
||||
outputFileListPaths: outputFileListPaths,
|
||||
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// - outputPaths: List of output file paths.
|
||||
/// - outputFileListPaths: List of output filelist paths.
|
||||
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
|
||||
/// - Returns: Target action.
|
||||
public static func pre(path: Path,
|
||||
arguments: [String],
|
||||
|
@ -189,7 +206,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: [Path] = [],
|
||||
inputFileListPaths: [Path] = [],
|
||||
outputPaths: [Path] = [],
|
||||
outputFileListPaths: [Path] = []) -> TargetAction
|
||||
outputFileListPaths: [Path] = [],
|
||||
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
|
||||
{
|
||||
TargetAction(name: name,
|
||||
tool: nil,
|
||||
|
@ -199,7 +217,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: inputPaths,
|
||||
inputFileListPaths: inputFileListPaths,
|
||||
outputPaths: outputPaths,
|
||||
outputFileListPaths: outputFileListPaths)
|
||||
outputFileListPaths: outputFileListPaths,
|
||||
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// - outputPaths: List of output file paths.
|
||||
/// - outputFileListPaths: List of output filelist paths.
|
||||
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
|
||||
/// - Returns: Target action.
|
||||
public static func post(tool: String,
|
||||
arguments: String...,
|
||||
|
@ -219,7 +239,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: [Path] = [],
|
||||
inputFileListPaths: [Path] = [],
|
||||
outputPaths: [Path] = [],
|
||||
outputFileListPaths: [Path] = []) -> TargetAction
|
||||
outputFileListPaths: [Path] = [],
|
||||
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
|
||||
{
|
||||
TargetAction(name: name,
|
||||
tool: tool,
|
||||
|
@ -229,7 +250,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: inputPaths,
|
||||
inputFileListPaths: inputFileListPaths,
|
||||
outputPaths: outputPaths,
|
||||
outputFileListPaths: outputFileListPaths)
|
||||
outputFileListPaths: outputFileListPaths,
|
||||
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// - outputPaths: List of output file paths.
|
||||
/// - outputFileListPaths: List of output filelist paths.
|
||||
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
|
||||
/// - Returns: Target action.
|
||||
public static func post(tool: String,
|
||||
arguments: [String],
|
||||
|
@ -249,7 +272,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: [Path] = [],
|
||||
inputFileListPaths: [Path] = [],
|
||||
outputPaths: [Path] = [],
|
||||
outputFileListPaths: [Path] = []) -> TargetAction
|
||||
outputFileListPaths: [Path] = [],
|
||||
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
|
||||
{
|
||||
TargetAction(name: name,
|
||||
tool: tool,
|
||||
|
@ -259,7 +283,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: inputPaths,
|
||||
inputFileListPaths: inputFileListPaths,
|
||||
outputPaths: outputPaths,
|
||||
outputFileListPaths: outputFileListPaths)
|
||||
outputFileListPaths: outputFileListPaths,
|
||||
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// - outputPaths: List of output file paths.
|
||||
/// - outputFileListPaths: List of output filelist paths.
|
||||
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
|
||||
/// - Returns: Target action.
|
||||
public static func post(path: Path,
|
||||
arguments: String...,
|
||||
|
@ -279,7 +305,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: [Path] = [],
|
||||
inputFileListPaths: [Path] = [],
|
||||
outputPaths: [Path] = [],
|
||||
outputFileListPaths: [Path] = []) -> TargetAction
|
||||
outputFileListPaths: [Path] = [],
|
||||
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
|
||||
{
|
||||
TargetAction(name: name,
|
||||
tool: nil,
|
||||
|
@ -289,7 +316,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: inputPaths,
|
||||
inputFileListPaths: inputFileListPaths,
|
||||
outputPaths: outputPaths,
|
||||
outputFileListPaths: outputFileListPaths)
|
||||
outputFileListPaths: outputFileListPaths,
|
||||
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// - outputPaths: List of output file paths.
|
||||
/// - outputFileListPaths: List of output filelist paths.
|
||||
/// - basedOnDependencyAnalysis: Whether to skip running this script in incremental builds
|
||||
/// - Returns: Target action.
|
||||
public static func post(path: Path,
|
||||
arguments: [String],
|
||||
|
@ -309,7 +338,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: [Path] = [],
|
||||
inputFileListPaths: [Path] = [],
|
||||
outputPaths: [Path] = [],
|
||||
outputFileListPaths: [Path] = []) -> TargetAction
|
||||
outputFileListPaths: [Path] = [],
|
||||
basedOnDependencyAnalysis: Bool? = nil) -> TargetAction
|
||||
{
|
||||
TargetAction(name: name,
|
||||
tool: nil,
|
||||
|
@ -319,7 +349,8 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputPaths: inputPaths,
|
||||
inputFileListPaths: inputFileListPaths,
|
||||
outputPaths: outputPaths,
|
||||
outputFileListPaths: outputFileListPaths)
|
||||
outputFileListPaths: outputFileListPaths,
|
||||
basedOnDependencyAnalysis: basedOnDependencyAnalysis)
|
||||
}
|
||||
|
||||
// MARK: - Codable
|
||||
|
@ -333,6 +364,7 @@ public struct TargetAction: Codable, Equatable {
|
|||
inputFileListPaths = try container.decodeIfPresent([Path].self, forKey: .inputFileListPaths) ?? []
|
||||
outputPaths = try container.decodeIfPresent([Path].self, forKey: .outputPaths) ?? []
|
||||
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) {
|
||||
self.path = path
|
||||
tool = nil
|
||||
|
@ -352,6 +384,7 @@ public struct TargetAction: Codable, Equatable {
|
|||
try container.encode(inputFileListPaths, forKey: .inputFileListPaths)
|
||||
try container.encode(outputPaths, forKey: .outputPaths)
|
||||
try container.encode(outputFileListPaths, forKey: .outputFileListPaths)
|
||||
try container.encode(basedOnDependencyAnalysis, forKey: .basedOnDependencyAnalysis)
|
||||
|
||||
if let tool = tool {
|
||||
try container.encode(tool, forKey: .tool)
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import Foundation
|
||||
|
||||
struct SimulatorDeviceAndRuntime: Hashable {
|
||||
/// Device
|
||||
let device: SimulatorDevice
|
||||
|
||||
/// Device's runtime.
|
||||
let runtime: SimulatorRuntime
|
||||
}
|
|
@ -19,6 +19,12 @@ public protocol BuildGraphInspecting {
|
|||
/// - graph: Dependency graph.
|
||||
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.
|
||||
/// - Parameter graph: Dependency graph.
|
||||
func buildableSchemes(graph: Graph) -> [Scheme]
|
||||
|
@ -27,6 +33,14 @@ public protocol BuildGraphInspecting {
|
|||
/// - Parameters:
|
||||
/// - graph: Dependency graph
|
||||
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 {
|
||||
|
@ -53,17 +67,26 @@ public class BuildGraphInspector: BuildGraphInspecting {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
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] {
|
||||
graph.targets.values.flatMap {
|
||||
$0.flatMap { $0.project.schemes }
|
||||
}
|
||||
graph.schemes
|
||||
.filter { $0.buildAction?.targets.isEmpty == false }
|
||||
.sorted(by: { $0.name < $1.name })
|
||||
}
|
||||
|
@ -76,6 +99,25 @@ public class BuildGraphInspector: BuildGraphInspecting {
|
|||
.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? {
|
||||
try directory.glob("**/*.xcworkspace")
|
||||
.filter {
|
||||
|
|
|
@ -53,6 +53,40 @@ public final class XcodeBuildController: XcodeBuildControlling {
|
|||
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,
|
||||
scheme: String,
|
||||
clean: Bool,
|
||||
|
@ -168,29 +202,18 @@ public final class XcodeBuildController: XcodeBuildControlling {
|
|||
switch event {
|
||||
case let .standardError(errorData):
|
||||
guard let line = String(data: errorData, encoding: .utf8) else { return Observable.empty() }
|
||||
return Observable.create { observer in
|
||||
let lines = line.split(separator: "\n")
|
||||
lines.map { line in
|
||||
let output = line.split(separator: "\n").map { line -> SystemEvent<XcodeBuildOutput> in
|
||||
let formatedOutput = self.parser.parse(line: String(line), colored: colored)
|
||||
return SystemEvent.standardError(XcodeBuildOutput(raw: "\(String(line))\n", formatted: formatedOutput.map { "\($0)\n" }))
|
||||
}
|
||||
.forEach(observer.onNext)
|
||||
observer.onCompleted()
|
||||
return Disposables.create()
|
||||
}
|
||||
return Observable.from(output)
|
||||
case let .standardOutput(outputData):
|
||||
guard let line = String(data: outputData, encoding: .utf8) else { return Observable.empty() }
|
||||
|
||||
return Observable.create { observer in
|
||||
let lines = line.split(separator: "\n")
|
||||
lines.map { line in
|
||||
let output = line.split(separator: "\n").map { line -> SystemEvent<XcodeBuildOutput> in
|
||||
let formatedOutput = self.parser.parse(line: String(line), colored: colored)
|
||||
return SystemEvent.standardOutput(XcodeBuildOutput(raw: "\(String(line))\n", formatted: formatedOutput.map { "\($0)\n" }))
|
||||
}
|
||||
.forEach(observer.onNext)
|
||||
observer.onCompleted()
|
||||
return Disposables.create()
|
||||
}
|
||||
return Observable.from(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,4 +43,27 @@ public final class MockBuildGraphInspector: BuildGraphInspecting {
|
|||
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) ?? []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
return Completable.zip(storages.map { $0.store(hash: hash, xcframeworkPath: xcframeworkPath) })
|
||||
return Completable.zip(storages.map { $0.store(hash: hash, paths: paths) })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ public final class CacheLocalStorage: CacheStoring {
|
|||
// MARK: - Init
|
||||
|
||||
public convenience init() {
|
||||
self.init(cacheDirectory: Environment.shared.xcframeworksCacheDirectory)
|
||||
self.init(cacheDirectory: Environment.shared.buildCacheDirectory)
|
||||
}
|
||||
|
||||
init(cacheDirectory: AbsolutePath) {
|
||||
|
@ -40,14 +40,14 @@ public final class CacheLocalStorage: CacheStoring {
|
|||
|
||||
public func exists(hash: String) -> Single<Bool> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
public func fetch(hash: String) -> Single<AbsolutePath> {
|
||||
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))
|
||||
} else {
|
||||
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 hashFolder = self.cacheDirectory.appending(component: hash)
|
||||
let destinationPath = hashFolder.appending(component: xcframeworkPath.basename)
|
||||
|
||||
do {
|
||||
if !FileHandler.shared.exists(hashFolder) {
|
||||
try FileHandler.shared.createFolder(hashFolder)
|
||||
}
|
||||
try paths.forEach { sourcePath in
|
||||
let destinationPath = hashFolder.appending(component: sourcePath.basename)
|
||||
if FileHandler.shared.exists(destinationPath) {
|
||||
try FileHandler.shared.delete(destinationPath)
|
||||
}
|
||||
|
||||
try FileHandler.shared.copy(from: xcframeworkPath, to: destinationPath)
|
||||
|
||||
try FileHandler.shared.copy(from: sourcePath, to: destinationPath)
|
||||
}
|
||||
} catch {
|
||||
completed(.error(error))
|
||||
return Disposables.create()
|
||||
|
@ -84,6 +85,14 @@ public final class CacheLocalStorage: CacheStoring {
|
|||
|
||||
// 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 {
|
||||
Completable.create { (completed) -> Disposable in
|
||||
do {
|
||||
|
|
|
@ -5,18 +5,18 @@ import TuistCore
|
|||
import TuistSupport
|
||||
|
||||
enum CacheRemoteStorageError: FatalError, Equatable {
|
||||
case archiveDoesNotContainXCFramework(AbsolutePath)
|
||||
case frameworkNotFound(hash: String)
|
||||
|
||||
var type: ErrorType {
|
||||
switch self {
|
||||
case .archiveDoesNotContainXCFramework: return .abort
|
||||
case .frameworkNotFound: return .abort
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case let .archiveDoesNotContainXCFramework(path):
|
||||
return "Unzipped archive at path \(path.pathString) does not contain any xcframework."
|
||||
case let .frameworkNotFound(hash):
|
||||
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 cloudClient: CloudClienting
|
||||
private let fileClient: FileClienting
|
||||
private let fileArchiverFactory: FileArchiverManufacturing
|
||||
private var fileArchiverMap: [AbsolutePath: FileArchiving] = [:]
|
||||
private let fileArchiverFactory: FileArchivingFactorying
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public convenience init(cloudConfig: Cloud, cloudClient: CloudClienting) {
|
||||
self.init(cloudConfig: cloudConfig,
|
||||
cloudClient: cloudClient,
|
||||
fileArchiverFactory: FileArchiverFactory(),
|
||||
fileArchiverFactory: FileArchivingFactory(),
|
||||
fileClient: FileClient())
|
||||
}
|
||||
|
||||
init(cloudConfig: Cloud,
|
||||
cloudClient: CloudClienting,
|
||||
fileArchiverFactory: FileArchiverManufacturing,
|
||||
fileArchiverFactory: FileArchivingFactorying,
|
||||
fileClient: FileClienting)
|
||||
{
|
||||
self.cloudConfig = cloudConfig
|
||||
|
@ -79,7 +78,10 @@ public final class CacheRemoteStorage: CacheStoring {
|
|||
return cloudClient
|
||||
.request(resource)
|
||||
.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
|
||||
do {
|
||||
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 {
|
||||
let archiver = fileArchiver(for: xcframeworkPath)
|
||||
let destinationZipPath = try archiver.zip()
|
||||
let archiver = try fileArchiverFactory.makeFileArchiver(for: paths)
|
||||
let destinationZipPath = try archiver.zip(name: hash)
|
||||
let resource = try CloudCacheResponse.storeResource(
|
||||
hash: hash,
|
||||
cloud: cloudConfig,
|
||||
|
@ -119,26 +121,31 @@ public final class CacheRemoteStorage: CacheStoring {
|
|||
|
||||
// MARK: - Private
|
||||
|
||||
private func xcframeworkPath(in archive: AbsolutePath) throws -> AbsolutePath? {
|
||||
let folderContent = try FileHandler.shared.contentsOfDirectory(archive)
|
||||
return folderContent.filter { FileHandler.shared.isFolder($0) && $0.extension == "xcframework" }.first
|
||||
private func frameworkPath(in archive: AbsolutePath) -> AbsolutePath? {
|
||||
if let xcframeworkPath = FileHandler.shared.glob(archive, glob: "*.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 {
|
||||
let zipPath = try FileHandler.shared.changeExtension(path: downloadedArchive, to: "zip")
|
||||
let archiveDestination = Environment.shared.xcframeworksCacheDirectory.appending(component: hash)
|
||||
try fileArchiver(for: zipPath).unzip(to: archiveDestination)
|
||||
guard let xcframework = try xcframeworkPath(in: archiveDestination) else {
|
||||
try FileHandler.shared.delete(archiveDestination)
|
||||
throw CacheRemoteStorageError.archiveDoesNotContainXCFramework(archiveDestination)
|
||||
let archiveDestination = Environment.shared.buildCacheDirectory.appending(component: hash)
|
||||
let fileUnarchiver = try fileArchiverFactory.makeFileUnarchiver(for: zipPath)
|
||||
let unarchivedDirectory = try fileUnarchiver.unzip()
|
||||
defer {
|
||||
try? fileUnarchiver.delete()
|
||||
}
|
||||
return xcframework
|
||||
if frameworkPath(in: unarchivedDirectory) == nil {
|
||||
throw CacheRemoteStorageError.frameworkNotFound(hash: hash)
|
||||
}
|
||||
|
||||
private func fileArchiver(for path: AbsolutePath) -> FileArchiving {
|
||||
let fileArchiver = fileArchiverMap[path] ?? fileArchiverFactory.makeFileArchiver(for: path)
|
||||
fileArchiverMap[path] = fileArchiver
|
||||
return fileArchiver
|
||||
if !FileHandler.shared.exists(archiveDestination.parentDirectory) {
|
||||
try FileHandler.shared.createFolder(archiveDestination.parentDirectory)
|
||||
}
|
||||
try FileHandler.shared.move(from: unarchivedDirectory, to: archiveDestination)
|
||||
return frameworkPath(in: archiveDestination)!
|
||||
}
|
||||
|
||||
private func deleteZipArchiveCompletable(archiver: FileArchiving) -> Completable {
|
||||
|
@ -152,12 +159,4 @@ public final class CacheRemoteStorage: CacheStoring {
|
|||
return Disposables.create {}
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: - Deinit
|
||||
|
||||
deinit {
|
||||
do {
|
||||
try fileArchiverMap.values.forEach { fileArchiver in try fileArchiver.delete() }
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,6 @@ public protocol CacheStoring {
|
|||
/// It stores the xcframework at the given path in the cache.
|
||||
/// - Parameters:
|
||||
/// - hash: Hash of the target the xcframework belongs to.
|
||||
/// - xcframeworkPath: Path to the .xcframework.
|
||||
func store(hash: String, xcframeworkPath: AbsolutePath) -> Completable
|
||||
/// - paths: Path to the files that will be stored.
|
||||
func store(hash: String, paths: [AbsolutePath]) -> Completable
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ public final class DependenciesContentHasher: DependenciesContentHashing {
|
|||
self.contentHasher = contentHasher
|
||||
}
|
||||
|
||||
// MARK: - HeadersContentHashing
|
||||
// MARK: - DependenciesContentHashing
|
||||
|
||||
public func hash(dependencies: [Dependency]) throws -> String {
|
||||
let hashes = dependencies.map { try? hash(dependency: $0) }
|
||||
|
|
|
@ -5,7 +5,7 @@ import TuistCore
|
|||
import TuistSupport
|
||||
|
||||
public protocol GraphContentHashing {
|
||||
func contentHashes(for graph: TuistCore.Graph) throws -> [TargetNode: String]
|
||||
func contentHashes(for graph: TuistCore.Graph, cacheOutputType: CacheOutputType) throws -> [TargetNode: String]
|
||||
}
|
||||
|
||||
/// `GraphContentHasher`
|
||||
|
@ -24,7 +24,7 @@ public final class GraphContentHasher: 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] = [:]
|
||||
let hashableTargets = graph.targets.values.flatMap { (targets: [TargetNode]) -> [TargetNode] in
|
||||
targets.compactMap { target in
|
||||
|
@ -35,7 +35,8 @@ public final class GraphContentHasher: GraphContentHashing {
|
|||
}
|
||||
}
|
||||
let hashes = try hashableTargets.map {
|
||||
try targetContentHasher.contentHash(for: $0)
|
||||
try targetContentHasher.contentHash(for: $0,
|
||||
cacheOutputType: cacheOutputType)
|
||||
}
|
||||
return Dictionary(uniqueKeysWithValues: zip(hashableTargets, hashes))
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ public final class ResourcesContentHasher: ResourcesContentHashing {
|
|||
self.contentHasher = contentHasher
|
||||
}
|
||||
|
||||
// MARK: - ResourcesContentHashing
|
||||
|
||||
public func hash(resources: [FileElement]) throws -> String {
|
||||
let hashes = try resources.map { try contentHasher.hash(path: $0.path) }
|
||||
return try contentHasher.hash(hashes)
|
||||
|
|
|
@ -16,10 +16,10 @@ public final class SettingsContentHasher: SettingsContentHashing {
|
|||
self.contentHasher = contentHasher
|
||||
}
|
||||
|
||||
// MARK: - InfoPlistContentHashing
|
||||
// MARK: - SettingsContentHashing
|
||||
|
||||
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 defaultSettingsHash = try hash(settings.defaultSettings)
|
||||
return try contentHasher.hash([baseSettingsHash, configurationHash, defaultSettingsHash])
|
||||
|
@ -39,12 +39,15 @@ public final class SettingsContentHasher: SettingsContentHashing {
|
|||
return try contentHasher.hash(configurationHashes)
|
||||
}
|
||||
|
||||
private func hash(_ settingsDictionary: SettingsDictionary) -> String {
|
||||
settingsDictionary.map { "\($0):\($1.normalize())" }.joined(separator: "-")
|
||||
private func hash(_ settingsDictionary: SettingsDictionary) throws -> String {
|
||||
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 {
|
||||
var configurationHash = hash(configuration.settings)
|
||||
var configurationHash = try hash(configuration.settings)
|
||||
if let xcconfigPath = configuration.xcconfig {
|
||||
let xcconfigHash = try contentHasher.hash(path: xcconfigPath)
|
||||
configurationHash += xcconfigHash
|
||||
|
|
|
@ -4,7 +4,7 @@ import TuistCore
|
|||
import TuistSupport
|
||||
|
||||
public protocol TargetContentHashing {
|
||||
func contentHash(for target: TargetNode) throws -> String
|
||||
func contentHash(for target: TargetNode, cacheOutputType: CacheOutputType) throws -> String
|
||||
}
|
||||
|
||||
/// `TargetContentHasher`
|
||||
|
@ -14,6 +14,7 @@ public final class TargetContentHasher: TargetContentHashing {
|
|||
private let coreDataModelsContentHasher: CoreDataModelsContentHashing
|
||||
private let sourceFilesContentHasher: SourceFilesContentHashing
|
||||
private let targetActionsContentHasher: TargetActionsContentHashing
|
||||
private let targetScriptsContentHasher: TargetScriptsContentHashing
|
||||
private let resourcesContentHasher: ResourcesContentHashing
|
||||
private let headersContentHasher: HeadersContentHashing
|
||||
private let deploymentTargetContentHasher: DeploymentTargetContentHashing
|
||||
|
@ -28,6 +29,7 @@ public final class TargetContentHasher: TargetContentHashing {
|
|||
contentHasher: contentHasher,
|
||||
sourceFilesContentHasher: SourceFilesContentHasher(contentHasher: contentHasher),
|
||||
targetActionsContentHasher: TargetActionsContentHasher(contentHasher: contentHasher),
|
||||
targetScriptsContentHasher: TargetScriptsContentHasher(contentHasher: contentHasher),
|
||||
coreDataModelsContentHasher: CoreDataModelsContentHasher(contentHasher: contentHasher),
|
||||
resourcesContentHasher: ResourcesContentHasher(contentHasher: contentHasher),
|
||||
headersContentHasher: HeadersContentHasher(contentHasher: contentHasher),
|
||||
|
@ -42,6 +44,7 @@ public final class TargetContentHasher: TargetContentHashing {
|
|||
contentHasher: ContentHashing,
|
||||
sourceFilesContentHasher: SourceFilesContentHashing,
|
||||
targetActionsContentHasher: TargetActionsContentHashing,
|
||||
targetScriptsContentHasher: TargetScriptsContentHashing,
|
||||
coreDataModelsContentHasher: CoreDataModelsContentHashing,
|
||||
resourcesContentHasher: ResourcesContentHashing,
|
||||
headersContentHasher: HeadersContentHashing,
|
||||
|
@ -54,6 +57,7 @@ public final class TargetContentHasher: TargetContentHashing {
|
|||
self.sourceFilesContentHasher = sourceFilesContentHasher
|
||||
self.coreDataModelsContentHasher = coreDataModelsContentHasher
|
||||
self.targetActionsContentHasher = targetActionsContentHasher
|
||||
self.targetScriptsContentHasher = targetScriptsContentHasher
|
||||
self.resourcesContentHasher = resourcesContentHasher
|
||||
self.headersContentHasher = headersContentHasher
|
||||
self.deploymentTargetContentHasher = deploymentTargetContentHasher
|
||||
|
@ -64,12 +68,13 @@ public final class TargetContentHasher: 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 sourcesHash = try sourceFilesContentHasher.hash(sources: target.sources)
|
||||
let resourcesHash = try resourcesContentHasher.hash(resources: target.resources)
|
||||
let coreDataModelHash = try coreDataModelsContentHasher.hash(coreDataModels: target.coreDataModels)
|
||||
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 environmentHash = try contentHasher.hash(target.environment)
|
||||
var stringsToHash = [target.name,
|
||||
|
@ -82,6 +87,7 @@ public final class TargetContentHasher: TargetContentHashing {
|
|||
resourcesHash,
|
||||
coreDataModelHash,
|
||||
targetActionsHash,
|
||||
targetScriptsHash,
|
||||
environmentHash]
|
||||
if let headers = target.headers {
|
||||
let headersHash = try headersContentHasher.hash(headers: headers)
|
||||
|
@ -104,6 +110,7 @@ public final class TargetContentHasher: TargetContentHashing {
|
|||
stringsToHash.append(settingsHash)
|
||||
}
|
||||
|
||||
stringsToHash.append(cacheOutputType.description)
|
||||
return try contentHasher.hash(stringsToHash)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
"""
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
/// - Parameters:
|
||||
/// - 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.
|
||||
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 {
|
||||
struct VisitedXCFramework {
|
||||
struct VisitedPrecompiledFramework {
|
||||
let path: AbsolutePath?
|
||||
}
|
||||
|
||||
|
@ -25,54 +25,66 @@ class CacheGraphMutator: CacheGraphMutating {
|
|||
/// Utility to parse an .xcframework from the filesystem and load it into memory.
|
||||
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.
|
||||
/// - 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
|
||||
}
|
||||
|
||||
// MARK: - CacheGraphMapping
|
||||
|
||||
public func map(graph: Graph, xcframeworks: [TargetNode: AbsolutePath], sources: Set<String>) throws -> Graph {
|
||||
var visitedXCFrameworkPaths: [TargetNode: VisitedXCFramework?] = [:]
|
||||
var loadedXCFrameworks: [AbsolutePath: XCFrameworkNode] = [:]
|
||||
public func map(graph: Graph, precompiledFrameworks: [TargetNode: AbsolutePath], sources: Set<String>) throws -> Graph {
|
||||
var visitedPrecompiledFrameworkPaths: [TargetNode: VisitedPrecompiledFramework?] = [:]
|
||||
var loadedPrecompiledNodes: [AbsolutePath: PrecompiledNode] = [:]
|
||||
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) }
|
||||
var sourceTargets: Set<TargetNode> = Set(userSpecifiedSourceTargets)
|
||||
|
||||
try (userSpecifiedSourceTargets + userSpecifiedSourceTestTargets)
|
||||
.forEach { try visit(targetNode: $0,
|
||||
xcframeworks: xcframeworks,
|
||||
precompiledFrameworks: precompiledFrameworks,
|
||||
sources: sources,
|
||||
sourceTargets: &sourceTargets,
|
||||
visitedXCFrameworkPaths: &visitedXCFrameworkPaths,
|
||||
loadedXCFrameworks: &loadedXCFrameworks) }
|
||||
visitedPrecompiledFrameworkPaths: &visitedPrecompiledFrameworkPaths,
|
||||
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,
|
||||
xcframeworks: [TargetNode: AbsolutePath],
|
||||
precompiledFrameworks: [TargetNode: AbsolutePath],
|
||||
sources: Set<String>,
|
||||
sourceTargets: inout Set<TargetNode>,
|
||||
visitedXCFrameworkPaths: inout [TargetNode: VisitedXCFramework?],
|
||||
loadedXCFrameworks: inout [AbsolutePath: XCFrameworkNode]) throws
|
||||
visitedPrecompiledFrameworkPaths: inout [TargetNode: VisitedPrecompiledFramework?],
|
||||
loadedPrecompiledNodes: inout [AbsolutePath: PrecompiledNode]) throws
|
||||
{
|
||||
sourceTargets.formUnion([targetNode])
|
||||
targetNode.dependencies = try mapDependencies(targetNode.dependencies,
|
||||
xcframeworks: xcframeworks,
|
||||
precompiledFrameworks: precompiledFrameworks,
|
||||
sources: sources,
|
||||
sourceTargets: &sourceTargets,
|
||||
visitedXCFrameworkPaths: &visitedXCFrameworkPaths,
|
||||
loadedXCFrameworks: &loadedXCFrameworks)
|
||||
visitedPrecompiledFrameworkPaths: &visitedPrecompiledFrameworkPaths,
|
||||
loadedPrecompiledFrameworks: &loadedPrecompiledNodes)
|
||||
}
|
||||
|
||||
// swiftlint:disable line_length
|
||||
fileprivate func mapDependencies(_ dependencies: [GraphNode],
|
||||
xcframeworks: [TargetNode: AbsolutePath],
|
||||
precompiledFrameworks: [TargetNode: AbsolutePath],
|
||||
sources: Set<String>,
|
||||
sourceTargets: inout Set<TargetNode>,
|
||||
visitedXCFrameworkPaths: inout [TargetNode: VisitedXCFramework?],
|
||||
loadedXCFrameworks: inout [AbsolutePath: XCFrameworkNode]) throws -> [GraphNode]
|
||||
visitedPrecompiledFrameworkPaths: inout [TargetNode: VisitedPrecompiledFramework?],
|
||||
loadedPrecompiledFrameworks: inout [AbsolutePath: PrecompiledNode]) throws -> [GraphNode]
|
||||
{
|
||||
var newDependencies: [GraphNode] = []
|
||||
try dependencies.forEach { dependency in
|
||||
|
@ -82,114 +94,80 @@ class CacheGraphMutator: CacheGraphMutating {
|
|||
return
|
||||
}
|
||||
|
||||
// If the target cannot be replaced with its associated .xcframework we return
|
||||
guard !sources.contains(targetDependency.target.name), let xcframeworkPath = xcframeworkPath(target: targetDependency,
|
||||
xcframeworks: xcframeworks,
|
||||
visitedXCFrameworkPaths: &visitedXCFrameworkPaths)
|
||||
// If the target cannot be replaced with its associated .(xc)framework we return
|
||||
guard !sources.contains(targetDependency.target.name), let precompiledFrameworkPath = precompiledFrameworkPath(target: targetDependency,
|
||||
precompiledFrameworks: precompiledFrameworks,
|
||||
visitedPrecompiledFrameworkPaths: &visitedPrecompiledFrameworkPaths)
|
||||
else {
|
||||
sourceTargets.formUnion([targetDependency])
|
||||
targetDependency.dependencies = try mapDependencies(targetDependency.dependencies,
|
||||
xcframeworks: xcframeworks,
|
||||
precompiledFrameworks: precompiledFrameworks,
|
||||
sources: sources,
|
||||
sourceTargets: &sourceTargets,
|
||||
visitedXCFrameworkPaths: &visitedXCFrameworkPaths,
|
||||
loadedXCFrameworks: &loadedXCFrameworks)
|
||||
visitedPrecompiledFrameworkPaths: &visitedPrecompiledFrameworkPaths,
|
||||
loadedPrecompiledFrameworks: &loadedPrecompiledFrameworks)
|
||||
newDependencies.append(targetDependency)
|
||||
return
|
||||
}
|
||||
|
||||
// We load the xcframework
|
||||
let xcframework = try self.loadXCFramework(path: xcframeworkPath, loadedXCFrameworks: &loadedXCFrameworks)
|
||||
// We load the .framework (or fallback on .xcframework)
|
||||
let precompiledFramework: PrecompiledNode = try loadPrecompiledFramework(path: precompiledFrameworkPath, loadedPrecompiledFrameworks: &loadedPrecompiledFrameworks)
|
||||
|
||||
try mapDependencies(targetDependency.dependencies,
|
||||
xcframeworks: xcframeworks,
|
||||
precompiledFrameworks: precompiledFrameworks,
|
||||
sources: sources,
|
||||
sourceTargets: &sourceTargets,
|
||||
visitedXCFrameworkPaths: &visitedXCFrameworkPaths,
|
||||
loadedXCFrameworks: &loadedXCFrameworks).forEach { dependency in
|
||||
visitedPrecompiledFrameworkPaths: &visitedPrecompiledFrameworkPaths,
|
||||
loadedPrecompiledFrameworks: &loadedPrecompiledFrameworks).forEach { dependency in
|
||||
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 {
|
||||
xcframework.add(dependency: XCFrameworkNode.Dependency.xcframework(xcframeworkDependency))
|
||||
precompiledFramework.add(dependency: PrecompiledNode.Dependency.xcframework(xcframeworkDependency))
|
||||
} else {
|
||||
// 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
|
||||
}
|
||||
|
||||
func treeShake(graph: Graph, sourceTargets: Set<TargetNode>) -> Graph {
|
||||
let targetReferences = Set(sourceTargets.map { TargetReference(projectPath: $0.path, name: $0.name) })
|
||||
|
||||
let projects = graph.projects.compactMap { (project) -> Project? in
|
||||
let targets: [Target] = project.targets.compactMap { (target) -> Target? in
|
||||
guard let targetNode = graph.target(path: project.path, name: target.name) else { return nil }
|
||||
guard sourceTargets.contains(targetNode) else { return nil }
|
||||
return target
|
||||
}
|
||||
if targets.isEmpty {
|
||||
return nil
|
||||
fileprivate func loadPrecompiledFramework(path: AbsolutePath, loadedPrecompiledFrameworks: inout [AbsolutePath: PrecompiledNode]) throws -> PrecompiledNode {
|
||||
if let cachedFramework = loadedPrecompiledFrameworks[path] {
|
||||
return cachedFramework
|
||||
} else if let framework = try? frameworkLoader.load(path: path) {
|
||||
loadedPrecompiledFrameworks[path] = framework
|
||||
return framework
|
||||
} 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)
|
||||
loadedXCFrameworks[path] = xcframework
|
||||
loadedPrecompiledFrameworks[path] = xcframework
|
||||
return xcframework
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func xcframeworkPath(target: TargetNode,
|
||||
xcframeworks: [TargetNode: AbsolutePath],
|
||||
visitedXCFrameworkPaths: inout [TargetNode: VisitedXCFramework?]) -> AbsolutePath?
|
||||
fileprivate func precompiledFrameworkPath(target: TargetNode,
|
||||
precompiledFrameworks: [TargetNode: AbsolutePath],
|
||||
visitedPrecompiledFrameworkPaths: inout [TargetNode: VisitedPrecompiledFramework?]) -> AbsolutePath?
|
||||
{
|
||||
// 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
|
||||
if xcframeworks[target] == nil {
|
||||
visitedXCFrameworkPaths[target] = VisitedXCFramework(path: nil)
|
||||
// The target doesn't have a cached .(xc)framework
|
||||
if precompiledFrameworks[target] == nil {
|
||||
visitedPrecompiledFrameworkPaths[target] = VisitedPrecompiledFramework(path: nil)
|
||||
return nil
|
||||
}
|
||||
// The target can be replaced
|
||||
else if let path = xcframeworks[target],
|
||||
target.targetDependencies.allSatisfy({ xcframeworkPath(target: $0, xcframeworks: xcframeworks,
|
||||
visitedXCFrameworkPaths: &visitedXCFrameworkPaths) != nil })
|
||||
else if let path = precompiledFrameworks[target],
|
||||
target.targetDependencies.allSatisfy({ precompiledFrameworkPath(target: $0,
|
||||
precompiledFrameworks: precompiledFrameworks,
|
||||
visitedPrecompiledFrameworkPaths: &visitedPrecompiledFrameworkPaths) != nil })
|
||||
{
|
||||
visitedXCFrameworkPaths[target] = VisitedXCFramework(path: path)
|
||||
visitedPrecompiledFrameworkPaths[target] = VisitedPrecompiledFramework(path: path)
|
||||
return path
|
||||
} else {
|
||||
visitedXCFrameworkPaths[target] = VisitedXCFramework(path: nil)
|
||||
visitedPrecompiledFrameworkPaths[target] = VisitedPrecompiledFramework(path: nil)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ public class CacheMapper: GraphMapping {
|
|||
/// Cache graph mapper.
|
||||
private let cacheGraphMutator: CacheGraphMutating
|
||||
|
||||
/// Configuration object
|
||||
/// Configuration object.
|
||||
private let config: Config
|
||||
|
||||
/// 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.
|
||||
private let queue: DispatchQueue
|
||||
|
||||
/// The type of artifact that the hasher is configured with.
|
||||
private let cacheOutputType: CacheOutputType
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public convenience init(config: Config,
|
||||
cacheStorageProvider: CacheStorageProviding,
|
||||
sources: Set<String>)
|
||||
sources: Set<String>,
|
||||
cacheOutputType: CacheOutputType)
|
||||
{
|
||||
self.init(config: config,
|
||||
cache: Cache(storageProvider: cacheStorageProvider),
|
||||
graphContentHasher: GraphContentHasher(),
|
||||
sources: sources)
|
||||
sources: sources,
|
||||
cacheOutputType: cacheOutputType)
|
||||
}
|
||||
|
||||
init(config: Config,
|
||||
cache: CacheStoring,
|
||||
graphContentHasher: GraphContentHashing,
|
||||
sources: Set<String>,
|
||||
cacheOutputType: CacheOutputType,
|
||||
cacheGraphMutator: CacheGraphMutating = CacheGraphMutator(),
|
||||
queue: DispatchQueue = CacheMapper.dispatchQueue())
|
||||
{
|
||||
|
@ -51,6 +57,7 @@ public class CacheMapper: GraphMapping {
|
|||
self.queue = queue
|
||||
self.cacheGraphMutator = cacheGraphMutator
|
||||
self.sources = sources
|
||||
self.cacheOutputType = cacheOutputType
|
||||
}
|
||||
|
||||
// MARK: - GraphMapping
|
||||
|
@ -70,7 +77,8 @@ public class CacheMapper: GraphMapping {
|
|||
fileprivate func hashes(graph: Graph) -> Single<[TargetNode: String]> {
|
||||
Single.create { (observer) -> Disposable in
|
||||
do {
|
||||
let hashes = try self.graphContentHasher.contentHashes(for: graph)
|
||||
let hashes = try self.graphContentHasher.contentHashes(for: graph,
|
||||
cacheOutputType: self.cacheOutputType)
|
||||
observer(.success(hashes))
|
||||
} catch {
|
||||
observer(.error(error))
|
||||
|
@ -83,7 +91,7 @@ public class CacheMapper: GraphMapping {
|
|||
fileprivate func map(graph: Graph, hashes: [TargetNode: String], sources: Set<String>) -> Single<Graph> {
|
||||
fetch(hashes: hashes).map { xcframeworkPaths in
|
||||
try self.cacheGraphMutator.map(graph: graph,
|
||||
xcframeworks: xcframeworkPaths,
|
||||
precompiledFrameworks: xcframeworkPaths,
|
||||
sources: sources)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"))
|
||||
}
|
||||
}
|
|
@ -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"))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,10 +30,10 @@ public final class MockCacheStorage: CacheStoring {
|
|||
}
|
||||
}
|
||||
|
||||
var storeStub: ((_ hash: String, _ xcframeworkPath: AbsolutePath) -> Void)?
|
||||
public func store(hash: String, xcframeworkPath: AbsolutePath) -> Completable {
|
||||
var storeStub: ((_ hash: String, _ paths: [AbsolutePath]) -> Void)?
|
||||
public func store(hash: String, paths: [AbsolutePath]) -> Completable {
|
||||
if let storeStub = storeStub {
|
||||
storeStub(hash, xcframeworkPath)
|
||||
storeStub(hash, paths)
|
||||
}
|
||||
return Completable.empty()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,23 +3,23 @@ import TuistCore
|
|||
@testable import TuistCache
|
||||
|
||||
public final class MockGraphContentHasher: GraphContentHashing {
|
||||
public init() {}
|
||||
|
||||
public var invokedContentHashes = false
|
||||
public var invokedContentHashesCount = 0
|
||||
public var invokedContentHashesParameters: (graph: TuistCore.Graph, Void)?
|
||||
public var invokedContentHashesParametersList = [(graph: TuistCore.Graph, Void)]()
|
||||
public var invokedContentHashesParameters: (graph: TuistCore.Graph, cacheOutputType: CacheOutputType)?
|
||||
public var invokedContentHashesParametersList = [(graph: TuistCore.Graph, cacheOutputType: CacheOutputType)]()
|
||||
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
|
||||
invokedContentHashesCount += 1
|
||||
invokedContentHashesParameters = (graph, ())
|
||||
invokedContentHashesParametersList.append((graph, ()))
|
||||
invokedContentHashesParameters = (graph, cacheOutputType)
|
||||
invokedContentHashesParametersList.append((graph, cacheOutputType))
|
||||
if let error = stubbedContentHashesError {
|
||||
throw error
|
||||
}
|
||||
return contentHashesStub
|
||||
return stubbedContentHashesResult
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,11 @@ import RxSwift
|
|||
import TSCBasic
|
||||
import TuistSupport
|
||||
|
||||
public enum XcodeBuildDestination: Equatable {
|
||||
case device(String)
|
||||
case mac
|
||||
}
|
||||
|
||||
public protocol XcodeBuildControlling {
|
||||
/// Returns an observable to build the given project using xcodebuild.
|
||||
/// - Parameters:
|
||||
|
@ -15,6 +20,20 @@ public protocol XcodeBuildControlling {
|
|||
clean: Bool,
|
||||
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.
|
||||
/// - Parameters:
|
||||
/// - target: The project or workspace to be archived.
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,7 +33,10 @@ public class Graph: Encodable, Equatable {
|
|||
/// The entry nodes of the graph.
|
||||
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]
|
||||
|
||||
/// 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.
|
||||
public let targets: [AbsolutePath: [TargetNode]]
|
||||
|
||||
/// Schemes of the graph
|
||||
public var schemes: [Scheme] {
|
||||
projects.flatMap(\.schemes) + workspace.schemes
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
convenience init(name: String, entryPath: AbsolutePath, cache: GraphLoaderCaching, entryNodes: [GraphNode]) {
|
||||
self.init(name: name,
|
||||
convenience init(
|
||||
name: String,
|
||||
entryPath: AbsolutePath,
|
||||
cache: GraphLoaderCaching,
|
||||
entryNodes: [GraphNode],
|
||||
workspace: Workspace
|
||||
) {
|
||||
self.init(
|
||||
name: name,
|
||||
entryPath: entryPath,
|
||||
entryNodes: entryNodes,
|
||||
workspace: workspace,
|
||||
projects: Array(cache.projects.values),
|
||||
cocoapods: Array(cache.cocoapodsNodes.values),
|
||||
packages: Array(cache.packages.flatMap { $0.value }),
|
||||
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,
|
||||
entryNodes: [GraphNode],
|
||||
workspace: Workspace,
|
||||
projects: [Project],
|
||||
cocoapods: [CocoaPodsNode],
|
||||
packages: [PackageNode],
|
||||
precompiled: [PrecompiledNode],
|
||||
targets: [AbsolutePath: [TargetNode]])
|
||||
{
|
||||
targets: [AbsolutePath: [TargetNode]]
|
||||
) {
|
||||
self.name = name
|
||||
self.entryPath = entryPath
|
||||
self.entryNodes = entryNodes
|
||||
self.workspace = workspace
|
||||
self.projects = projects
|
||||
self.cocoapods = cocoapods
|
||||
self.packages = packages
|
||||
|
@ -186,7 +206,7 @@ public class Graph: Encodable, Equatable {
|
|||
/// - Parameters:
|
||||
/// - path: Path to the directory where the project that defines the target is located.
|
||||
/// - 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 {
|
||||
return []
|
||||
}
|
||||
|
@ -205,6 +225,13 @@ public class Graph: Encodable, Equatable {
|
|||
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 {
|
||||
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) {
|
||||
references.subtract(try embeddableFrameworks(path: hostApp.path, name: hostApp.name))
|
||||
} else {
|
||||
references.subtract(precompiledFrameworks)
|
||||
references = []
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -387,7 +414,7 @@ public class Graph: Encodable, Equatable {
|
|||
/// - Parameter project: Project whose dependency references will be returned.
|
||||
public func allDependencyReferences(for project: Project) throws -> [GraphDependencyReference] {
|
||||
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 {
|
||||
|
@ -419,6 +446,14 @@ public class Graph: Encodable, Equatable {
|
|||
.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
|
||||
/// and explores as far as possible along each branch before backtracking.
|
||||
///
|
||||
|
@ -475,7 +510,7 @@ public class Graph: Encodable, Equatable {
|
|||
stack.push(child)
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
@ -531,31 +566,60 @@ public class Graph: Encodable, Equatable {
|
|||
} ?? 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.
|
||||
/// - Parameter projects: Projects to be set to the copy.
|
||||
public func with(projects: [Project]) -> Graph {
|
||||
Graph(name: name,
|
||||
Graph(
|
||||
name: name,
|
||||
entryPath: entryPath,
|
||||
entryNodes: entryNodes,
|
||||
workspace: workspace,
|
||||
projects: projects,
|
||||
cocoapods: cocoapods,
|
||||
packages: packages,
|
||||
precompiled: precompiled,
|
||||
targets: targets)
|
||||
targets: targets
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a copy of the graph with the given targets.
|
||||
/// - Parameter targets: Targets to be set to the copy.
|
||||
/// - Returns: New graph with the given targets.
|
||||
public func with(targets: [AbsolutePath: [TargetNode]]) -> Graph {
|
||||
Graph(name: name,
|
||||
Graph(
|
||||
name: name,
|
||||
entryPath: entryPath,
|
||||
entryNodes: entryNodes,
|
||||
workspace: workspace,
|
||||
projects: projects,
|
||||
cocoapods: cocoapods,
|
||||
packages: packages,
|
||||
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) {
|
||||
|
@ -587,8 +651,8 @@ public class Graph: Encodable, Equatable {
|
|||
stack.push(child)
|
||||
}
|
||||
} else if let frameworkNode = node as? FrameworkNode {
|
||||
for child in frameworkNode.dependencies where !visited.contains(child) {
|
||||
stack.push(child)
|
||||
for child in frameworkNode.dependencies where !visited.contains(child.node) {
|
||||
stack.push(child.node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -600,11 +664,6 @@ public class Graph: Encodable, Equatable {
|
|||
.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 {
|
||||
targetNode.target.product.isStatic
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ public protocol GraphLoading: AnyObject {
|
|||
|
||||
/// Loads the graph for the workspace in the given directory.
|
||||
/// - 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.
|
||||
///
|
||||
|
@ -64,15 +64,19 @@ public class GraphLoader: GraphLoading {
|
|||
let entryNodes: [GraphNode] = try project.targets.map { target in
|
||||
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,
|
||||
cache: graphLoaderCache,
|
||||
entryNodes: entryNodes)
|
||||
entryNodes: entryNodes,
|
||||
workspace: workspace
|
||||
)
|
||||
return (graph, project)
|
||||
}
|
||||
|
||||
public func loadWorkspace(path: AbsolutePath) throws -> (Graph, Workspace) {
|
||||
public func loadWorkspace(path: AbsolutePath) throws -> Graph {
|
||||
let graphLoaderCache = GraphLoaderCache()
|
||||
let graphCircularDetector = GraphCircularDetector()
|
||||
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,
|
||||
cache: graphLoaderCache,
|
||||
entryNodes: entryNodes)
|
||||
return (graph, workspace)
|
||||
entryNodes: entryNodes,
|
||||
workspace: workspace
|
||||
)
|
||||
return graph
|
||||
}
|
||||
|
||||
public func loadConfig(path: AbsolutePath) throws -> Config {
|
||||
|
|
|
@ -34,4 +34,8 @@ public final class GraphTraverser: GraphTraversing {
|
|||
public func directStaticDependencies(path: AbsolutePath, name: String) -> [GraphDependencyReference] {
|
||||
graph.staticDependencies(path: path, name: name)
|
||||
}
|
||||
|
||||
public func appClipsDependency(path: AbsolutePath, name: String) -> Target? {
|
||||
graph.appClipsDependency(path: path, name: name).map { $0.target }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,10 +24,16 @@ public class TargetNodeGraphMapper: GraphMapping {
|
|||
map(node: $0, mappedCache: &mappedCache, cache: cache)
|
||||
}
|
||||
|
||||
return (Graph(name: graph.name,
|
||||
return (
|
||||
Graph(
|
||||
name: graph.name,
|
||||
entryPath: graph.entryPath,
|
||||
cache: cache,
|
||||
entryNodes: updatedNodes), [])
|
||||
entryNodes: updatedNodes,
|
||||
workspace: graph.workspace
|
||||
),
|
||||
[]
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -15,9 +15,6 @@ public class FrameworkNode: PrecompiledNode {
|
|||
/// The architectures supported by the binary.
|
||||
public let architectures: [BinaryArchitecture]
|
||||
|
||||
/// Framework dependencies.
|
||||
public let dependencies: [FrameworkNode]
|
||||
|
||||
/// Returns the type of product.
|
||||
public var product: Product {
|
||||
if linking == .static {
|
||||
|
@ -42,14 +39,13 @@ public class FrameworkNode: PrecompiledNode {
|
|||
bcsymbolmapPaths: [AbsolutePath],
|
||||
linking: BinaryLinking,
|
||||
architectures: [BinaryArchitecture] = [],
|
||||
dependencies: [FrameworkNode] = [])
|
||||
dependencies: [Dependency] = [])
|
||||
{
|
||||
self.dsymPath = dsymPath
|
||||
self.bcsymbolmapPaths = bcsymbolmapPaths
|
||||
self.linking = linking
|
||||
self.architectures = architectures
|
||||
self.dependencies = dependencies
|
||||
super.init(path: path)
|
||||
super.init(path: path, dependencies: dependencies)
|
||||
}
|
||||
|
||||
override public func encode(to encoder: Encoder) throws {
|
||||
|
|
|
@ -2,7 +2,7 @@ import Foundation
|
|||
import TSCBasic
|
||||
import TuistSupport
|
||||
|
||||
public class GraphNode: Equatable, Hashable, Encodable, CustomStringConvertible {
|
||||
public class GraphNode: Equatable, Hashable, Encodable, CustomStringConvertible, CustomDebugStringConvertible {
|
||||
// MARK: - Attributes
|
||||
|
||||
/// The path to the node.
|
||||
|
@ -14,6 +14,9 @@ public class GraphNode: Equatable, Hashable, Encodable, CustomStringConvertible
|
|||
/// The description of the node.
|
||||
public var description: String { name }
|
||||
|
||||
/// The debug description of the node.
|
||||
public var debugDescription: String { name }
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public init(path: AbsolutePath, name: String) {
|
||||
|
|
|
@ -3,11 +3,37 @@ import TSCBasic
|
|||
import TuistSupport
|
||||
|
||||
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
|
||||
/// Alamofire.framework -> Alamofire
|
||||
/// libAlamofire.a -> libAlamofire
|
||||
let name = String(path.components.last!.split(separator: ".").first!)
|
||||
self.dependencies = dependencies
|
||||
super.init(path: path, name: name)
|
||||
}
|
||||
|
||||
|
@ -29,4 +55,25 @@ public class PrecompiledNode: GraphNode {
|
|||
case product
|
||||
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: ", "))]"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,14 @@ public class SDKNode: GraphNode {
|
|||
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 {
|
||||
let sdkRootPath: AbsolutePath
|
||||
if name == SDKNode.xctestFrameworkName {
|
||||
|
|
|
@ -9,6 +9,9 @@ public class TargetNode: GraphNode {
|
|||
public let target: Target
|
||||
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 {
|
||||
case path
|
||||
case name
|
||||
|
@ -23,11 +26,13 @@ public class TargetNode: GraphNode {
|
|||
|
||||
public init(project: Project,
|
||||
target: Target,
|
||||
dependencies: [GraphNode])
|
||||
dependencies: [GraphNode],
|
||||
prune: Bool = false)
|
||||
{
|
||||
self.project = project
|
||||
self.target = target
|
||||
self.dependencies = dependencies
|
||||
self.prune = prune
|
||||
super.init(path: project.path, name: target.name)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,28 +3,6 @@ import TSCBasic
|
|||
import TuistSupport
|
||||
|
||||
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.
|
||||
enum XCFrameworkNodeCodingKeys: String, CodingKey {
|
||||
case linking
|
||||
|
@ -43,9 +21,6 @@ public class XCFrameworkNode: PrecompiledNode {
|
|||
/// Returns the type of linking
|
||||
public let linking: BinaryLinking
|
||||
|
||||
/// List of other .xcframeworks this xcframework depends on.
|
||||
public private(set) var dependencies: [Dependency]
|
||||
|
||||
/// Path to the binary.
|
||||
override public var binaryPath: AbsolutePath { primaryBinaryPath }
|
||||
|
||||
|
@ -65,8 +40,7 @@ public class XCFrameworkNode: PrecompiledNode {
|
|||
self.infoPlist = infoPlist
|
||||
self.linking = linking
|
||||
self.primaryBinaryPath = primaryBinaryPath
|
||||
self.dependencies = dependencies
|
||||
super.init(path: path)
|
||||
super.init(path: path, dependencies: dependencies)
|
||||
}
|
||||
|
||||
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(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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,4 +41,10 @@ public protocol GraphTraversing {
|
|||
/// - path: Path to the directory where the project that defines the target is located.
|
||||
/// - name: Name of the target.
|
||||
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?
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import Foundation
|
|||
import TSCBasic
|
||||
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
|
||||
/// in the same framework directory.
|
||||
/// - Parameter frameworkPath: Path to the .framework directory.
|
||||
|
@ -18,14 +18,14 @@ protocol FrameworkMetadataProviding: PrecompiledMetadataProviding {
|
|||
func product(frameworkPath: AbsolutePath) throws -> Product
|
||||
}
|
||||
|
||||
final class FrameworkMetadataProvider: PrecompiledMetadataProvider, FrameworkMetadataProviding {
|
||||
func dsymPath(frameworkPath: AbsolutePath) -> AbsolutePath? {
|
||||
public final class FrameworkMetadataProvider: PrecompiledMetadataProvider, FrameworkMetadataProviding {
|
||||
public func dsymPath(frameworkPath: AbsolutePath) -> AbsolutePath? {
|
||||
let path = AbsolutePath("\(frameworkPath.pathString).dSYM")
|
||||
if FileHandler.shared.exists(path) { return path }
|
||||
return nil
|
||||
}
|
||||
|
||||
func bcsymbolmapPaths(frameworkPath: AbsolutePath) throws -> [AbsolutePath] {
|
||||
public func bcsymbolmapPaths(frameworkPath: AbsolutePath) throws -> [AbsolutePath] {
|
||||
let binaryPath = FrameworkNode.binaryPath(frameworkPath: frameworkPath)
|
||||
let uuids = try self.uuids(binaryPath: binaryPath)
|
||||
return uuids
|
||||
|
@ -34,7 +34,7 @@ final class FrameworkMetadataProvider: PrecompiledMetadataProvider, FrameworkMet
|
|||
.sorted()
|
||||
}
|
||||
|
||||
func product(frameworkPath: AbsolutePath) throws -> Product {
|
||||
public func product(frameworkPath: AbsolutePath) throws -> Product {
|
||||
let binaryPath = FrameworkNode.binaryPath(frameworkPath: frameworkPath)
|
||||
switch try linking(binaryPath: binaryPath) {
|
||||
case .dynamic:
|
||||
|
|
|
@ -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.
|
||||
/// - Parameter binaryPath: Binary path.
|
||||
func architectures(binaryPath: AbsolutePath) throws -> [BinaryArchitecture]
|
||||
|
@ -46,10 +46,10 @@ protocol PrecompiledMetadataProviding {
|
|||
func uuids(binaryPath: AbsolutePath) throws -> Set<UUID>
|
||||
}
|
||||
|
||||
class PrecompiledMetadataProvider: PrecompiledMetadataProviding {
|
||||
public class PrecompiledMetadataProvider: PrecompiledMetadataProviding {
|
||||
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 regexes = [
|
||||
// Non-fat file: path is architecture: x86_64
|
||||
|
@ -70,12 +70,12 @@ class PrecompiledMetadataProvider: PrecompiledMetadataProviding {
|
|||
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() ?? ""
|
||||
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])
|
||||
// UUIDs are letters, decimals, or hyphens.
|
||||
var uuidCharacterSet = CharacterSet()
|
||||
|
|
|
@ -13,3 +13,11 @@ public enum BinaryArchitecture: String, Codable {
|
|||
public enum BinaryLinking: String, Codable {
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,10 @@ public struct Config: Equatable, Hashable {
|
|||
public enum GenerationOption: Hashable, Equatable {
|
||||
case xcodeProjectName(String)
|
||||
case organizationName(String)
|
||||
case developmentRegion(String)
|
||||
case disableAutogeneratedSchemes
|
||||
case disableSynthesizedResourceAccessors
|
||||
case disableShowEnvironmentVarsInScriptPhases
|
||||
}
|
||||
|
||||
/// Generation options.
|
||||
|
@ -23,9 +25,12 @@ public struct Config: Equatable, Hashable {
|
|||
/// Cloud configuration.
|
||||
public let cloud: Cloud?
|
||||
|
||||
/// The path of the config file.
|
||||
public let path: AbsolutePath?
|
||||
|
||||
/// Returns the default Tuist configuration.
|
||||
public static var `default`: Config {
|
||||
Config(compatibleXcodeVersions: .all, cloud: nil, generationOptions: [])
|
||||
Config(compatibleXcodeVersions: .all, cloud: nil, generationOptions: [], path: nil)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// - cloud: Cloud configuration.
|
||||
/// - generationOptions: Generation options.
|
||||
/// - path: The path of the config file.
|
||||
public init(compatibleXcodeVersions: CompatibleXcodeVersions,
|
||||
cloud: Cloud?,
|
||||
generationOptions: [GenerationOption])
|
||||
generationOptions: [GenerationOption],
|
||||
path: AbsolutePath?)
|
||||
{
|
||||
self.compatibleXcodeVersions = compatibleXcodeVersions
|
||||
self.cloud = cloud
|
||||
self.generationOptions = generationOptions
|
||||
self.path = path
|
||||
}
|
||||
|
||||
// MARK: - Hashable
|
||||
|
|
|
@ -7,15 +7,18 @@ public struct ExecutionAction: Equatable {
|
|||
public let title: String
|
||||
public let scriptText: String
|
||||
public let target: TargetReference?
|
||||
public let showEnvVarsInLog: Bool
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public init(title: String,
|
||||
scriptText: String,
|
||||
target: TargetReference?)
|
||||
target: TargetReference?,
|
||||
showEnvVarsInLog: Bool = true)
|
||||
{
|
||||
self.title = title
|
||||
self.scriptText = scriptText
|
||||
self.target = target
|
||||
self.showEnvVarsInLog = showEnvVarsInLog
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ public enum Product: String, CustomStringConvertible, CaseIterable, Encodable {
|
|||
// case messagesApplication = "messages_application"
|
||||
case messagesExtension = "messages_extension"
|
||||
case stickerPackExtension = "sticker_pack_extension"
|
||||
case appClip
|
||||
|
||||
public var caseValue: String {
|
||||
switch self {
|
||||
|
@ -56,6 +57,8 @@ public enum Product: String, CustomStringConvertible, CaseIterable, Encodable {
|
|||
return "messagesExtension"
|
||||
case .stickerPackExtension:
|
||||
return "stickerPackExtension"
|
||||
case .appClip:
|
||||
return "appClip"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,13 +98,15 @@ public enum Product: String, CustomStringConvertible, CaseIterable, Encodable {
|
|||
return "iMessage extension"
|
||||
case .stickerPackExtension:
|
||||
return "sticker pack extension"
|
||||
case .appClip:
|
||||
return "appClip"
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the target can be ran.
|
||||
public var runnable: Bool {
|
||||
switch self {
|
||||
case .app:
|
||||
case .app, .appClip:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
@ -126,6 +131,7 @@ public enum Product: String, CustomStringConvertible, CaseIterable, Encodable {
|
|||
base.append(.stickerPackExtension)
|
||||
// base.append(.messagesApplication)
|
||||
base.append(.messagesExtension)
|
||||
base.append(.appClip)
|
||||
}
|
||||
|
||||
if platform == .tvOS {
|
||||
|
@ -195,6 +201,12 @@ public enum Product: String, CustomStringConvertible, CaseIterable, Encodable {
|
|||
return .messagesExtension
|
||||
case .stickerPackExtension:
|
||||
return .stickerPack
|
||||
case .appClip:
|
||||
return .onDemandInstallCapableApplication
|
||||
}
|
||||
}
|
||||
|
||||
public func canHostTests() -> Bool {
|
||||
[.app, .appClip].contains(self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug
|
|||
lhs.xcodeProjPath == rhs.xcodeProjPath &&
|
||||
lhs.name == rhs.name &&
|
||||
lhs.organizationName == rhs.organizationName &&
|
||||
lhs.developmentRegion == rhs.developmentRegion &&
|
||||
lhs.targets == rhs.targets &&
|
||||
lhs.packages == rhs.packages &&
|
||||
lhs.schemes == rhs.schemes &&
|
||||
|
@ -34,6 +35,9 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug
|
|||
/// Organization name.
|
||||
public var organizationName: String?
|
||||
|
||||
/// Development region code e.g. `en`.
|
||||
public var developmentRegion: String?
|
||||
|
||||
/// Project targets.
|
||||
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.
|
||||
/// - name: Project name.
|
||||
/// - organizationName: Organization name.
|
||||
/// - developmentRegion: Development region.
|
||||
/// - settings: The settings to apply at the project level
|
||||
/// - filesGroup: The root group to place project files within
|
||||
/// - targets: The project targets
|
||||
|
@ -72,6 +77,7 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug
|
|||
xcodeProjPath: AbsolutePath,
|
||||
name: String,
|
||||
organizationName: String?,
|
||||
developmentRegion: String?,
|
||||
settings: Settings,
|
||||
filesGroup: ProjectGroup,
|
||||
targets: [Target],
|
||||
|
@ -84,6 +90,7 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug
|
|||
self.xcodeProjPath = xcodeProjPath
|
||||
self.name = name
|
||||
self.organizationName = organizationName
|
||||
self.developmentRegion = developmentRegion
|
||||
self.targets = targets
|
||||
self.packages = packages
|
||||
self.schemes = schemes
|
||||
|
@ -155,6 +162,7 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug
|
|||
xcodeProjPath: xcodeProjPath,
|
||||
name: name,
|
||||
organizationName: organizationName,
|
||||
developmentRegion: developmentRegion,
|
||||
settings: settings,
|
||||
filesGroup: filesGroup,
|
||||
targets: targets,
|
||||
|
@ -171,6 +179,7 @@ public struct Project: Hashable, Equatable, CustomStringConvertible, CustomDebug
|
|||
xcodeProjPath: xcodeProjPath,
|
||||
name: name,
|
||||
organizationName: organizationName,
|
||||
developmentRegion: developmentRegion,
|
||||
settings: settings,
|
||||
filesGroup: filesGroup,
|
||||
targets: targets,
|
||||
|
|
|
@ -47,6 +47,7 @@ public struct Target: Equatable, Hashable, Comparable {
|
|||
public var environment: [String: String]
|
||||
public var launchArguments: [String: Bool]
|
||||
public var filesGroup: ProjectGroup
|
||||
public var scripts: [TargetScript]
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
|
@ -67,7 +68,8 @@ public struct Target: Equatable, Hashable, Comparable {
|
|||
environment: [String: String] = [:],
|
||||
launchArguments: [String: Bool] = [:],
|
||||
filesGroup: ProjectGroup,
|
||||
dependencies: [Dependency] = [])
|
||||
dependencies: [Dependency] = [],
|
||||
scripts: [TargetScript] = [])
|
||||
{
|
||||
self.name = name
|
||||
self.product = product
|
||||
|
@ -87,6 +89,7 @@ public struct Target: Equatable, Hashable, Comparable {
|
|||
self.launchArguments = launchArguments
|
||||
self.filesGroup = filesGroup
|
||||
self.dependencies = dependencies
|
||||
self.scripts = scripts
|
||||
}
|
||||
|
||||
/// Target can be included in the link phase of other targets
|
||||
|
@ -116,6 +119,17 @@ public struct Target: Equatable, Hashable, Comparable {
|
|||
].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.
|
||||
public var productNameWithExtension: String {
|
||||
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.
|
||||
public var supportsSources: Bool {
|
||||
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.
|
||||
/// - Parameter path: Path to the file to be checked.
|
||||
public static func isResource(path: AbsolutePath) -> Bool {
|
||||
|
@ -263,8 +295,8 @@ extension Sequence where Element == Target {
|
|||
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] {
|
||||
filter { $0.product == .app }
|
||||
filter { $0.product == .app || $0.product == .appClip }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,12 @@ public struct TargetAction: Equatable {
|
|||
/// List of output filelist paths
|
||||
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.
|
||||
///
|
||||
/// - Parameters:
|
||||
|
@ -52,6 +58,8 @@ public struct TargetAction: Equatable {
|
|||
/// - inputFileListPaths: List of input filelist paths
|
||||
/// - outputPaths: List of output file 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,
|
||||
order: Order,
|
||||
tool: String? = nil,
|
||||
|
@ -60,7 +68,9 @@ public struct TargetAction: Equatable {
|
|||
inputPaths: [AbsolutePath] = [],
|
||||
inputFileListPaths: [AbsolutePath] = [],
|
||||
outputPaths: [AbsolutePath] = [],
|
||||
outputFileListPaths: [AbsolutePath] = [])
|
||||
outputFileListPaths: [AbsolutePath] = [],
|
||||
showEnvVarsInLog: Bool = true,
|
||||
basedOnDependencyAnalysis: Bool? = nil)
|
||||
{
|
||||
self.name = name
|
||||
self.order = order
|
||||
|
@ -71,6 +81,8 @@ public struct TargetAction: Equatable {
|
|||
self.inputFileListPaths = inputFileListPaths
|
||||
self.outputPaths = outputPaths
|
||||
self.outputFileListPaths = outputFileListPaths
|
||||
self.showEnvVarsInLog = showEnvVarsInLog
|
||||
self.basedOnDependencyAnalysis = basedOnDependencyAnalysis
|
||||
}
|
||||
|
||||
/// Returns the shell script that should be used in the target build phase.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
|
||||
public struct TestableTarget: Equatable {
|
||||
public struct TestableTarget: Equatable, Hashable {
|
||||
public let target: TargetReference
|
||||
public let isSkipped: Bool
|
||||
public let isParallelizable: Bool
|
||||
|
|
|
@ -5,11 +5,11 @@ import TuistSupport
|
|||
public struct Workspace: Equatable {
|
||||
// MARK: - Attributes
|
||||
|
||||
public let path: AbsolutePath
|
||||
public let name: String
|
||||
public let projects: [AbsolutePath]
|
||||
public let schemes: [Scheme]
|
||||
public let additionalFiles: [FileElement]
|
||||
public var path: AbsolutePath
|
||||
public var name: String
|
||||
public var projects: [AbsolutePath]
|
||||
public var schemes: [Scheme]
|
||||
public var additionalFiles: [FileElement]
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
|
@ -23,6 +23,12 @@ public struct Workspace: Equatable {
|
|||
}
|
||||
|
||||
extension Workspace {
|
||||
public func with(name: String) -> Workspace {
|
||||
var copy = self
|
||||
copy.name = name
|
||||
return copy
|
||||
}
|
||||
|
||||
public func adding(files: [AbsolutePath]) -> Workspace {
|
||||
Workspace(path: path,
|
||||
name: name,
|
||||
|
|
|
@ -28,17 +28,17 @@ public protocol FrameworkNodeLoading {
|
|||
func load(path: AbsolutePath) throws -> FrameworkNode
|
||||
}
|
||||
|
||||
final class FrameworkNodeLoader: FrameworkNodeLoading {
|
||||
public final class FrameworkNodeLoader: FrameworkNodeLoading {
|
||||
/// Framework metadata provider.
|
||||
fileprivate let frameworkMetadataProvider: FrameworkMetadataProviding
|
||||
|
||||
/// Initializes the loader with its attributes.
|
||||
/// - Parameter frameworkMetadataProvider: Framework metadata provider.
|
||||
init(frameworkMetadataProvider: FrameworkMetadataProviding = FrameworkMetadataProvider()) {
|
||||
public init(frameworkMetadataProvider: FrameworkMetadataProviding = FrameworkMetadataProvider()) {
|
||||
self.frameworkMetadataProvider = frameworkMetadataProvider
|
||||
}
|
||||
|
||||
func load(path: AbsolutePath) throws -> FrameworkNode {
|
||||
public func load(path: AbsolutePath) throws -> FrameworkNode {
|
||||
guard FileHandler.shared.exists(path) else {
|
||||
throw FrameworkNodeLoaderError.frameworkNotFound(path)
|
||||
}
|
||||
|
|
|
@ -1,38 +1,62 @@
|
|||
import Foundation
|
||||
import RxSwift
|
||||
import struct TSCUtility.Version
|
||||
import TuistSupport
|
||||
|
||||
protocol SimulatorControlling {
|
||||
public protocol SimulatorControlling {
|
||||
/// Returns the list of simulator devices that are available in the system.
|
||||
func devices() -> Single<[SimulatorDevice]>
|
||||
|
||||
/// Returns the list of simulator runtimes that are available in the system.
|
||||
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]>
|
||||
|
||||
/// 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 deviceNotFound(Platform, Version?, String?, [SimulatorDeviceAndRuntime])
|
||||
|
||||
var type: ErrorType {
|
||||
public var type: ErrorType {
|
||||
switch self {
|
||||
case .simctlError: return .abort
|
||||
case .simctlError, .deviceNotFound:
|
||||
return .abort
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
public var description: String {
|
||||
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 {
|
||||
private let jsonDecoder: JSONDecoder = JSONDecoder()
|
||||
public final class SimulatorController: SimulatorControlling {
|
||||
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"])
|
||||
.mapToString()
|
||||
.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"])
|
||||
.debug()
|
||||
.mapToString()
|
||||
.collectOutput()
|
||||
.asSingle()
|
||||
|
@ -88,7 +111,7 @@ final class SimulatorController: SimulatorControlling {
|
|||
}
|
||||
}
|
||||
|
||||
func devicesAndRuntimes() -> Single<[SimulatorDeviceAndRuntime]> {
|
||||
public func devicesAndRuntimes() -> Single<[SimulatorDeviceAndRuntime]> {
|
||||
runtimes()
|
||||
.flatMap { (runtimes) -> Single<([SimulatorDevice], [SimulatorRuntime])> in
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,44 +2,45 @@ import Foundation
|
|||
import TSCBasic
|
||||
|
||||
/// 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.
|
||||
let dataPath: AbsolutePath
|
||||
public let dataPath: AbsolutePath
|
||||
|
||||
/// Device log path.
|
||||
let logPath: AbsolutePath
|
||||
public let logPath: AbsolutePath
|
||||
|
||||
/// Device unique identifier (3A8C9673-C1FD-4E33-8EFA-AEEBF43161CC)
|
||||
let udid: String
|
||||
public let udid: String
|
||||
|
||||
/// 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-)
|
||||
let deviceTypeIdentifier: String?
|
||||
public let deviceTypeIdentifier: String?
|
||||
|
||||
/// Device state (e.g. Shutdown)
|
||||
let state: String
|
||||
public let state: String
|
||||
|
||||
/// Returns true if the device is shutdown.
|
||||
var isShutdown: Bool {
|
||||
public var isShutdown: Bool {
|
||||
state == "Shutdown"
|
||||
}
|
||||
|
||||
/// 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.
|
||||
let availabilityError: String?
|
||||
public let availabilityError: String?
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
public init(dataPath: AbsolutePath,
|
||||
public init(
|
||||
dataPath: AbsolutePath,
|
||||
logPath: AbsolutePath,
|
||||
udid: String,
|
||||
isAvailable: Bool,
|
||||
|
@ -47,8 +48,8 @@ struct SimulatorDevice: Decodable, Hashable, CustomStringConvertible {
|
|||
state: String,
|
||||
name: String,
|
||||
availabilityError: String?,
|
||||
runtimeIdentifier: String)
|
||||
{
|
||||
runtimeIdentifier: String
|
||||
) {
|
||||
self.dataPath = dataPath
|
||||
self.logPath = logPath
|
||||
self.udid = udid
|
|
@ -0,0 +1,9 @@
|
|||
import Foundation
|
||||
|
||||
public struct SimulatorDeviceAndRuntime: Hashable {
|
||||
/// Device
|
||||
public let device: SimulatorDevice
|
||||
|
||||
/// Device's runtime.
|
||||
public let runtime: SimulatorRuntime
|
||||
}
|
|
@ -3,36 +3,37 @@ import TSCBasic
|
|||
|
||||
/// It represents a runtime that is available in the system. The list of available runtimes is obtained
|
||||
/// 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)
|
||||
let bundlePath: AbsolutePath
|
||||
public let bundlePath: AbsolutePath
|
||||
|
||||
/// 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)
|
||||
let runtimeRoot: AbsolutePath
|
||||
public let runtimeRoot: AbsolutePath
|
||||
|
||||
/// Runtime identifier (e.g. com.apple.CoreSimulator.SimRuntime.iOS-13-5)
|
||||
let identifier: String
|
||||
public let identifier: String
|
||||
|
||||
/// Runtime version (e.g. 13.5)
|
||||
let version: SimulatorRuntimeVersion
|
||||
public let version: SimulatorRuntimeVersion
|
||||
|
||||
// True if the runtime is available.
|
||||
let isAvailable: Bool
|
||||
public let isAvailable: Bool
|
||||
|
||||
// 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,
|
||||
runtimeRoot: AbsolutePath,
|
||||
identifier: String,
|
||||
version: SimulatorRuntimeVersion,
|
||||
isAvailable: Bool,
|
||||
name: String)
|
||||
{
|
||||
name: String
|
||||
) {
|
||||
self.bundlePath = bundlePath
|
||||
self.buildVersion = buildVersion
|
||||
self.runtimeRoot = runtimeRoot
|
||||
|
@ -52,7 +53,7 @@ struct SimulatorRuntime: Decodable, Hashable, CustomStringConvertible {
|
|||
case name
|
||||
}
|
||||
|
||||
var description: String {
|
||||
public var description: String {
|
||||
name
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
import Foundation
|
||||
|
||||
struct SimulatorRuntimeVersion: CustomStringConvertible, Hashable, ExpressibleByStringLiteral, Comparable, Decodable {
|
||||
public struct SimulatorRuntimeVersion: CustomStringConvertible, Hashable, ExpressibleByStringLiteral, Comparable, Decodable {
|
||||
// MARK: - Attributes
|
||||
|
||||
let major: Int
|
||||
let minor: Int?
|
||||
let patch: Int?
|
||||
public let major: Int
|
||||
public let minor: Int?
|
||||
public let patch: Int?
|
||||
|
||||
// MARK: - Constructors
|
||||
|
||||
|
@ -15,7 +15,7 @@ struct SimulatorRuntimeVersion: CustomStringConvertible, Hashable, ExpressibleBy
|
|||
self.patch = patch
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
self.init(stringLiteral: try container.decode(String.self))
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ struct SimulatorRuntimeVersion: CustomStringConvertible, Hashable, ExpressibleBy
|
|||
|
||||
// MARK: - CustomStringConvertible
|
||||
|
||||
var description: String {
|
||||
public var description: String {
|
||||
var version = "\(major)"
|
||||
if let minor = minor {
|
||||
version.append(".\(minor)")
|
||||
|
@ -47,7 +47,7 @@ struct SimulatorRuntimeVersion: CustomStringConvertible, Hashable, ExpressibleBy
|
|||
|
||||
// MARK: - Equatable
|
||||
|
||||
static func == (lhs: SimulatorRuntimeVersion, rhs: SimulatorRuntimeVersion) -> Bool {
|
||||
public static func == (lhs: SimulatorRuntimeVersion, rhs: SimulatorRuntimeVersion) -> Bool {
|
||||
lhs.major == rhs.major &&
|
||||
lhs.minor == rhs.minor &&
|
||||
lhs.patch == rhs.patch
|
||||
|
@ -55,7 +55,7 @@ struct SimulatorRuntimeVersion: CustomStringConvertible, Hashable, ExpressibleBy
|
|||
|
||||
// MARK: - Comparable
|
||||
|
||||
static func < (lhs: SimulatorRuntimeVersion, rhs: SimulatorRuntimeVersion) -> Bool {
|
||||
public static func < (lhs: SimulatorRuntimeVersion, rhs: SimulatorRuntimeVersion) -> Bool {
|
||||
let lhs = lhs.flattened()
|
||||
let rhs = rhs.flattened()
|
||||
|
||||
|
@ -76,7 +76,7 @@ struct SimulatorRuntimeVersion: CustomStringConvertible, Hashable, ExpressibleBy
|
|||
|
||||
// MARK: - ExpressibleByStringLiteral
|
||||
|
||||
init(stringLiteral value: String) {
|
||||
public init(stringLiteral value: String) {
|
||||
let components = value.split(separator: ".")
|
||||
|
||||
// Major
|
|
@ -72,7 +72,7 @@ public struct ValueGraph: Equatable {
|
|||
if let targetNode = node as? TargetNode {
|
||||
targetNode.dependencies.forEach { nodeDependencies.formUnion([self.dependency(from: $0)]) }
|
||||
} 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 {
|
||||
xcframeworkNode.dependencies.forEach { nodeDependencies.formUnion([self.dependency(from: $0.node)]) }
|
||||
}
|
||||
|
|
|
@ -71,6 +71,11 @@ public class ValueGraphTraverser: GraphTraversing {
|
|||
.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] {
|
||||
graph.dependencies[.target(name: name, path: path)]?
|
||||
.compactMap { (dependency: ValueGraphDependency) -> (path: AbsolutePath, name: String)? in
|
||||
|
|
|
@ -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>>)?
|
||||
func archive(_ target: XcodeBuildTarget,
|
||||
scheme: String,
|
||||
|
|
|
@ -9,7 +9,7 @@ public extension FrameworkNode {
|
|||
bcsymbolmapPaths: [AbsolutePath] = [],
|
||||
linking: BinaryLinking = .dynamic,
|
||||
architectures: [BinaryArchitecture] = [],
|
||||
dependencies: [FrameworkNode] = []) -> FrameworkNode
|
||||
dependencies: [PrecompiledNode.Dependency] = []) -> FrameworkNode
|
||||
{
|
||||
FrameworkNode(path: path,
|
||||
dsymPath: dsymPath,
|
||||
|
|
|
@ -3,23 +3,28 @@ import TSCBasic
|
|||
@testable import TuistCore
|
||||
|
||||
public extension Graph {
|
||||
static func test(name: String = "test",
|
||||
static func test(
|
||||
name: String = "test",
|
||||
entryPath: AbsolutePath = AbsolutePath("/test/graph"),
|
||||
entryNodes: [GraphNode] = [],
|
||||
workspace: Workspace = Workspace.test(path: "/test/graph"),
|
||||
projects: [Project] = [],
|
||||
cocoapods: [CocoaPodsNode] = [],
|
||||
packages: [PackageNode] = [],
|
||||
precompiled: [PrecompiledNode] = [],
|
||||
targets: [AbsolutePath: [TargetNode]] = [:]) -> Graph
|
||||
{
|
||||
Graph(name: name,
|
||||
targets: [AbsolutePath: [TargetNode]] = [:]
|
||||
) -> Graph {
|
||||
Graph(
|
||||
name: name,
|
||||
entryPath: entryPath,
|
||||
entryNodes: entryNodes,
|
||||
workspace: workspace,
|
||||
projects: projects,
|
||||
cocoapods: cocoapods,
|
||||
packages: packages,
|
||||
precompiled: precompiled,
|
||||
targets: targets)
|
||||
targets: targets
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a test dependency graph for targets within a single project
|
||||
|
|
|
@ -10,9 +10,9 @@ public final class MockGraphLoader: GraphLoading {
|
|||
return try loadProjectStub?(path) ?? (Graph.test(), Project.test())
|
||||
}
|
||||
|
||||
public var loadWorkspaceStub: ((AbsolutePath) throws -> (Graph, Workspace))?
|
||||
public func loadWorkspace(path: AbsolutePath) throws -> (Graph, Workspace) {
|
||||
return try loadWorkspaceStub?(path) ?? (Graph.test(), Workspace.test())
|
||||
public var loadWorkspaceStub: ((AbsolutePath) throws -> (Graph))?
|
||||
public func loadWorkspace(path: AbsolutePath) throws -> (Graph) {
|
||||
return try loadWorkspaceStub?(path) ?? Graph.test()
|
||||
}
|
||||
|
||||
public var loadConfigStub: ((AbsolutePath) throws -> (Config))?
|
||||
|
|
|
@ -6,10 +6,12 @@ import TSCBasic
|
|||
public extension TargetNode {
|
||||
static func test(project: Project = .test(),
|
||||
target: Target = .test(),
|
||||
dependencies: [GraphNode] = []) -> TargetNode
|
||||
dependencies: [GraphNode] = [],
|
||||
prune: Bool = false) -> TargetNode
|
||||
{
|
||||
TargetNode(project: project,
|
||||
target: target,
|
||||
dependencies: dependencies)
|
||||
dependencies: dependencies,
|
||||
prune: prune)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ public extension XCFrameworkNode {
|
|||
infoPlist: XCFrameworkInfoPlist = .test(),
|
||||
primaryBinaryPath: AbsolutePath = "/MyFramework/MyFramework.xcframework/binary",
|
||||
linking: BinaryLinking = .dynamic,
|
||||
dependencies: [XCFrameworkNode.Dependency] = []) -> XCFrameworkNode
|
||||
dependencies: [PrecompiledNode.Dependency] = []) -> XCFrameworkNode
|
||||
{
|
||||
XCFrameworkNode(path: path,
|
||||
infoPlist: infoPlist,
|
||||
|
|
|
@ -100,4 +100,8 @@ final class MockGraphTraverser: GraphTraversing {
|
|||
invokedDirectStaticDependenciesParametersList.append((path, name))
|
||||
return stubbedDirectStaticDependenciesResult
|
||||
}
|
||||
|
||||
func appClipsDependency(path _: AbsolutePath, name _: String) -> Target? {
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,12 @@ import TSCBasic
|
|||
public extension Config {
|
||||
static func test(compatibleXcodeVersions: CompatibleXcodeVersions = .all,
|
||||
cloud: Cloud? = Cloud.test(),
|
||||
generationOptions: [GenerationOption] = []) -> Config
|
||||
generationOptions: [GenerationOption] = [],
|
||||
path: AbsolutePath? = nil) -> Config
|
||||
{
|
||||
Config(compatibleXcodeVersions: compatibleXcodeVersions,
|
||||
cloud: cloud,
|
||||
generationOptions: generationOptions)
|
||||
generationOptions: generationOptions,
|
||||
path: path)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ public extension Project {
|
|||
xcodeProjPath: AbsolutePath = AbsolutePath("/Project/Project.xcodeproj"),
|
||||
name: String = "Project",
|
||||
organizationName: String? = nil,
|
||||
developmentRegion: String? = nil,
|
||||
settings: Settings = Settings.test(),
|
||||
filesGroup: ProjectGroup = .group(name: "Project"),
|
||||
targets: [Target] = [Target.test()],
|
||||
|
@ -20,6 +21,7 @@ public extension Project {
|
|||
xcodeProjPath: xcodeProjPath,
|
||||
name: name,
|
||||
organizationName: organizationName,
|
||||
developmentRegion: developmentRegion,
|
||||
settings: settings,
|
||||
filesGroup: filesGroup,
|
||||
targets: targets,
|
||||
|
@ -33,6 +35,7 @@ public extension Project {
|
|||
xcodeProjPath: AbsolutePath = AbsolutePath("/test/text.xcodeproj"),
|
||||
name: String = "Project",
|
||||
organizationName: String? = nil,
|
||||
developmentRegion: String? = nil,
|
||||
settings: Settings = .default,
|
||||
filesGroup: ProjectGroup = .group(name: "Project"),
|
||||
targets: [Target] = [],
|
||||
|
@ -45,6 +48,7 @@ public extension Project {
|
|||
xcodeProjPath: xcodeProjPath,
|
||||
name: name,
|
||||
organizationName: organizationName,
|
||||
developmentRegion: developmentRegion,
|
||||
settings: settings,
|
||||
filesGroup: filesGroup,
|
||||
targets: targets,
|
||||
|
|
|
@ -21,7 +21,8 @@ public extension Target {
|
|||
actions: [TargetAction] = [],
|
||||
environment: [String: String] = [:],
|
||||
filesGroup: ProjectGroup = .group(name: "Project"),
|
||||
dependencies: [Dependency] = []) -> Target
|
||||
dependencies: [Dependency] = [],
|
||||
scripts: [TargetScript] = []) -> Target
|
||||
{
|
||||
Target(name: name,
|
||||
platform: platform,
|
||||
|
@ -39,7 +40,8 @@ public extension Target {
|
|||
actions: actions,
|
||||
environment: environment,
|
||||
filesGroup: filesGroup,
|
||||
dependencies: dependencies)
|
||||
dependencies: dependencies,
|
||||
scripts: scripts)
|
||||
}
|
||||
|
||||
/// Creates a bare bones Target with as little data as possible
|
||||
|
@ -59,7 +61,8 @@ public extension Target {
|
|||
actions: [TargetAction] = [],
|
||||
environment: [String: String] = [:],
|
||||
filesGroup: ProjectGroup = .group(name: "Project"),
|
||||
dependencies: [Dependency] = []) -> Target
|
||||
dependencies: [Dependency] = [],
|
||||
scripts: [TargetScript] = []) -> Target
|
||||
{
|
||||
Target(name: name,
|
||||
platform: platform,
|
||||
|
@ -77,6 +80,7 @@ public extension Target {
|
|||
actions: actions,
|
||||
environment: environment,
|
||||
filesGroup: filesGroup,
|
||||
dependencies: dependencies)
|
||||
dependencies: dependencies,
|
||||
scripts: scripts)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -3,14 +3,19 @@ import TSCBasic
|
|||
@testable import TuistCore
|
||||
|
||||
public extension Workspace {
|
||||
static func test(path: AbsolutePath = AbsolutePath("/"),
|
||||
static func test(
|
||||
path: AbsolutePath = AbsolutePath("/"),
|
||||
name: String = "test",
|
||||
projects: [AbsolutePath] = [],
|
||||
additionalFiles: [FileElement] = []) -> Workspace
|
||||
{
|
||||
Workspace(path: path,
|
||||
schemes: [Scheme] = [],
|
||||
additionalFiles: [FileElement] = []
|
||||
) -> Workspace {
|
||||
Workspace(
|
||||
path: path,
|
||||
name: name,
|
||||
projects: projects,
|
||||
additionalFiles: additionalFiles)
|
||||
schemes: schemes,
|
||||
additionalFiles: additionalFiles
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import Foundation
|
||||
import RxSwift
|
||||
import struct TSCUtility.Version
|
||||
import TuistCore
|
||||
import TuistSupport
|
||||
|
||||
@testable import TuistAutomation
|
||||
@testable import TuistCore
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
public final class MockSimulatorController: SimulatorControlling {
|
||||
|
@ -43,4 +45,9 @@ public final class MockSimulatorController: SimulatorControlling {
|
|||
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())
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
@testable import TuistAutomation
|
||||
@testable import TuistCore
|
||||
|
||||
extension SimulatorDevice {
|
||||
static func test(dataPath: AbsolutePath = "/Library/Developer/CoreSimulator/Devices/3A8C9673-C1FD-4E33-8EFA-AEEBF43161CC/data",
|
|
@ -1,6 +1,6 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
@testable import TuistAutomation
|
||||
@testable import TuistCore
|
||||
|
||||
extension SimulatorDeviceAndRuntime {
|
||||
static func test(device: SimulatorDevice = .test(),
|
|
@ -1,6 +1,6 @@
|
|||
import Foundation
|
||||
import TSCBasic
|
||||
@testable import TuistAutomation
|
||||
@testable import TuistCore
|
||||
|
||||
extension SimulatorRuntime {
|
||||
// swiftlint:disable:next line_length
|
|
@ -3,8 +3,6 @@ import TSCBasic
|
|||
@testable import TuistCore
|
||||
|
||||
public final class MockRootDirectoryLocator: RootDirectoryLocating {
|
||||
public init() {}
|
||||
|
||||
public var locateArgs: [AbsolutePath] = []
|
||||
public var locateStub: AbsolutePath?
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue