Skip to content

Commit

Permalink
Add versioned CLI launcher (#202)
Browse files Browse the repository at this point in the history
Most devs currently don’t pin the CLI because it’s not part of the
“happy” path in the setup instructions. Devs that do decide to pin the
CLI usually do so by checking it into source control which is not
sustainable long term.

This change makes pinning the binary part of the happy path of setting
up the framework for all package managers by adding a “launcher” to
abstract the CLI initialization process. The launcher allows the CLI to
be downloaded on-demand from an artifact provider. By default, GitHub’s
release artifacts is used, but it’s possible to set the download URL at
various levels:

- `REPO_URL` for artifacts hosted on a forked GitHub repo
- `ARTIFACTS_URL` for versioned artifacts at /<version>/Mockingbird.zip
- `ZIP_RELEASE_URL` for artifacts at arbitrary URLs

A side effect of the launcher is that it’s now trivial to run against an
arbitrary CLI version simply by setting the `MKB_VERSION` env var.
  • Loading branch information
andrewchang-bird authored Jul 9, 2021
1 parent 9266bd7 commit 0646c87
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 74 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/build-framework-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ jobs:
- name: Build and Install CLI
run: |
make print-debug-info | grep "Mockingbird rpath: /var/tmp/mockingbird/$(make get-version)/libs"
PREFIX=$(pwd) USE_RELATIVE_RPATH=1 make print-debug-info
PREFIX=$(pwd) USE_RELATIVE_RPATH=1 make install
PREFIX=$(pwd) HERMETIC=1 make print-debug-info
PREFIX=$(pwd) HERMETIC=1 make install
- name: Set Up Caching Target
run: |
./bin/mockingbird install \
Expand Down Expand Up @@ -86,8 +86,8 @@ jobs:
- name: Build and Install CLI
run: |
make print-debug-info | grep "Mockingbird rpath: /var/tmp/mockingbird/$(make get-version)/libs"
PREFIX=$(pwd) USE_RELATIVE_RPATH=1 make print-debug-info
PREFIX=$(pwd) USE_RELATIVE_RPATH=1 make install
PREFIX=$(pwd) HERMETIC=1 make print-debug-info
PREFIX=$(pwd) HERMETIC=1 make install
- name: Set Up Target
run: |
./bin/mockingbird install \
Expand Down Expand Up @@ -123,8 +123,8 @@ jobs:
- name: Build and Install CLI
run: |
make print-debug-info | grep "Mockingbird rpath: /var/tmp/mockingbird/$(make get-version)/libs"
PREFIX=$(pwd) USE_RELATIVE_RPATH=1 make print-debug-info
PREFIX=$(pwd) USE_RELATIVE_RPATH=1 make install
PREFIX=$(pwd) HERMETIC=1 make print-debug-info
PREFIX=$(pwd) HERMETIC=1 make install
- name: Set Up Target
run: |
./bin/mockingbird install \
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v2
- name: Import Secrets
uses: apple-actions/import-codesign-certs@v1
with:
with:
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
- name: Print Debug Info
Expand Down Expand Up @@ -64,7 +64,7 @@ jobs:
- uses: actions/checkout@v2
- name: Import Secrets
uses: apple-actions/import-codesign-certs@v1
with:
with:
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
- name: Print Debug Info
Expand All @@ -77,7 +77,7 @@ jobs:
env:
AC_USERNAME: ${{ secrets.AC_USERNAME }}
AC_PASSWORD: ${{ secrets.AC_PASSWORD }}
USE_RELATIVE_RPATH: 1
HERMETIC: 1
run: make signed-release
- name: Document SHAs
run: |
Expand Down
9 changes: 4 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
*.xcodeproj/GeneratedModuleMap/
build/
DerivedData
Mockingbird.pkg
Mockingbird.zip
MockingbirdCli.pkg
MockingbirdCli.zip
Mockingbird*.pkg
Mockingbird*.zip
MockingbirdCli*.pkg
MockingbirdCli*.zip
MockingbirdSupport.zip
mockingbird
Mockingbird-*.framework
bin/
var/
Expand Down
40 changes: 26 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
TEMPORARY_FOLDER_ROOT?=/tmp
USE_RELATIVE_RPATH?=0
HERMETIC?=0
PREFIX?=/usr/local
BUILD_TOOL?=xcodebuild
REPO_URL?=https://github.com/birdrides/mockingbird
ARTIFACTS_URL?=$(REPO_URL)/releases/download
VERIFY_SIGNATURES?=1
AC_USERNAME?=
AC_PASSWORD?=
PKG_IDENTITY?=Developer ID Installer: Bird Rides, Inc. (P2T4T6R4SL)
Expand All @@ -14,11 +16,12 @@ TEMPORARY_INSTALLER_FOLDER=$(TEMPORARY_FOLDER)/install
XCODEBUILD_DERIVED_DATA=$(TEMPORARY_FOLDER)/xcodebuild/DerivedData/MockingbirdFramework
XCODE_PATH=$(shell xcode-select --print-path)
CLI_BUNDLE_PLIST=Sources/MockingbirdCli/Info.plist
VERSION_STRING?=$(shell /usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$(CLI_BUNDLE_PLIST)")
MKB_VERSION?=$(shell /usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$(CLI_BUNDLE_PLIST)")
VERSION_STRING=$(MKB_VERSION)

# Needs to be kept in sync with `LoadDylib.swift` and `build-framework-cli.yml`.
$(eval RELATIVE_RPATH_FLAG = $(shell [[ $(USE_RELATIVE_RPATH) -eq 1 ]] && echo '-Xswiftc -DRELATIVE_RPATH' || echo ''))
$(eval MOCKINGBIRD_RPATH = $(shell [[ $(USE_RELATIVE_RPATH) -eq 1 ]] && echo '@executable_path' || echo '/var/tmp/mockingbird/$(VERSION_STRING)/libs'))
$(eval RELATIVE_RPATH_FLAG = $(shell [[ $(HERMETIC) -eq 1 ]] && echo '-Xswiftc -DRELATIVE_RPATH' || echo ''))
$(eval MOCKINGBIRD_RPATH = $(shell [[ $(HERMETIC) -eq 1 ]] && echo '@executable_path' || echo '/var/tmp/mockingbird/$(VERSION_STRING)/libs'))

SIMULATOR_NAME=iphone11-mockingbird
SIMULATOR_DEVICE_TYPE=com.apple.CoreSimulator.SimDeviceType.iPhone-11
Expand Down Expand Up @@ -51,8 +54,9 @@ DEFAULT_XCODE_RPATH=$(XCODE_PATH)/Toolchains/XcodeDefault.xctoolchain/usr/lib/sw

PKG_BUNDLE_IDENTIFIER=co.bird.mockingbird
CLI_DESIGNATED_REQUIREMENT=Codesigning/MockingbirdCli.dr
ZIP_FILENAME=Mockingbird.zip
CLI_FILENAME=mockingbird
$(eval CLI_PATH = $(shell [[ $(HERMETIC) -eq 1 ]] && echo "bin/$(VERSION_STRING)" || echo "bin/$(VERSION_STRING)-portable"))
$(eval ZIP_FILENAME = $(shell [[ $(HERMETIC) -eq 1 ]] && echo 'Mockingbird-cisafe.zip' || echo 'Mockingbird.zip'))

FRAMEWORK_FILENAME=Mockingbird.framework

Expand All @@ -74,7 +78,7 @@ APPLETVSIMULATOR_FRAMEWORK_PATH=$(APPLETVSIMULATOR_FRAMEWORK_FOLDER)/$(FRAMEWORK
LICENSE_FILENAME=LICENSE
LICENSE_PATH=$(shell pwd)/$(LICENSE_FILENAME)

INSTALLABLE_FILENAMES="$(CLI_FILENAME)" \
INSTALLABLE_FILENAMES="$(CLI_PATH)/$(CLI_FILENAME)" \
"$(MACOS_FRAMEWORK_FILENAME)" \
"$(IPHONESIMULATOR_FRAMEWORK_FILENAME)" \
"$(APPLETVSIMULATOR_FRAMEWORK_FILENAME)" \
Expand All @@ -87,9 +91,9 @@ OUTPUT_ZIP=Mockingbird.zip
OUTPUT_STARTER_PACK_ZIP=MockingbirdSupport.zip
OUTPUT_DOCS_FOLDER=docs/$(VERSION_STRING)

ZIP_RELEASE_URL=$(REPO_URL)/releases/download/$(VERSION_STRING)/$(ZIP_FILENAME)
SUCCESS_MSG=Verified the Mockingbird CLI binary
ERROR_MSG=error: The downloaded Mockingbird CLI binary does not satisfy the expected code signature!
ZIP_RELEASE_URL?=$(ARTIFACTS_URL)/$(VERSION_STRING)/$(ZIP_FILENAME)
SUCCESS_MSG=Verified the CLI binary code signature
ERROR_MSG= The CLI binary is not signed with the expected code signature! (Set VERIFY_SIGNATURES=0 to ignore this error.)

REDIRECT_DOCS_PAGE=<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0;url=/mockingbird/$(VERSION_STRING)/"></head></html>

Expand Down Expand Up @@ -306,12 +310,17 @@ docs: clean-docs setup-swiftdoc docs/index.html docs/latest/index.html
.PHONY: download
download:
$(eval CURL_AUTH_HEADER = $(shell [[ -z "${GH_ACCESS_TOKEN}" ]] || echo '-H "Authorization: token' ${GH_ACCESS_TOKEN}'"'))
curl $(CURL_AUTH_HEADER) -Lo "$(ZIP_FILENAME)" "$(ZIP_RELEASE_URL)"
unzip -o "$(ZIP_FILENAME)" "$(CLI_FILENAME)"
@codesign -v -R "$(CLI_DESIGNATED_REQUIREMENT)" "$(CLI_FILENAME)" \
curl $(CURL_AUTH_HEADER) --progress-bar -Lo "$(ZIP_FILENAME)" "$(ZIP_RELEASE_URL)"
mkdir -p "$(CLI_PATH)"
unzip -o "$(ZIP_FILENAME)" "$(CLI_FILENAME)" -d "$(CLI_PATH)"
chmod +x "$(CLI_PATH)/$(CLI_FILENAME)"
@if [[ $(VERIFY_SIGNATURES) -eq 1 ]]; then $(MAKE) verify; fi

.PHONY: verify
verify:
@codesign -v -R "$(CLI_DESIGNATED_REQUIREMENT)" "$(CLI_PATH)/$(CLI_FILENAME)" \
&& echo "$(SUCCESS_MSG)" \
|| $$(echo "$(ERROR_MSG)" >&2; exit 1)
chmod +x "$(CLI_FILENAME)"

.PHONY: install
install: build-cli
Expand All @@ -321,7 +330,7 @@ install: build-cli
.PHONY: install-prebuilt
install-prebuilt: download
install -d "$(BINARIES_FOLDER)"
install "$(CLI_FILENAME)" "$(BINARIES_FOLDER)"
install "$(CLI_PATH)/$(CLI_FILENAME)" "$(BINARIES_FOLDER)"

.PHONY: uninstall
uninstall:
Expand Down Expand Up @@ -434,5 +443,8 @@ get-zip-sha256:
get-repo-url:
@echo $(REPO_URL)

get-release-url:
@echo $(ZIP_RELEASE_URL)

%:
@:
54 changes: 19 additions & 35 deletions README-0.17.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ verify(bird.fly()).wasCalled()

## Installation

Select your preferred dependency manager below for installation instructions and example projects.
Select your preferred dependency manager below for installation instructions and example projects.

<details><summary><b>CocoaPods</b></summary>

Expand All @@ -86,29 +86,22 @@ target 'MyAppTests' do
end
```

Initialize the pod and install the CLI.
In your project directory, initialize the pod and download the starter [supporting source files](https://github.com/birdrides/mockingbird/wiki/Supporting-Source-Files).

```console
$ pod install
$ (cd Pods/MockingbirdFramework && make install-prebuilt)
```

Then download the starter [supporting source files](https://github.com/birdrides/mockingbird/wiki/Supporting-Source-Files).

```console
$ mockingbird download starter-pack
$ Pods/MockingbirdFramework/mockingbird download starter-pack
```

Finally, configure a test target to generate mocks for each listed source module. For advanced usages, see the [available installer options](#install) and how to [set up targets manually](https://github.com/birdrides/mockingbird/wiki/Manual-Setup).

```console
$ mockingbird install --target MyAppTests --sources MyApp MyLibrary1 MyLibrary2
$ Pods/MockingbirdFramework/mockingbird install --target MyAppTests --sources MyApp MyLibrary1 MyLibrary2
```

Optional but recommended:

- [Exclude generated files from source control](https://github.com/birdrides/mockingbird/wiki/Integration-Tips#source-control-exclusion)
- [Pin the binary for hermetic builds](https://github.com/birdrides/mockingbird/wiki/Integration-Tips#pin-the-binary)

Have questions or issues?

Expand All @@ -126,17 +119,11 @@ Add the framework to your `Cartfile`.
github "birdrides/mockingbird" ~> 0.17
```

Build the framework with Carthage, [link it to your test target](https://github.com/birdrides/mockingbird/wiki/Linking-Test-Targets), and install the CLI.
In your project directory, build the framework, [link it to your test target](https://github.com/birdrides/mockingbird/wiki/Linking-Test-Targets), and download the starter [supporting source files](https://github.com/birdrides/mockingbird/wiki/Supporting-Source-Files).

```console
$ carthage update
$ (cd Carthage/Checkouts/mockingbird && make install-prebuilt)
```

Then download the starter [supporting source files](https://github.com/birdrides/mockingbird/wiki/Supporting-Source-Files).

```console
$ mockingbird download starter-pack
$ Carthage/Checkouts/mockingbird download starter-pack
```

Finally, configure a test target to generate mocks for each listed source module. For advanced usages, see the [available installer options](#install) and how to [set up targets manually](https://github.com/birdrides/mockingbird/wiki/Manual-Setup).
Expand All @@ -148,7 +135,6 @@ $ mockingbird install --target MyAppTests --sources MyApp MyLibrary1 MyLibrary2
Optional but recommended:

- [Exclude generated files from source control](https://github.com/birdrides/mockingbird/wiki/Integration-Tips#source-control-exclusion)
- [Pin the binary for hermetic builds](https://github.com/birdrides/mockingbird/wiki/Integration-Tips#pin-the-binary)

Have questions or issues?

Expand Down Expand Up @@ -191,30 +177,24 @@ let package = Package(

</details>

In your project directory, initialize the package dependency and install the CLI.
In your project directory, initialize the package dependency and download the starter [supporting source files](https://github.com/birdrides/mockingbird/wiki/Supporting-Source-Files).

```console
$ xcodebuild -resolvePackageDependencies
$ DERIVED_DATA=$(xcodebuild -showBuildSettings | pcregrep -o1 'OBJROOT = (/.*)/Build')
$ (cd "${DERIVED_DATA}/SourcePackages/checkouts/mockingbird" && make install-prebuilt)
```

Then download the starter [supporting source files](https://github.com/birdrides/mockingbird/wiki/Supporting-Source-Files).

```console
$ mockingbird download starter-pack
$ REPO_PATH="${DERIVED_DATA}/SourcePackages/checkouts/mockingbird"
$ REPO_PATH/mockingbird download starter-pack
```

Finally, configure a test target to generate mocks for each listed source module. For advanced usages, see the [available installer options](#install) and how to [set up targets manually](https://github.com/birdrides/mockingbird/wiki/Manual-Setup).

```console
$ mockingbird install --target MyPackageTests --sources MyPackage MyLibrary1 MyLibrary2
$ REPO_PATH/mockingbird install --target MyPackageTests --sources MyPackage MyLibrary1 MyLibrary2
```

Optional but recommended:

- [Exclude generated files from source control](https://github.com/birdrides/mockingbird/wiki/Integration-Tips#source-control-exclusion)
- [Pin the binary for hermetic builds](https://github.com/birdrides/mockingbird/wiki/Integration-Tips#pin-the-binary)

Have questions or issues?

Expand Down Expand Up @@ -337,7 +317,7 @@ bird.useDefaultValues(from: .standardProvider)
print(bird.name) // Prints ""
```

You can create custom value providers by registering values for types.
You can create custom value providers by registering values for types.

```swift
var valueProvider = ValueProvider()
Expand Down Expand Up @@ -479,7 +459,7 @@ inOrder(with: .noInvocationsAfter) {

#### Asynchronous Verification

Mocked methods that are invoked asynchronously can be verified using an `eventually` block which returns an `XCTestExpectation`.
Mocked methods that are invoked asynchronously can be verified using an `eventually` block which returns an `XCTestExpectation`.

```swift
DispatchQueue.main.async {
Expand Down Expand Up @@ -607,9 +587,9 @@ Usage is determined by statically analyzing test target sources for calls to `mo

Generate mocks for a set of targets in a project.

`mockingbird generate`
`mockingbird generate`

| Option | Default Value | Description |
| Option | Default Value | Description |
| --- | --- | --- |
| `--targets` | *(required)* | List of target names to generate mocks for. |
| `--project` | [`(inferred)`](#--project) | Path to an `.xcodeproj` file or a [JSON project description](https://github.com/birdrides/mockingbird/wiki/Manual-Setup#generating-mocks-for-non-xcode-projects). |
Expand Down Expand Up @@ -681,6 +661,10 @@ Download and unpack a compatible asset bundle. Bundles will never overwrite exis
| --- | --- |
| `starter-pack` | Starter [supporting source files](https://github.com/birdrides/mockingbird/wiki/Supporting-Source-Files). |

| Option | Default Value | Description |
| --- | --- | --- |
| `--url` | `https://github.com/birdrides/mockingbird/releases/download` | The base URL containing downloadable asset bundles. |

### Global Options

| Flag | Description |
Expand All @@ -696,7 +680,7 @@ Mockingbird first checks the environment variable `PROJECT_FILE_PATH` set by the

#### `--srcroot`

Mockingbird checks the environment variables `SRCROOT` and `SOURCE_ROOT` set by the Xcode build context and then falls back to the directory containing the `.xcodeproj` project file. Note that source root is ignored when using JSON project descriptions.
Mockingbird checks the environment variables `SRCROOT` and `SOURCE_ROOT` set by the Xcode build context and then falls back to the directory containing the `.xcodeproj` project file. Note that source root is ignored when using JSON project descriptions.

#### `--outputs`

Expand Down
16 changes: 6 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ verify(bird.fly()).wasCalled()

## Installation

Select your preferred dependency manager below for installation instructions and example projects.
Select your preferred dependency manager below for installation instructions and example projects.

<details><summary><b>CocoaPods</b></summary>

Expand Down Expand Up @@ -337,7 +337,7 @@ bird.useDefaultValues(from: .standardProvider)
print(bird.name) // Prints ""
```

You can create custom value providers by registering values for types.
You can create custom value providers by registering values for types.

```swift
var valueProvider = ValueProvider()
Expand Down Expand Up @@ -479,7 +479,7 @@ inOrder(with: .noInvocationsAfter) {

#### Asynchronous Verification

Mocked methods that are invoked asynchronously can be verified using an `eventually` block which returns an `XCTestExpectation`.
Mocked methods that are invoked asynchronously can be verified using an `eventually` block which returns an `XCTestExpectation`.

```swift
DispatchQueue.main.async {
Expand Down Expand Up @@ -607,9 +607,9 @@ Usage is determined by statically analyzing test target sources for calls to `mo

Generate mocks for a set of targets in a project.

`mockingbird generate`
`mockingbird generate`

| Option | Default Value | Description |
| Option | Default Value | Description |
| --- | --- | --- |
| `--targets` | *(required)* | List of target names to generate mocks for. |
| `--project` | [`(inferred)`](#--project) | Path to an `.xcodeproj` file or a [JSON project description](https://github.com/birdrides/mockingbird/wiki/Manual-Setup#generating-mocks-for-non-xcode-projects). |
Expand Down Expand Up @@ -681,10 +681,6 @@ Download and unpack a compatible asset bundle. Bundles will never overwrite exis
| --- | --- |
| `starter-pack` | Starter [supporting source files](https://github.com/birdrides/mockingbird/wiki/Supporting-Source-Files). |

| Option | Default Value | Description |
| --- | --- | --- |
| `--url` | `https://github.com/birdrides/mockingbird/releases/download` | The base URL containing downloadable asset bundles. |

### Global Options

| Flag | Description |
Expand All @@ -700,7 +696,7 @@ Mockingbird first checks the environment variable `PROJECT_FILE_PATH` set by the

#### `--srcroot`

Mockingbird checks the environment variables `SRCROOT` and `SOURCE_ROOT` set by the Xcode build context and then falls back to the directory containing the `.xcodeproj` project file. Note that source root is ignored when using JSON project descriptions.
Mockingbird checks the environment variables `SRCROOT` and `SOURCE_ROOT` set by the Xcode build context and then falls back to the directory containing the `.xcodeproj` project file. Note that source root is ignored when using JSON project descriptions.

#### `--outputs`

Expand Down
Loading

0 comments on commit 0646c87

Please sign in to comment.