Skip to content

Commit 2701194

Browse files
Add Conda Publishing (Azure#17889)
* Add Conda Build Steps and supporting infrastructure. * Add meta.yml and extensions to ci.yml in both sdk/core and sdk/storage * Add placeholder Conda release stage Co-authored-by: Sean Kane <68240067+seankane-msft@users.noreply.github.com>
1 parent f8e1b23 commit 2701194

File tree

11 files changed

+702
-0
lines changed

11 files changed

+702
-0
lines changed

doc/dev/conda-builds.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Azure SDK for Python Conda Distributions
2+
3+
## Local Environment Setup
4+
5+
Follow the instructions [here](https://docs.conda.io/projects/conda-build/en/latest/install-conda-build.html) to install `conda` and `conda-build`.
6+
7+
## CI Build Process
8+
9+
There will be a `CondaArtifact` defined in the `ci.yml` of each service directory. (`sdk/<service>`)
10+
11+
A Conda Artifact defines:
12+
- The location of the `meta.yml`
13+
- Which packages will be pulled into the combined artifact
14+
- The name of the combined artifact
15+
- Any other necessary details.
16+
17+
## How to Build an Azure SDK Conda Package Locally
18+
19+
20+
### Create Your Build Directory
21+
Given how Conda packages are comprised of multiple source distributions _combined_, the buildable source does not exist directly within the azure-sdk-for-python repo. Currently, there is _some_ manual work that needs to be done.
22+
23+
To begin, check your `ci.yml` for a `CondaArtifact`. Each these artifacts will become a single conda package. Let's use `storage/ci.yml` as an example.
24+
25+
```
26+
- name: azure-storage
27+
meta_source: meta.yml
28+
common_root: azure/storage
29+
checkout:
30+
- package: azure-storage-blob
31+
checkout_path: sdk/storage
32+
version: 12.8.0
33+
- package: azure-storage-queue
34+
checkout_path: sdk/storage
35+
version: 12.1.5
36+
- package: azure-storage-file-share
37+
checkout_path: sdk/storage
38+
version: 12.4.1
39+
- package: azure-storage-file-datalake
40+
checkout_path: sdk/storage
41+
version: 12.3.0
42+
```
43+
44+
- `name: azure-storage`: will be the name of the "combined" sdist package that we generate.
45+
- `meta_source: meta.yml`: this is the path (relative to the service directory) to the target conda package meta.yml.
46+
- `common_root: azure/storage`: when generating the combined package, where will we begin combining? This is tightly bound to folder structure within the generated sdist.
47+
- `checkout`: the `checkout` setting is a list of target packages that will go into the combined artifact. These targets will be individually sparse cloned, and copied into the conda build directory. Currently, this is a **manual step** in your local build. Reference `eng/pipelines/templates/get-tagged-code.yml` for exact details on how CI does it.
48+
49+
Before we continue, you should be aware of two primary locations that are necessary, but not referenced directly in the `ci.yml`.
50+
51+
The `build` folder and the `output` folder. The `build` folder (`$(Conda.Build)` variable in CI) is where we will...
52+
53+
- store the cloned package code
54+
- generate the combined sdist
55+
56+
To locally repro without magic given a specific `checkout` artifact:
57+
58+
```
59+
<cd sdk-for-python>
60+
git checkout `<package>_<version>`
61+
grab the entire <package> directory under the <checkout_path>. place into your `build` folder.
62+
```
63+
64+
Given the `storage` example. This is what your `build` folder should look like prior to invoking `build_conda_artifacts.py`.
65+
66+
```
67+
<build directory>/
68+
azure-storage-blob/ <-- the package directly ripped from specified tag
69+
azure-storage-file-datalake/
70+
azure-storage-file-share/
71+
azure-storage-queue/
72+
```
73+
74+
### Create the Combined SDist
75+
76+
Once you have a directory assembled, invoke the script to build. The below command is formatted for visibility, recombine the lines however necessary for your chosen shell environment.
77+
78+
79+
```
80+
python `build_conda_artifacts.py`
81+
-d "<output folder>"
82+
-b "<build folder>"
83+
-m "<resolvable path to sdk/storage/meta.yml>"
84+
-r "azure/storage"
85+
-n "azure-storage"
86+
-s "storage"
87+
```
88+
89+
### Generate the Conda Package
90+
91+
Locally, from the anaconda prompt, set the environment variable `STORAGE_SOURCE_DISTRIBUTION` to the location of the generated sdist. After that:
92+
93+
```bash
94+
export STORAGE_SOURCE_DISTRIBUTION=<path/to/generated/sdist>
95+
cd <meta.yml directory>
96+
conda-build . --output-folder <conda.output>
97+
```

eng/pipelines/templates/jobs/ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ parameters:
55
- name: Artifacts
66
type: object
77
default: []
8+
- name: CondaArtifacts
9+
type: object
10+
default: []
811
- name: TestPipeline
912
type: boolean
1013
default: false
@@ -61,6 +64,12 @@ jobs:
6164
TestPipeline: ${{ parameters.TestPipeline }}
6265
Artifacts: ${{ parameters.Artifacts }}
6366

67+
- template: ../steps/build-conda-artifacts.yml
68+
parameters:
69+
ServiceDirectory: ${{ parameters.ServiceDirectory }}
70+
TestPipeline: ${{ parameters.TestPipeline }}
71+
CondaArtifacts: ${{ parameters.CondaArtifacts }}
72+
6473
- job: 'Analyze'
6574
condition: and(succeededOrFailed(), ne(variables['Skip.Analyze'], 'true'))
6675
variables:
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
parameters:
2+
CondaArtifacts: []
3+
TestPipeline: false
4+
DependsOn: Build
5+
ArtifactName: 'not-specified'
6+
ServiceDirectory: 'not-specified'
7+
8+
9+
stages:
10+
- ${{if and(eq(variables['Build.Reason'], 'Manual'), eq(variables['System.TeamProject'], 'internal'))}}:
11+
- ${{ each artifact in parameters.CondaArtifacts }}:
12+
- stage: Release_${{ replace(artifact.name, '-', '_') }}
13+
displayName: 'Conda Release: ${{artifact.name}}'
14+
dependsOn: ${{parameters.DependsOn}}
15+
condition: and(succeeded(), ne(variables['SetDevVersion'], 'true'), ne(variables['Skip.Release'], 'true'), ne(variables['Build.Repository.Name'], 'Azure/azure-sdk-for-python-pr'))
16+
jobs:
17+
- deployment: CondaRelease
18+
displayName: "Publish Conda Artifacts"
19+
condition: ne(variables['Skip.TagRepository'], 'true')
20+
environment: pypi
21+
22+
pool:
23+
name: azsdk-pool-mms-ubuntu-1804-general
24+
vmImage: MMSUbuntu18.04
25+
26+
strategy:
27+
runOnce:
28+
deploy:
29+
steps:
30+
- checkout: self
31+
- pwsh: |
32+
Get-ChildItem -Recurse $(Pipeline.Workspace)/${{parameters.ArtifactName}}/${{artifact.name}}
33+
workingDirectory: $(Pipeline.Workspace)
34+
displayName: Output Visible Conda Artifacts

eng/pipelines/templates/stages/archetype-sdk-client.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ parameters:
55
- name: Artifacts
66
type: object
77
default: []
8+
- name: CondaArtifacts
9+
type: object
10+
default: []
811
- name: TestPipeline
912
type: boolean
1013
default: false
@@ -62,6 +65,7 @@ stages:
6265
parameters:
6366
ServiceDirectory: ${{ parameters.ServiceDirectory }}
6467
Artifacts: ${{ parameters.Artifacts }}
68+
CondaArtifacts: ${{ parameters.CondaArtifacts }}
6569
${{ if eq(parameters.ServiceDirectory, 'template') }}:
6670
TestPipeline: true
6771
BeforePublishSteps: ${{ parameters.BeforePublishSteps }}
@@ -85,6 +89,7 @@ stages:
8589
- ${{ each replacement in parameters.MatrixReplace }}:
8690
- ${{ replacement }}
8791

92+
8893
# The Prerelease and Release stages are conditioned on whether we are building a pull request and the branch.
8994
- ${{if and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['System.TeamProject'], 'internal'))}}:
9095
- template: archetype-python-release.yml
@@ -99,3 +104,15 @@ stages:
99104
TargetDocRepoOwner: ${{ parameters.TargetDocRepoOwner }}
100105
TargetDocRepoName: ${{ parameters.TargetDocRepoName }}
101106
DevFeedName: ${{ parameters.DevFeedName }}
107+
108+
# The Prerelease and Release stages are conditioned on whether we are building a pull request and the branch.
109+
- ${{if and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['System.TeamProject'], 'internal'))}}:
110+
- template: archetype-conda-release.yml
111+
parameters:
112+
DependsOn: Build
113+
ServiceDirectory: ${{ parameters.ServiceDirectory }}
114+
CondaArtifacts: ${{ parameters.CondaArtifacts }}
115+
ArtifactName: conda
116+
${{ if eq(parameters.ServiceDirectory, 'template') }}:
117+
TestPipeline: true
118+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
parameters:
2+
- name: TestPipeline
3+
type: boolean
4+
default: false
5+
- name: ServiceDirectory
6+
type: string
7+
default: ''
8+
- name: CondaArtifacts
9+
type: object
10+
default: []
11+
12+
steps:
13+
- template: /eng/common/pipelines/templates/steps/set-test-pipeline-version.yml
14+
parameters:
15+
PackageName: "azure-template"
16+
ServiceDirectory: "template"
17+
TestPipeline: ${{ parameters.TestPipeline }}
18+
19+
- task: UsePythonVersion@0
20+
displayName: 'Use Python $(PythonVersion)'
21+
inputs:
22+
versionSpec: $(PythonVersion)
23+
24+
- script: |
25+
pip install -r eng/ci_tools.txt
26+
displayName: 'Prep Environment'
27+
28+
- pwsh: |
29+
mkdir $(Agent.BuildDirectory)/conda/
30+
mkdir $(Agent.BuildDirectory)/conda/output
31+
mkdir $(Agent.BuildDirectory)/conda/build
32+
33+
Write-Host "##vso[task.setvariable variable=conda.output]$(Agent.BuildDirectory)/conda/output"
34+
Write-Host "##vso[task.setvariable variable=conda.build]$(Agent.BuildDirectory)/conda/build"
35+
displayName: 'Create Conda Working Directories'
36+
37+
- ${{ each artifact in parameters.CondaArtifacts }}:
38+
# there may be multiple CondaArtifacts. Be certain $(conda.build) is clean just in case!
39+
- pwsh:
40+
Write-Host "Clean up Conda Build Directory $(conda.build)"
41+
Remove-Item $(conda.build)/* -Recurse -Force
42+
displayName: 'Clean Up Before Building ${{ artifact.name }}'
43+
44+
- ${{ each checkout in artifact.checkout }}:
45+
- template: /eng/pipelines/templates/steps/get-tagged-code.yml
46+
parameters:
47+
DestinationDirectory: $(conda.build)/${{checkout.package}}
48+
Package: ${{checkout.package}}
49+
CheckoutPath: ${{checkout.checkout_path}}
50+
Version: ${{checkout.version}}
51+
52+
- task: PythonScript@0
53+
displayName: 'Build Source Distribution for ${{ artifact.name }}'
54+
inputs:
55+
scriptPath: 'scripts/devops_tasks/build_conda_artifacts.py'
56+
arguments: '-d "$(conda.output)" -b "$(conda.build)" -m "$(Build.SourcesDirectory)/sdk/${{ parameters.ServiceDirectory }}/${{ artifact.meta_source }}" -r "${{ artifact.common_root }}" -n "${{ artifact.name }}" -s "${{ parameters.ServiceDirectory }}" -o "${{ upper(parameters.ServiceDirectory) }}_SOURCE_DISTRIBUTION"'
57+
58+
- bash: |
59+
echo "##vso[task.prependpath]$CONDA/bin"
60+
displayName: 'Prepend PATH with Conda and INIT'
61+
62+
- bash: |
63+
conda create --yes --quiet --name ${{ artifact.name }}
64+
source activate ${{ artifact.name }}
65+
conda install --yes --quiet --name ${{ artifact.name }} conda-build
66+
displayName: 'Prepare Conda Environment for building ${{ artifact.name }}'
67+
68+
- bash: |
69+
source activate ${{ artifact.name }}
70+
conda-build . --output-folder "$(Agent.BuildDirectory)/conda/output" -c https://azuresdkconda.blob.core.windows.net/channel1/
71+
displayName: 'Activate Conda Environment and Build ${{ artifact.name }}'
72+
workingDirectory: $(Build.SourcesDirectory)/sdk/${{ parameters.ServiceDirectory }}
73+
74+
- template: /eng/common/pipelines/templates/steps/publish-artifact.yml
75+
parameters:
76+
ArtifactPath: '$(Agent.BuildDirectory)/conda/output'
77+
ArtifactName: 'conda'
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
parameters:
2+
- name: DestinationDirectory
3+
type: string
4+
default: ''
5+
- name: Package
6+
type: string
7+
default: ''
8+
- name: CheckoutPath
9+
type: string
10+
default: ''
11+
- name: Version
12+
type: string
13+
default: ''
14+
15+
steps:
16+
- pwsh: |
17+
$targetPath = "$(Agent.TempDirectory)/${{ parameters.Package }}"
18+
if (!(Test-Path $targetPath)) {
19+
mkdir $targetPath
20+
}
21+
22+
Write-Host "##vso[task.setvariable variable=Package.Clone]$targetPath"
23+
displayName: 'Prep for Sparse Checkout'
24+
25+
- template: /eng/common/pipelines/templates/steps/sparse-checkout.yml
26+
parameters:
27+
Paths:
28+
- "${{ parameters.CheckoutPath }}/${{ parameters.Package }}"
29+
Repositories:
30+
- Name: "Azure/azure-sdk-for-python"
31+
Commitish: "${{ parameters.Package }}_${{ parameters.Version }}"
32+
WorkingDirectory: "$(Package.Clone)"
33+
SkipDefaultCheckout: true
34+
35+
- pwsh: |
36+
$pathForCopy = Join-Path -Path "$(Package.Clone)" -ChildPath "${{ parameters.CheckoutPath }}/${{ parameters.Package }}"
37+
38+
Write-Host $pathForCopy
39+
Write-Host ${{ parameters.DestinationDirectory }}
40+
41+
Copy-Item -Path $pathForCopy -Destination ${{ parameters.DestinationDirectory }} -Recurse
42+
displayName: 'Copy Source to Target Directory'

0 commit comments

Comments
 (0)