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

Automate SDK release from CI #2457

Merged
merged 38 commits into from
Jul 7, 2021
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
dbf7400
Added first version on the publish workflow
LaPeste Jun 15, 2021
e6b55cc
Move to work from windows
LaPeste Jun 21, 2021
e21f709
Merge branch 'master' into ac/ci-publish-nupkg
LaPeste Jun 21, 2021
e186ef7
Some more work done
LaPeste Jun 21, 2021
da17089
Added apexes around nupkg
LaPeste Jun 22, 2021
e936664
First "close to final" release workflow
LaPeste Jul 2, 2021
4bf8118
Rename AWS secrets and cleaned templates/
LaPeste Jul 5, 2021
33e6b21
Cleaned-up PR and version from nupkg when on release branch
LaPeste Jul 5, 2021
d7825bd
Corrected env vars names
LaPeste Jul 5, 2021
38c1e5d
Generate docs in release branch from main and upload them in release …
LaPeste Jul 5, 2021
0d0010e
Fix for contains in main.yml
LaPeste Jul 5, 2021
9848ff9
Test docs
LaPeste Jul 5, 2021
9a9cd6e
Test docs - 0
LaPeste Jul 5, 2021
3571f63
Test docs - 1
LaPeste Jul 5, 2021
a3fc1b6
Test docs - 2
LaPeste Jul 5, 2021
ea2214b
Test docs - 3
LaPeste Jul 5, 2021
3598d87
Test docs - 4
LaPeste Jul 5, 2021
0919697
Docs creation works
LaPeste Jul 5, 2021
652fb51
Docs creation works - 0
LaPeste Jul 5, 2021
b296c37
Minor fixes before review
LaPeste Jul 5, 2021
8ce7465
Fix some errors in release workflow
LaPeste Jul 5, 2021
e1090d8
Applied fixes from things learned from the test repo
LaPeste Jul 6, 2021
8a02492
Added negation to wrong file
LaPeste Jul 6, 2021
c12133c
Merge branch 'master' into ac/ci-publish-nupkg
nirinchev Jul 6, 2021
9a2f219
Build docs immediately after a package has been built
nirinchev Jul 6, 2021
491df05
Build PR HEAD rather than the merge commit
nirinchev Jul 6, 2021
d80346b
Try to cache docfx
nirinchev Jul 6, 2021
6e306c7
Don't always checkout recursive submodules
nirinchev Jul 6, 2021
b327cc7
Clean up some logging
nirinchev Jul 6, 2021
8c0a3fd
Try #2
nirinchev Jul 7, 2021
36b5358
Try #3
nirinchev Jul 7, 2021
17b42c3
Update regex and comment-out nuget publishing for now
nirinchev Jul 7, 2021
a3d9617
Removed <br/> from readme
LaPeste Jul 7, 2021
6c812b7
Changed from env vars to outputs
LaPeste Jul 7, 2021
d4e1607
Added apexes to avoid problems in interpretation of string
LaPeste Jul 7, 2021
357b341
Clean up some step names
nirinchev Jul 7, 2021
816fc7b
Remove accidental space
nirinchev Jul 7, 2021
fb552e5
Inverse doc condition
nirinchev Jul 7, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/templates/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ We're using [ytt](https://github.com/vmware-tanzu/carvel-ytt) to generate the gi

## Prerequisites

Get ytt from your package manager of choice. If, for example, you're on Windows and use chocolatey you can run:<br/> `choco install ytt -y`

If instead ytt isn't in your packager manager or you don't use one you can manually get ytt as follows:

