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

Skip Building on incompatible Python Versions #7160

Merged
merged 8 commits into from
Sep 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion build_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def create_package(name, dest_folder=DEFAULT_DEST_FOLDER):
absdirs = [os.path.dirname(package) for package in (glob.glob('{}/setup.py'.format(name)) + glob.glob('sdk/*/{}/setup.py'.format(name)))]

absdirpath = os.path.abspath(absdirs[0])
check_call(['python', 'setup.py', 'bdist_wheel', '--universal', '-d', dest_folder], cwd=absdirpath)
check_call(['python', 'setup.py', 'bdist_wheel', '-d', dest_folder], cwd=absdirpath)
check_call(['python', 'setup.py', "sdist", "--format", "zip", '-d', dest_folder], cwd=absdirpath)


Expand Down
12 changes: 11 additions & 1 deletion doc/dev/engineering_assumptions.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
## Engineering System Assumptions, Gotchas, and Minutiae

1. All wheels are generated with `--universal` flag passed to `bdist_wheel`. We do not currently allow non-universal packages to be shipped out of this repository.
1. All wheels are generated with influence from `setup.cfg` at the built package root. This means that for most of our packages, the `universal` flag is set within the `setup.cfg`.

```
[bdist_wheel]
universal=1
```
2. If a package does _not_ have `universal` set, then the default build will produce a wheel for `py3`. This is due to the fact that wheels are currently generated by a CI step running `Python 3.6`.

## Build

Expand All @@ -15,3 +21,7 @@ Build CI for `azure-sdk-for-python` essentially builds and tests packages in one
1. Install on packages (and their dev_requirements!) in one go.
2. Run `pytest <folder1>, pytest <folder2>` where folders correspond to package folders
1. While all packages are installed alongside each other, each test run is individual to the package. This has the benefit of not allowing `packageA`'s `conftest.py` to mess with `packageB`'s environment.'

### So when do they run?

A standard pull request will option target the `Individual Packages` option. On a nightly cadence, the `python - client` build installs the entire world and tests in _both_ methodologies outlined above.
2 changes: 1 addition & 1 deletion eng/pipelines/templates/steps/build-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ steps:
versionSpec: $(PythonVersion)

- script: |
pip install wheel setuptools pathlib twine readme-renderer[md]
pip install wheel setuptools pathlib twine readme-renderer[md] packaging
displayName: 'Prep Environment'

- task: PythonScript@0
Expand Down
2 changes: 1 addition & 1 deletion eng/pipelines/templates/steps/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ steps:
arguments: ${{ parameters.OSName }}

- script: |
pip install pathlib twine codecov beautifulsoup4 tox tox-monorepo
pip install pathlib twine codecov beautifulsoup4 tox tox-monorepo packaging
displayName: 'Prep Environment'

- ${{ parameters.BeforeTestSteps }}
Expand Down
2 changes: 1 addition & 1 deletion eng/pipelines/templates/steps/test-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ steps:
arguments: ${{ parameters.OSName }}

- script: |
pip install pathlib twine codecov beautifulsoup4 tox tox-monorepo
pip install pathlib twine codecov beautifulsoup4 tox tox-monorepo packaging
displayName: 'Prep Environment'

- ${{ parameters.BeforeTestSteps }}
Expand Down
1 change: 0 additions & 1 deletion eng/tox/create_wheel_and_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ def cleanup_build_artifacts(build_folder):
"python",
os.path.join(args.target_setup, "setup.py"),
"bdist_wheel",
"--universal",
"-d",
args.distribution_directory,
]
Expand Down
79 changes: 71 additions & 8 deletions scripts/devops_tasks/common_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,44 @@
# package targeting during release.

import glob
from pathlib import Path
from subprocess import check_call, CalledProcessError
import os
import errno
import shutil
import sys
import logging
import ast
import textwrap
import io
import re
import pdb

# ssumes the presence of setuptools
from pkg_resources import parse_version

# this assumes the presence of "packaging"
from packaging.specifiers import SpecifierSet
from packaging.version import Version


logging.getLogger().setLevel(logging.INFO)

