Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Xcode Cloud CI #758

Merged
merged 7 commits into from
Nov 14, 2023
Merged

Improve Xcode Cloud CI #758

merged 7 commits into from
Nov 14, 2023

Conversation

mvasilak
Copy link
Contributor

@mvasilak mvasilak commented Aug 28, 2023

  • Uses App Store Connect API to set version in post clone script. It uses current released version, and increments it according to version bump type.
  • Tags the commit used for the build with an annotated version tag <version>-<build>. (Reminder, the build numbers themselves are handled by Xcode Cloud.
  • Adds TestFlight message in post build script. Uses standard text, and then logs branch (if available), commit hash, and commit message.

A workflow using these script needs to have these environment variables set:

  • app_store_connect_key_id
  • zotero_app_id
  • app_store_connect_api_key
  • app_store_connect_issuer_id

scripts/push_build can be called to trigger an Xcode Cloud build by setting a trigger tag (not annotated, but we can change it), that decides the version bump type.
Those have the format trigger-build-bump-[patch (default)|minor|major]-<timestamp>-<random value>.
For minor & major version bumps a confirmation prompt is shown.

The tag is set locally, pushed to origin, and then the local tag is deleted.
The remote tag will trigger the workflow. To avoid triggering the workflow by other tags, it's configured to do so only with those that start with trigger-build.
The remote tags are kept in the repo so that Xcode Cloud also keep the build page around. When not needed anymore, they can be delte manually or via a cleanup GitHub action, that e.g. deletes those trigger tags with timestamp older than 1 month.

In the post clone script:

  • New version is determined.

In the post build script:

  • <version>-<build> annotated tag is pushed to origin. It uses an environment secret that contains a GitHub Personal Access Token (PAT) URL for the repo. That limits each workflow in one repo, e.g. main org repo, or a developer fork. The reason is that it doesn't seem possible to read the primary repo URL used by the workflow. (We can probably use git remote to get the value, but I'll have to check if it works as expected, e.g. if there are multiple remotes.)
    PAT should be created by a user that has access to each repo, with the minimum allowed permissions (public_repo, repo:status should be enough), but even those might be a lot. There is a newer fine-grained GitHub tokens feature, that we can try to limit it only to specific repositories, but in this case the repo owner should be the one to create each respective token.

@mvasilak mvasilak self-assigned this Aug 28, 2023
@michalrentka
Copy link
Contributor

@mvasilak looks good, would be great if you could alter the push_build script next so that it pushes "new build" tag or something and cleans it up in post-build script. It would also be good to be able to specify which part of version string we want to increment. If I understand correctly right now it always increments minor version even if we set something manually.

@dstillman
Copy link
Member

Uses App Store Connect API to set version in post clone script. It uses current released version, and increments the minor part by 1.

Sorry, could you give an example of what this is doing? Do you mean the minor part as in the "19" in "1.0.19" or the "189" in "10.0.19-189"?

@mvasilak
Copy link
Contributor Author

Sorry, could you give an example of what this is doing? Do you mean the minor part as in the "19" in "1.0.19" or the "189" in "10.0.19-189"?

My bad, it updates the patch part of major.minor.patch semantic version, I corrected the description. The build number is decided by the Xcode Cloud autoincremented value.

I'll implement @michalrentka's recommendation to select which part of the version to update, by updating the push_build script to allow a command line argument. Default will still be patch, and for minor and major, the script will also require a confirmation.

@dstillman
Copy link
Member

I still don't understand what this is for, though. We don't want to update the patch component on each TestFlight build that we make. Can you explain the purpose of this?

@michalrentka
Copy link
Contributor

@mvasilak if possible we should also include incrementing build number so that we don't have to use tags for that.

@mvasilak
Copy link
Contributor Author

I still don't understand what this is for, though. We don't want to update the patch component on each TestFlight build that we make. Can you explain the purpose of this?

The script fetches via the API the currently released version. E.g. currently it is 1.0.19.
Then it bumps it's patch part, so the new version will be 1.0.20. This value is set to the project before building.
As long as another version is not released in the app store, every call of this script would create a build w/ version 1.0.20.
The build number itself is handled by Xcode Cloud automatically.

If the script is updated to allow for bumping minor or major versions, that would result in the current example to 1.1.0 and 2.0.0 respectively.

@michalrentka
Copy link
Contributor

michalrentka commented Aug 29, 2023

The build number itself is handled by Xcode Cloud automatically.

Just correction on my part, I meant build number in post-build script so that the final tag contains proper build number.

@mvasilak
Copy link
Contributor Author

mvasilak commented Aug 29, 2023

@mvasilak if possible we should also include incrementing build number so that we don't have to use tags for that.

Build number is incremented automatically by Xcode Cloud. And since TestFlight description will be set automatically, it will be easy to understand where each build comes from. Of course we can further improve this generated description.

@dstillman
Copy link
Member

But when would this be triggered? Aren't these scripts for Xcode Cloud? How is that related to updating the version number?

@mvasilak
Copy link
Contributor Author

The build number itself is handled by Xcode Cloud automatically.

Just correction on my part, I meant build number in post-build script so that the final tag contains proper build number.

Got it, will look into it.