1. Download the executable for your platform from the [releases page](https://github.com/vmware-tanzu/carvel-ytt/releases).
1. Rename it to `ytt`.
1. Place it in your PATH.

## Building the docs
1. In `$SolutionDir/.github/templates` run `ytt -f main.yml --output-files ../workflows/`.
1. `cd $SolutionDir/.github/templates`
1. `ytt -f . --output-files ../workflows/` ==> to target all files<br/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need for <br/> or is there?

Copy link
Contributor Author

@LaPeste LaPeste Jul 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In markdown we can insert a line break in 2 ways:

  1. Specifically adding a line break tag (like a did)
  2. Ending a line with 2 or more spaces and a new line char

I thought that in the option number 2 it would be harder to notice the new line. But maybe it's nicer to read? Which approach do you recommend?

or<br/>
`ytt -f YOUR_FILE --output-files ../workflows/` ==> for a specific file
59 changes: 50 additions & 9 deletions .github/templates/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,8 @@ if: #@ wrappersCacheCondition
#@ def setupVcpkg():
name: Setup Vcpkg
run: |
Write-Output 'Beginning download...'
Invoke-WebRequest -Uri https://static.realm.io/downloads/vcpkg.zip -OutFile C:\vcpkg.zip
Write-Output ((Get-Item C:\vcpkg.zip).length/1MB)
Expand-Archive -Path C:\vcpkg.zip -DestinationPath C:\
Write-Output 'Completed!'
shell: powershell
if: #@ wrappersCacheCondition + " && steps.check-vcpkg-cache.outputs.cache-hit != 'true'"
#@ end
Expand All @@ -50,7 +47,7 @@ if: #@ wrappersCacheCondition + " && steps.check-vcpkg-cache.outputs.cache-hit !
#@ actualCommand = cmd + configurationParam + ltoParam

steps:
- #@ template.replace(checkoutCode())
- #@ template.replace(checkoutCode("recursive"))
- #@ checkCache(cacheKey)
#@ for step in intermediateSteps:
- #@ step
Expand All @@ -66,11 +63,12 @@ steps:
retention-days: 1
#@ end

#@ def checkoutCode():
#@ def checkoutCode(submodules=False):
nirinchev marked this conversation as resolved.
Show resolved Hide resolved
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
submodules: #@ submodules
ref: ${{ github.event.pull_request.head.sha }}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the behavior of checkout@v2 to checkout the HEAD rather than the merge commit - this is useful in regular PRs, but especially important for releases, where we don't want to build the merged thing.

Copy link
Contributor Author

@LaPeste LaPeste Jul 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to double check that I got this right:
Our main workflow is triggered with push on master/main and on pull_request (with events opened, synchronize, or reopened). Since ref defaults to the reference or SHA of the event that triggered the workflow, you're saying that we need to avoid to fetch a push to master, which generally happens to be a merge of a pull request, but we always need to carefully fetch the head of the current PR which could be lacking the latest merged PRs that master has, which is a good thing in this case.
Did I get this right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default behavior for checkout on a PR trigger is to checkout the merge commit (i.e. have the PR branch merged into the base branch and use that). This is why when there are merge conflicts, our GHA workflows don't run. This is the change suggested in the checkout action docs to make sure that PR builds actually build and test the code that is in the PR itself.

While for PR builds against master, checking out the merge commit is not a big deal - and in some cases may even be useful - for release PRs it's plain wrong and may result in very surprising behavior where we actually push a nuget package containing code that was not present in the release branch.

Copy link
Contributor Author

@LaPeste LaPeste Jul 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now it all makes sense. I wasn't fully aware of the default behaviour for the checkout on PRs, that's why I got confused. Thank you for the explanation. On top of this, the change of line 74 will stop "there are merge conflicts with master" that prevent the workflow to run; which I'm actually rather happy about.

- name: Register csc problem matcher
run: echo "::add-matcher::.github/problem-matchers/csc.json"
- name: Register msvc problem matcher
Expand Down Expand Up @@ -101,6 +99,35 @@ steps:
retention-days: ${{ github.event_name != 'pull_request' && 30 || 1 }}
#@ end

#@ def buildDocs():
#@ docsCondition = "${{ !contains(github.ref, 'release') }}"
- name: Check Docfx cache
if: #@ docsCondition
id: check-docfx-cache
uses: actions/cache@v2
with:
path: 'C:\docfx'
key: docfx
- name: Download docfx
if: #@ docsCondition + " && steps.check-docfx-cache.outputs.cache-hit != 'true'"
run: |
Invoke-WebRequest -Uri https://github.com/dotnet/docfx/releases/download/v2.58/docfx.zip -OutFile C:\docfx.zip
Expand-Archive -Path C:\docfx.zip -DestinationPath C:\docfx
shell: powershell
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than use choco, we're caching docfx to avoid downloading it every time. This seems to cut docs build times from 70 to 35 seconds.

- name: Build docs
if: #@ docsCondition
run: |
C:\docfx\docfx Docs/docfx.json
Compress-Archive -Path Docs/_site -DestinationPath "Realm/packages/Docs.zip"
- name: Store docs artifacts
if: #@ docsCondition
uses: actions/upload-artifact@v2
with:
name: Docs.zip
path: ${{ github.workspace }}/Realm/packages/Docs.zip
retention-days: ${{ github.event_name != 'pull_request' && 30 || 1 }}
#@ end

#@ def uploadPackageArtifacts():
#@ for pkgName in nugetPackages:
#@ finalPkgName = pkgName + ".${{ steps.find-nupkg-version.outputs.package_version }}.nupkg"
Expand All @@ -114,13 +141,13 @@ steps:
#@ end

#@ def fetchPackageArtifacts():
#@ for pkg in [ "Realm", "Realm.Fody" ]:
#@ for pkg in [ "Realm", "Realm.Fody" ]:
- name: #@ "Fetch " + pkg
uses: actions/download-artifact@v2
with:
name: #@ pkg + ".${{ needs.build-packages.outputs.package_version }}.nupkg"
path: ${{ github.workspace }}/Realm/packages/
#@ end
#@ end
#@ end

#@ def deleteTempPackageArtifacts():
Expand Down Expand Up @@ -288,10 +315,24 @@ jobs:
- name: Set version suffix
id: set-version-suffix
#! Build suffix is PR-1234.5 for PR builds or alpha.123 for branch builds.
run: echo "::set-output name=build_suffix::${{ github.event_name == 'pull_request' && format('PR-{0}', github.event.number) || 'alpha'}}.${{ github.run_number }}"
run: |
$suffix = ""
if ($env:GITHUB_EVENT_NAME -eq "pull_request")
{
if (-Not $env:GITHUB_REF.Contains("release"))
{
$suffix = "PR-${{ github.event.number }}.$env:GITHUB_RUN_NUMBER"
nirinchev marked this conversation as resolved.
Show resolved Hide resolved
}
}
else
{
$suffix = "alpha.$env:GITHUB_RUN_NUMBER"
}
echo "::set-output name=build_suffix::$suffix"
- #@ template.replace(fetchWrapperBinaries())
- #@ template.replace(buildPackages())
- #@ findPackageVersion()
- #@ template.replace(buildDocs())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the important change that fixed the docs issues - moving it to after the package was built allows docfx to pick up the correct references.

Copy link
Contributor Author

@LaPeste LaPeste Jul 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦🏻.... makes sense

- #@ template.replace(uploadPackageArtifacts())
- #@ template.replace(buildUnityPackage())
run-tests-net-framework:
Expand Down
73 changes: 59 additions & 14 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ jobs:
uses: actions/checkout@v2
with:
submodules: recursive
ref: ${{ github.event.pull_request.head.sha }}
- name: Register csc problem matcher
run: echo "::add-matcher::.github/problem-matchers/csc.json"
- name: Register msvc problem matcher
Expand Down Expand Up @@ -41,6 +42,7 @@ jobs:
uses: actions/checkout@v2
with:
submodules: recursive
ref: ${{ github.event.pull_request.head.sha }}
- name: Register csc problem matcher
run: echo "::add-matcher::.github/problem-matchers/csc.json"
- name: Register msvc problem matcher
Expand Down Expand Up @@ -68,6 +70,7 @@ jobs:
uses: actions/checkout@v2
with:
submodules: recursive
ref: ${{ github.event.pull_request.head.sha }}
- name: Register csc problem matcher
run: echo "::add-matcher::.github/problem-matchers/csc.json"
- name: Register msvc problem matcher
Expand Down Expand Up @@ -102,6 +105,7 @@ jobs:
uses: actions/checkout@v2
with:
submodules: recursive
ref: ${{ github.event.pull_request.head.sha }}
- name: Register csc problem matcher
run: echo "::add-matcher::.github/problem-matchers/csc.json"
- name: Register msvc problem matcher
Expand Down Expand Up @@ -134,6 +138,7 @@ jobs:
uses: actions/checkout@v2
with:
submodules: recursive
ref: ${{ github.event.pull_request.head.sha }}
- name: Register csc problem matcher
run: echo "::add-matcher::.github/problem-matchers/csc.json"
- name: Register msvc problem matcher
Expand All @@ -153,11 +158,8 @@ jobs:
if: steps.check-cache.outputs.cache-hit != 'true'
- name: Setup Vcpkg
run: |
Write-Output 'Beginning download...'
Invoke-WebRequest -Uri https://static.realm.io/downloads/vcpkg.zip -OutFile C:\vcpkg.zip
Write-Output ((Get-Item C:\vcpkg.zip).length/1MB)
Expand-Archive -Path C:\vcpkg.zip -DestinationPath C:\
Write-Output 'Completed!'
shell: powershell
if: steps.check-cache.outputs.cache-hit != 'true' && steps.check-vcpkg-cache.outputs.cache-hit != 'true'
- name: Build wrappers
Expand All @@ -183,6 +185,7 @@ jobs:
uses: actions/checkout@v2
with:
submodules: recursive
ref: ${{ github.event.pull_request.head.sha }}
- name: Register csc problem matcher
run: echo "::add-matcher::.github/problem-matchers/csc.json"
- name: Register msvc problem matcher
Expand All @@ -202,11 +205,8 @@ jobs:
if: steps.check-cache.outputs.cache-hit != 'true'
- name: Setup Vcpkg
run: |
Write-Output 'Beginning download...'
Invoke-WebRequest -Uri https://static.realm.io/downloads/vcpkg.zip -OutFile C:\vcpkg.zip
Write-Output ((Get-Item C:\vcpkg.zip).length/1MB)
Expand-Archive -Path C:\vcpkg.zip -DestinationPath C:\
Write-Output 'Completed!'
shell: powershell
if: steps.check-cache.outputs.cache-hit != 'true' && steps.check-vcpkg-cache.outputs.cache-hit != 'true'
- name: Build wrappers
Expand Down Expand Up @@ -236,14 +236,28 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
submodules: false
ref: ${{ github.event.pull_request.head.sha }}
- name: Register csc problem matcher
run: echo "::add-matcher::.github/problem-matchers/csc.json"
- name: Register msvc problem matcher
run: echo "::add-matcher::.github/problem-matchers/msvc.json"
- name: Set version suffix
id: set-version-suffix
run: echo "::set-output name=build_suffix::${{ github.event_name == 'pull_request' && format('PR-{0}', github.event.number) || 'alpha'}}.${{ github.run_number }}"
run: |
$suffix = ""
if ($env:GITHUB_EVENT_NAME -eq "pull_request")
{
if (-Not $env:GITHUB_REF.Contains("release"))
{
$suffix = "PR-${{ github.event.number }}.$env:GITHUB_RUN_NUMBER"
}
}
else
{
$suffix = "alpha.$env:GITHUB_RUN_NUMBER"
}
echo "::set-output name=build_suffix::$suffix"
- name: Fetch artifacts for macos
uses: actions/download-artifact@v2
with:
Expand Down Expand Up @@ -320,6 +334,31 @@ jobs:
pkgName=${tmpName#"Realm.Fody."}
echo "::set-output name=package_version::$pkgName"
shell: bash
- name: Check Docfx cache
if: ${{ !contains(github.ref, 'release') }}
id: check-docfx-cache
uses: actions/cache@v2
with:
path: C:\docfx
key: docfx
- name: Download docfx
if: ${{ !contains(github.ref, 'release') }} && steps.check-docfx-cache.outputs.cache-hit != 'true'
run: |
Invoke-WebRequest -Uri https://github.com/dotnet/docfx/releases/download/v2.58/docfx.zip -OutFile C:\docfx.zip
Expand-Archive -Path C:\docfx.zip -DestinationPath C:\docfx
shell: powershell
- name: Build docs
if: ${{ !contains(github.ref, 'release') }}
run: |
C:\docfx\docfx Docs/docfx.json
Compress-Archive -Path Docs/_site -DestinationPath "Realm/packages/Docs.zip"
- name: Store docs artifacts
if: ${{ !contains(github.ref, 'release') }}
uses: actions/upload-artifact@v2
with:
name: Docs.zip
path: ${{ github.workspace }}/Realm/packages/Docs.zip
retention-days: ${{ github.event_name != 'pull_request' && 30 || 1 }}
- name: Store artifacts for Realm.Fody
uses: actions/upload-artifact@v2
with:
Expand Down Expand Up @@ -360,7 +399,8 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
submodules: false
ref: ${{ github.event.pull_request.head.sha }}
- name: Register csc problem matcher
run: echo "::add-matcher::.github/problem-matchers/csc.json"
- name: Register msvc problem matcher
Expand Down Expand Up @@ -396,7 +436,8 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
submodules: false
ref: ${{ github.event.pull_request.head.sha }}
- name: Register csc problem matcher
run: echo "::add-matcher::.github/problem-matchers/csc.json"
- name: Register msvc problem matcher
Expand Down Expand Up @@ -472,7 +513,8 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
submodules: false
ref: ${{ github.event.pull_request.head.sha }}
- name: Register csc problem matcher
run: echo "::add-matcher::.github/problem-matchers/csc.json"
- name: Register msvc problem matcher
Expand Down Expand Up @@ -508,7 +550,8 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
submodules: false
ref: ${{ github.event.pull_request.head.sha }}
- name: Register csc problem matcher
run: echo "::add-matcher::.github/problem-matchers/csc.json"
- name: Register msvc problem matcher
Expand Down Expand Up @@ -542,7 +585,8 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
submodules: false
ref: ${{ github.event.pull_request.head.sha }}
- name: Register csc problem matcher
run: echo "::add-matcher::.github/problem-matchers/csc.json"
- name: Register msvc problem matcher
Expand Down Expand Up @@ -580,7 +624,8 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
submodules: false
ref: ${{ github.event.pull_request.head.sha }}
- name: Register csc problem matcher
run: echo "::add-matcher::.github/problem-matchers/csc.json"
- name: Register msvc problem matcher
Expand Down
54 changes: 54 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: release
on:
workflow_dispatch

jobs:
publish-to-nuget:
runs-on: windows-latest
name: Publish nupkg to nuget
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Download all artifacts
uses: dawidd6/action-download-artifact@v2
with:
workflow: main.yml
branch: ${{ github.ref }}
path: ${{ github.workspace }}/Realm/packages/
- name: Extract release notes
run: |
$changelogContent = Get-Content -Raw -Path "./CHANGELOG.md"
$output_file = "./RELEASE_NOTES.md"
$regex = "(?sm)^(## \d{1,2}\.\d{1,2}\.\d{1,2}(?:-beta\.\d{1,2})? \(\d{4}-\d{2}-\d{2}\))(.+?)(\n## \d{1,2}\.\d{1,2}\.\d{1,2}(?:-beta\.\d{1,2})? \(\d{4}-\d{2}-\d{2}\))"
$changelogContent -match $regex
Set-Content -Path $output_file -Value $Matches[2]
echo "RELEASE_NOTES=$output_file" | Out-File $env:GITHUB_ENV -Encoding utf8
- name: Extract release name
run: |
$nupkgName = ls "Realm/packages/Realm.Fody.$env:RELEASE_VERSION.nupkg" | select -expandproperty Name
$nupkgName -match "(?sm)\d{1,2}\.\d{1,2}\.\d{1,2}(?:-beta\.\d{1,2})?"
$version = $Matches[0]
echo "RELEASE_VERSION=$version" | Out-File $env:GITHUB_ENV -Encoding utf8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than use env variables here, I think we should use set-output

Copy link
Contributor Author

@LaPeste LaPeste Jul 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can do that but, what's the advantage? As a con it has longer syntax, as a pro maybe it's easier to understand where it's set (although a search in the editor achieves the same). What other pros do you see?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generally dislike env variables as a way of passing data between steps. The step outputs are designed precisely for that and we should prefer to use them.

- name: Upload to nuget
run: |
dotnet nuget push "Realm/packages/Realm.Fody.$env:RELEASE_VERSION.nupkg/Realm.Fody.$env:RELEASE_VERSION.nupkg" --skip-duplicate --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
dotnet nuget push "Realm/packages/Realm.$env:RELEASE_VERSION.nupkg/Realm.$env:RELEASE_VERSION.nupkg" --skip-duplicate --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
- name: Create Github Release
uses: ncipollo/release-action@v1
with:
artifacts: Realm/packages/io.realm.unity-$env:RELEASE_VERSION.tgz/io.realm.unity-$env:RELEASE_VERSION.tgz
bodyFile: ${{ env.RELEASE_NOTES }}
name: "${{ env.RELEASE_VERSION }} - expand with the main features of this release"
commit: ${{ github.ref }}
tag: ${{ env.RELEASE_VERSION }}
token: ${{ secrets.GITHUB_TOKEN }}
draft: true
- name: Upload docs
run: |
Expand-Archive -Path Realm/packages/Docs.zip/Docs.zip -DestinationPath Realm/packages
py -m pip install s3cmd
$versions = $env:RELEASE_VERSION, "latest"
Foreach ($ver in $versions)
{
s3cmd put --recursive --acl-public --access_key=${{ secrets.DOCS_S3_ACCESS_KEY }} --secret_key=${{ secrets.DOCS_S3_SECRET_KEY }} "${{ github.workspace }}/Realm/packages/_site s3://realm-sdks/realm-sdks/dotnet/$ver/
}