OMITTED_CI_PACKAGES = ["azure-mgmt-documentdb", "azure-servicemanagement-legacy"]

MANAGEMENT_PACKAGE_IDENTIFIERS = [
"mgmt",
"azure-cognitiveservices",
"azure-servicefabric",
"azure-nspkg",
]


def log_file(file_location, is_error=False):
with open(file_location, "r") as file:
for line in file:
sys.stdout.write(line)
sys.stdout.write("\n")
# CI consistently sees outputs in the wrong location. Trying this to see if it helps
sys.stdout.flush()



def read_file(file_location):
str_buffer = ""
with open(file_location, "r") as file:
Expand Down Expand Up @@ -68,6 +76,57 @@ def clean_coverage(coverage_dir):
else:
raise

def parse_setup_requires(setup_path):
setup_filename = os.path.join(setup_path, 'setup.py')
mock_setup = textwrap.dedent('''\
def setup(*args, **kwargs):
__setup_calls__.append((args, kwargs))
''')
parsed_mock_setup = ast.parse(mock_setup, filename=setup_filename)
with io.open(setup_filename, 'r', encoding='utf-8-sig') as setup_file:
parsed = ast.parse(setup_file.read())
for index, node in enumerate(parsed.body[:]):
if (
not isinstance(node, ast.Expr) or
not isinstance(node.value, ast.Call) or
not hasattr(node.value.func, 'id') or
node.value.func.id != 'setup'
):
continue
parsed.body[index:index] = parsed_mock_setup.body
break

fixed = ast.fix_missing_locations(parsed)
codeobj = compile(fixed, setup_filename, 'exec')
local_vars = {}
global_vars = {'__setup_calls__': []}
current_dir = os.getcwd()
working_dir = os.path.dirname(setup_filename)
os.chdir(working_dir)
exec(codeobj, global_vars, local_vars)
os.chdir(current_dir)
_, kwargs = global_vars['__setup_calls__'][0]

try:
python_requires = kwargs['python_requires']
# most do not define this, fall back to what we define as universal
except KeyError as e:
python_requires = ">=2.7"

return python_requires

def filter_for_compatibility(package_set):
collected_packages = []
v = sys.version_info
running_major_version = Version(".".join([str(v[0]), str(v[1]), str(v[2])]))

for pkg in package_set:
spec_set = SpecifierSet(parse_setup_requires(pkg))

if running_major_version in spec_set:
collected_packages.append(pkg)

return collected_packages

# this function is where a glob string gets translated to a list of packages
# It is called by both BUILD (package) and TEST. In the future, this function will be the central location
Expand All @@ -91,12 +150,17 @@ def process_glob_string(glob_string, target_root_dir):
# if we have individually queued this specific package, it's obvious that we want to build it specifically
# in this case, do not honor the omission list
if len(collected_directories) == 1:
return collected_directories
return filter_for_compatibility(collected_directories)
# however, if there are multiple packages being built, we should honor the omission list and NOT build the omitted
# packages
else:
return sorted(remove_omitted_packages(collected_directories))
allowed_package_set = remove_omitted_packages(collected_directories)
logging.info("Target packages after filtering by omission list: {}".format(allowed_package_set))

pkg_set_ci_filtered = filter_for_compatibility(allowed_package_set)
logging.info("Package(s) omitted by CI filter: {}".format(list(set(allowed_package_set) - set(pkg_set_ci_filtered))))

return sorted(pkg_set_ci_filtered)

def remove_omitted_packages(collected_directories):
return [
Expand All @@ -105,7 +169,6 @@ def remove_omitted_packages(collected_directories):
if os.path.basename(package_dir) not in OMITTED_CI_PACKAGES
]


def run_check_call(
command_array,
working_directory,
Expand Down
2 changes: 1 addition & 1 deletion sdk/redis/azure-mgmt-redis/dev_requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
-e ../../../tools/azure-sdk-tools
-e ../../../tools/azure-sdk-tools
2 changes: 1 addition & 1 deletion sdk/storage/azure-storage-file/dev_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
-e ../../../tools/azure-sdk-tools
-e ../../core/azure-core
aiohttp>=3.0; python_version >= '3.5'
pytest
pytest