@dstillman
Copy link
Member

As long as another version is not released in the app store, every call of this script would create a build w/ version 1.0.20.

Oh, OK, I guess this is the part that I was missing.

@michalrentka
Copy link
Contributor

As long as another version is not released in the app store, every call of this script would create a build w/ version 1.0.20.

Oh, OK, I guess this is the part that I was missing.

@dstillman the whole idea is that we can simply call the push build script and it relies on actual version of app in app store instead of our tags in repo, which can get out of sync with real app version. So you'd call push_build/push_build minor/push_build major and then xcode cloud fetches current app store version, increases proper version part and submits to test flight, where you can manually release it later.

The advantage is that you'd trigger the build process by submitting a tag like "new build" or something similar, Xcode cloud is triggered, does all mentioned above, removes the temporary "new build" tag and tags last commit with proper "1.0.20-189" for example. So we'd have our format of tags but we rely on app store API for actual real values.

@mvasilak
Copy link
Contributor Author

mvasilak commented Aug 29, 2023

But when would this be triggered? Aren't these scripts for Xcode Cloud? How is that related to updating the version number?

The whole idea is that we don't need to thoroughly maintain the version number in the repository. That way we avoid the mistake of uploading an invalid version build, just because we forgot to bump it before building.

Additionally, since we now use forks for development, if we build from a commit in a fork, then there is not one repo that will hold all build tags.

The tag back that @michalrentka proposed will just be for informative purposes. The actual tag triggering a build would be a specific one, e.g. trigger-build and we can have specialized versions like trigger-build-version-minor etc. And the CI will also clean this special tag afterwards.

@dstillman
Copy link
Member

dstillman commented Aug 29, 2023

OK, got it, thanks. I'm a little concerned about the tag-cleaning part — I feel like tags in git have a habit of getting out of sync, though maybe they've improved this. It's possible we'll need to use fetch.pruneTags true. Not sure if annotated tags work any more reliably. (We use those in zotero/zotero.)

But we can try this and see how it goes.

@mvasilak mvasilak force-pushed the improve-ci branch 8 times, most recently from 4851382 to b3ec23b Compare August 29, 2023 14:46
@mvasilak
Copy link
Contributor Author

@michalrentka @dstillman latest additions do the following:

  • scripts/push_build can be called to trigger an Xcode Cloud build by setting one of the tags (not annotated, but we can change it) trigger-build-bump-patch, trigger-build-bump-minor, or trigger-build-bump-major. In the latter two cases a confirmation prompt is shown.
  • The tag is set locally, pushed to origin, and then the local tag is deleted.
  • The remote tag will trigger the workflow. To avoid triggering the workflow by other tags, it's configured to do so only with those that start with trigger-build.

In post clone script:

  • New version is determined.
  • Trigger tag is deleted from origin, using an environment secret that contains GitHub Personal Access Token (PAT) URL for the repo. That limits each workflow on one repo, e.g. main org repo, or a dev fork. The reason is that it doesn't seem possible to read the primary repo URL used by the workflow. We can probably use git remote to get the value, but I'll have to check if it works as expected, e.g. if there are multiple remotes.

In post build script:

  • version-build annotated tag is pushed to origin.

There are some issues to consider:

  • The PAT should be by a user that has access to each repo, with the minimum allowed permissions (public_repo, repo:status should be enough), but even those might be a lot. There is a newer fine-grained GitHub tokens feature, that we can try to limit it only to specific repositories, but in this case the repo owner should be the one to create each respective token.
  • Deleting a tag that triggered an Xcode Cloud workflow, makes these builds not listed in the app store connect site! The builds are there, but you can only access their report by their explicit URL, which you can get via the API, or wait for the email that informs of failure or success.
  • If the workflow is triggered by a tag, the environment variable for the branch is empty, which we use in the TestFlight message, but that is an easy fix to get it through a git command.

@michalrentka
Copy link
Contributor

I don't like that deleting tag makes the build unlisted but I guess we don't really have a choice. We could generate tags with random hashes but then we'd have just many unnecessary tags in repo. @dstillman any comments?

The code looks good though.

@mvasilak
Copy link
Contributor Author

I was looking for a recent build in Xcode Cloud, because its artifacts is the only place AFAIK that you can get debug symbols from, which are necessary to symbolicate crash reports. I couldn't find it in the app store connect site, although the tag is still available in the repo. Maybe Xcode Cloud hides old builds after a while?

The same build is visible in the Xcode app, by going to Product > Xcode Cloud > Zotero > View Builds, but unfortunately when opening the build itself, it does not load its details (for newer builds I could do so successfully).

If that is the case, we probably need to export anything potentially necessary, regardless if we use a random tag or not. This could be an automatic step in the post build process, using an endpoint to upload e.g. the artifacts, or a manual one for builds that are release candidates.

@mvasilak
Copy link
Contributor Author

@dstillman any comments on the above?

@michalrentka
Copy link
Contributor

@dstillman would it be possible to create a simple API where we could upload (and access later as needed) build artifacts for each build?

@mvasilak mvasilak merged commit 1100500 into zotero:master Nov 14, 2023
1 check passed
@mvasilak mvasilak deleted the improve-ci branch November 21, 2023 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants