diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0e0d2710689a5..ba96e6e9c9173 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,7 +52,24 @@ jobs: ti diagnose ti test -vr2 -t2 - # TODO(#1580): Upload to PyPI. Need to set up PYPI_PWD in the secrets first. + - name: Create Python Wheel + run: | + export TAICHI_REPO_DIR=`pwd` + export PATH=$TAICHI_REPO_DIR/bin:$PATH + export PATH=$TAICHI_REPO_DIR/taichi-llvm/bin/:$PATH + export PYTHONPATH=$TAICHI_REPO_DIR/python + cd python + python build.py build + + - name: Archive Wheel Artifacts + # https://docs.github.com/en/actions/guides/storing-workflow-data-as-artifacts#uploading-build-and-test-artifacts + uses: actions/upload-artifact@v2 + with: + # While ${{ github.ref }} does provide the release tag, it is of + # format `refs/tags/`, which isn't a valid file path. + name: taichi-py${{ matrix.python }}-${{ matrix.os }}.whl + path: python/dist/*.whl + - name: Upload PyPI env: # https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets#using-encrypted-secrets-in-a-workflow @@ -63,4 +80,4 @@ jobs: export PATH=$TAICHI_REPO_DIR/taichi-llvm/bin/:$PATH export PYTHONPATH=$TAICHI_REPO_DIR/python cd python - python build.py try_upload && bash <(curl -s https://codecov.io/bash) + python build.py try_upload --skip_build && bash <(curl -s https://codecov.io/bash) diff --git a/python/build.py b/python/build.py index d0ea9b4c24c56..4f812da471aa4 100644 --- a/python/build.py +++ b/python/build.py @@ -5,26 +5,6 @@ import shutil import taichi as ti -if len(sys.argv) != 2: - print("Usage: python3 build.py [upload/try_upload/test]") - exit(-1) - -version = ti.core.get_version_string() -gpu = ti.core.with_cuda() -mode = sys.argv[1] - -env_pypi_pwd = os.environ.get('PYPI_PWD', '') - -if mode == 'try_upload': - if env_pypi_pwd == '': - print("Missing environment variable PYPI_PWD") - print("Giving up and exiting 0 [try_upload mode]") - exit(0) - mode = 'upload' - -if mode == 'upload' and env_pypi_pwd == '': - assert False, "Missing environment variable PYPI_PWD" - def get_os_name(): name = platform.platform() @@ -43,123 +23,165 @@ def get_python_executable(): return '"' + sys.executable.replace('\\', '/') + '"' -if platform.system() == 'Linux': - if os.environ.get('CXX', - 'clang++') not in ['clang++-8', 'clang++-7', 'clang++']: - print('Only the wheel with clang will be released to PyPI.') - sys.exit(-1) - -with open('../setup.py') as fin: - with open('setup.py', 'w') as fout: - project_name = 'taichi' - print("project_name = '{}'".format(project_name), file=fout) - print("version = '{}'".format(version), file=fout) - for l in fin: - print(l, file=fout, end='') - -print("*** project_name = '{}'".format(project_name)) - -try: - os.remove('taichi/CHANGELOG.md') -except FileNotFoundError: - pass -shutil.rmtree('taichi/lib', ignore_errors=True) -shutil.rmtree('taichi/tests', ignore_errors=True) -shutil.rmtree('taichi/examples', ignore_errors=True) -shutil.rmtree('taichi/assets', ignore_errors=True) -os.makedirs('taichi/lib', exist_ok=True) -shutil.rmtree('build', ignore_errors=True) -shutil.rmtree('dist', ignore_errors=True) -shutil.rmtree('taichi/include', ignore_errors=True) -# shutil.copytree('../include/', 'taichi/include') -build_dir = '../build' - -if get_os_name() == 'linux': - shutil.copy('../build/libtaichi_core.so', 'taichi/lib/taichi_core.so') -elif get_os_name() == 'osx': - shutil.copy('../build/libtaichi_core.dylib', 'taichi/lib/taichi_core.so') -else: - shutil.copy('../runtimes/RelWithDebInfo/taichi_core.dll', - 'taichi/lib/taichi_core.pyd') - -os.system(f'cd .. && {get_python_executable()} -m taichi changelog --save') - -try: - with open('../CHANGELOG.md') as f: - print(f.read()) -except FileNotFoundError: - print('CHANGELOG.md not found') - pass - -try: - shutil.copy('../CHANGELOG.md', './taichi/CHANGELOG.md') -except FileNotFoundError: - pass -shutil.copytree('../tests/python', './taichi/tests') -shutil.copytree('../examples', './taichi/examples') -shutil.copytree('../external/assets', './taichi/assets') - -if get_os_name() != 'osx': - libdevice_path = ti.core.libdevice_path() - print("copying libdevice:", libdevice_path) - assert os.path.exists(libdevice_path) - shutil.copy(libdevice_path, 'taichi/lib/slim_libdevice.10.bc') - -ti.core.compile_runtimes() -runtime_dir = ti.core.get_runtime_dir() -for f in os.listdir(runtime_dir): - if f.startswith('runtime_') and f.endswith('.bc'): - print(f"Fetching runtime file {f}") - shutil.copy(os.path.join(runtime_dir, f), 'taichi/lib') - -print("Using python executable", get_python_executable()) -os.system('{} -m pip install --user --upgrade twine setuptools wheel'.format( - get_python_executable())) - -if get_os_name() == 'linux': - os.system('{} setup.py bdist_wheel -p manylinux1_x86_64'.format( - get_python_executable())) -else: - os.system('{} setup.py bdist_wheel'.format(get_python_executable())) - -shutil.rmtree('taichi/lib') -shutil.rmtree('taichi/tests') -shutil.rmtree('taichi/examples') -shutil.rmtree('taichi/assets') -try: - os.remove('taichi/CHANGELOG.md') -except FileNotFoundError: - pass -shutil.rmtree('./build') - -if mode == 'upload': +def build(): + """Build and package the wheel file in `python/dist`""" + if platform.system() == 'Linux': + if os.environ.get( + 'CXX', 'clang++') not in ['clang++-8', 'clang++-7', 'clang++']: + raise RuntimeError( + 'Only the wheel with clang will be released to PyPI') + + version = ti.core.get_version_string() + with open('../setup.py') as fin: + with open('setup.py', 'w') as fout: + project_name = 'taichi' + print("project_name = '{}'".format(project_name), file=fout) + print("version = '{}'".format(version), file=fout) + for l in fin: + print(l, file=fout, end='') + + print("*** project_name = '{}'".format(project_name)) + + try: + os.remove('taichi/CHANGELOG.md') + except FileNotFoundError: + pass + shutil.rmtree('taichi/lib', ignore_errors=True) + shutil.rmtree('taichi/tests', ignore_errors=True) + shutil.rmtree('taichi/examples', ignore_errors=True) + shutil.rmtree('taichi/assets', ignore_errors=True) + os.makedirs('taichi/lib', exist_ok=True) + shutil.rmtree('build', ignore_errors=True) + shutil.rmtree('dist', ignore_errors=True) + shutil.rmtree('taichi/include', ignore_errors=True) + # shutil.copytree('../include/', 'taichi/include') + build_dir = '../build' + + if get_os_name() == 'linux': + shutil.copy('../build/libtaichi_core.so', 'taichi/lib/taichi_core.so') + elif get_os_name() == 'osx': + shutil.copy('../build/libtaichi_core.dylib', + 'taichi/lib/taichi_core.so') + else: + shutil.copy('../runtimes/RelWithDebInfo/taichi_core.dll', + 'taichi/lib/taichi_core.pyd') + + os.system(f'cd .. && {get_python_executable()} -m taichi changelog --save') + + try: + with open('../CHANGELOG.md') as f: + print(f.read()) + except FileNotFoundError: + print('CHANGELOG.md not found') + pass + + try: + shutil.copy('../CHANGELOG.md', './taichi/CHANGELOG.md') + except FileNotFoundError: + pass + shutil.copytree('../tests/python', './taichi/tests') + shutil.copytree('../examples', './taichi/examples') + shutil.copytree('../external/assets', './taichi/assets') + + if get_os_name() != 'osx': + libdevice_path = ti.core.libdevice_path() + print("copying libdevice:", libdevice_path) + assert os.path.exists(libdevice_path) + shutil.copy(libdevice_path, 'taichi/lib/slim_libdevice.10.bc') + + ti.core.compile_runtimes() + runtime_dir = ti.core.get_runtime_dir() + for f in os.listdir(runtime_dir): + if f.startswith('runtime_') and f.endswith('.bc'): + print(f"Fetching runtime file {f}") + shutil.copy(os.path.join(runtime_dir, f), 'taichi/lib') + + print("Using python executable", get_python_executable()) os.system( - '{} -m twine upload dist/* --verbose -u yuanming-hu -p {}'.format( - get_python_executable(), - '%PYPI_PWD%' if get_os_name() == 'win' else '$PYPI_PWD')) -elif mode == 'test': - print('Uninstalling old taichi packages...') - os.system(f'{get_python_executable()} -m pip uninstall taichi-nightly') - os.system(f'{get_python_executable()} -m pip uninstall taichi') - dists = os.listdir('dist') - assert len(dists) == 1 - dist = dists[0] - print('Installing ', dist) - os.environ['PYTHONPATH'] = '' - os.makedirs('test_env', exist_ok=True) - os.system('cd test_env && {} -m pip install ../dist/{} --user'.format( - get_python_executable(), dist)) - print('Entering test environment...') - if get_os_name() == 'win': - os.system( - 'cmd /V /C "set PYTHONPATH=&& set TAICHI_REPO_DIR=&& cd test_env && cmd"' - ) + '{} -m pip install --user --upgrade twine setuptools wheel'.format( + get_python_executable())) + + if get_os_name() == 'linux': + os.system('{} setup.py bdist_wheel -p manylinux1_x86_64'.format( + get_python_executable())) else: + os.system('{} setup.py bdist_wheel'.format(get_python_executable())) + + shutil.rmtree('taichi/lib') + shutil.rmtree('taichi/tests') + shutil.rmtree('taichi/examples') + shutil.rmtree('taichi/assets') + try: + os.remove('taichi/CHANGELOG.md') + except FileNotFoundError: + pass + shutil.rmtree('./build') + + +def parse_args(): + parser = argparse.ArgumentParser(description=( + 'Build and uploads wheels to PyPI. Make sure to run this script ' + 'inside `python/`')) + parser.add_argument('mode', + type=str, + default='', + help=('Choose one of the modes: ' + '[build, test, try_upload, upload]')) + parser.add_argument('--skip_build', + action='store_true', + help=('Skip the build process if this is enabled')) + return parser.parse_args() + + +def main(): + args = parse_args() + mode = args.mode + + env_pypi_pwd = os.environ.get('PYPI_PWD', '') + if mode == 'try_upload': + if env_pypi_pwd == '': + print("Missing environment variable PYPI_PWD") + print("Giving up and exiting 0 [try_upload mode]") + exit(0) + mode = 'upload' + + if mode == 'upload' and env_pypi_pwd == '': + raise RuntimeError("Missing environment variable PYPI_PWD") + + if not args.skip_build: + build() + + if mode == 'build': + return + elif mode == 'upload': os.system( - 'cd test_env && PYTHONPATH= TAICHI_REPO_DIR= bash --noprofile --norc ' - ) -elif mode == '': - pass -else: - print("Unknown mode: ", mode) - exit(-1) + '{} -m twine upload dist/* --verbose -u yuanming-hu -p {}'.format( + get_python_executable(), + '%PYPI_PWD%' if get_os_name() == 'win' else '$PYPI_PWD')) + elif mode == 'test': + print('Uninstalling old taichi packages...') + os.system(f'{get_python_executable()} -m pip uninstall taichi-nightly') + os.system(f'{get_python_executable()} -m pip uninstall taichi') + dists = os.listdir('dist') + assert len(dists) == 1 + dist = dists[0] + print('Installing ', dist) + os.environ['PYTHONPATH'] = '' + os.makedirs('test_env', exist_ok=True) + os.system('cd test_env && {} -m pip install ../dist/{} --user'.format( + get_python_executable(), dist)) + print('Entering test environment...') + if get_os_name() == 'win': + os.system( + 'cmd /V /C "set PYTHONPATH=&& set TAICHI_REPO_DIR=&& cd test_env && cmd"' + ) + else: + os.system( + 'cd test_env && PYTHONPATH= TAICHI_REPO_DIR= bash --noprofile --norc ' + ) + else: + raise ValueError("Unknown mode: %s" % mode) + + +if __name__ == '__main__': + main()