How to Use AppVeyor to Build and Deploy Python Wheels from C/C++ Code

AppVeyor is a continuous integration (CI) service used to automatically build code projects and deploy relevant artifacts. It provides build environments for Windows, Linux, and macOS. In this article, I will share how to use AppVeyor to build and deploy Python Wheels (Windows edition) from C/C++ code.

Creating Python Wheels

It takes two steps to build a Python wheel from CPython code:

  1. Build *.pyd extension file from C/C++ code.
  2. Pack *.pyd and dependent *.dll files into a wheel file.

For the past few years, I’ve been maintaining the source code of Python barcode extension based on Dynamsoft Barcode Reader C/C++ SDK. Let’s do some changes based on the repository.

The *.pyd Python module

Get the source code:

git clone

Install DynamsoftBarcodeReader7.2.1.exe. Copy *.dll files from Dynamsoft\Barcode Reader 7.2.1\Components\C_C++\Redist\x64 to bin folder and copy DBRx64.lib from Dynamsoft\Barcode Reader 7.2.1\Components\C_C++\Lib\DBRx64.lib to lib folder. Create an empty folder named wheel.

pypi python project structure

Open src\ to set the link directories:

elif sys.platform == "win32":
    # Windows
    dbr_lib_name = 'DBRx64'
    dbr_lib_dir = r'..\\lib'
    dbr_dll = r'..\\bin'

Build the Python barcode module:

cd src
python build

The *.whl file

Create a folder named dbr under the wheel folder. Add a file:

from setuptools import setup, find_packages, Distribution
from codecs import open
from os import path
import sys

here = path.abspath(path.dirname(__file__))

with open(path.join(here, ''), encoding='utf-8') as f:
    long_description =

class BinaryDistribution(Distribution):
    """Distribution which always forces a binary package with platform name"""
    def has_ext_modules(foo):
        return True

data_info = {'dbr':\['\*.pyd', 'vcomp110.dll', 'DynamicPdfx64.dll', 'DynamsoftBarcodeReaderx64.dll', 'DynamsoftLicClientx64.dll'\]}

    description='Dynamsoft Barcode Reader Python project',  
    license = '',
        'Development Status :: 5 - Production/Stable',
        'Intended Audience :: Developers',
        'Topic :: Software Development :: Libraries',
        'License :: Other/Proprietary License',
        'Programming Language :: Python',
        'Programming Language :: Python :: 2',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: 3.8',
        'Programming Language :: C++',
        'Programming Language :: Python :: Implementation :: CPython',
        'Operating System :: POSIX :: Linux ',
        'Operating System :: Microsoft :: Windows :: Windows 10',
        'Topic :: Scientific/Engineering',
        'Topic :: Scientific/Engineering :: Image Recognition',
        'Topic :: Software Development',
    keywords='barcode DataMatrix QRCode 1D PDF417 MaxiCode Aztec',  
    packages=find_packages(exclude=\['contrib', 'docs', 'tests'\]),  
    install_requires=\['numpy', 'opencv-python'\],  
    platforms=\['Windows', 'Linux'\]

The data_info contains the *.dll and *.whl files.

Create under the dbr folder:

from .dbr import \*

Copy *.pyd and *.dll files to the dbr folder from the src folder:

copy ..\\bin\\\*.\* ..\\wheel\\dbr\\
cd build\\\*\\
copy \*.\* ..\\..\\..\\wheel\\dbr\\

Build the wheel package under the wheel folder:

python bdist_wheel

Configuring YAML file for AppVeyor Build Service

Login with your GitHub account.

Click Projects to import the target repository:

appveyor new project

To trigger the build, create an appveyor.yml file under the project root directory.

Set the branch to build:

    - wheel

Add the Python environments:

    - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
      PYTHON: "C:/Python27-x64"
    - PYTHON: "C:/Python35-x64"
    - PYTHON: "C:/Python36-x64"
    - PYTHON: "C:/Python37-x64"
    - PYTHON: "C:/Python38-x64"

Why do I use APPVEYOR_BUILD_WORKER_IMAGE  for Python 2.7? When building the source code with the default environment, I got the following error message:

Traceback (most recent call last):
  File "", line 63, in <module>
    cmdclass={'install': CustomInstall}
  File "C:\\Python27-x64\\lib\\distutils\\", line 151, in setup
  File "C:\\Python27-x64\\lib\\distutils\\", line 953, in run_commands
  File "C:\\Python27-x64\\lib\\distutils\\", line 972, in run_command
  File "C:\\Python27-x64\\lib\\distutils\\command\\", line 127, in run
  File "C:\\Python27-x64\\lib\\distutils\\", line 326, in run_command
  File "C:\\Python27-x64\\lib\\distutils\\", line 972, in run_command
  File "C:\\Python27-x64\\lib\\distutils\\command\\", line 340, in run
  File "C:\\Python27-x64\\lib\\distutils\\command\\", line 449, in build_extensions
  File "C:\\Python27-x64\\lib\\distutils\\command\\", line 499, in build_extension
  File "C:\\Python27-x64\\lib\\distutils\\", line 473, in compile
  File "C:\\Python27-x64\\lib\\distutils\\", line 383, in initialize
    vc_env = query_vcvarsall(VERSION, plat_spec)
  File "C:\\Python27-x64\\lib\\distutils\\", line 299, in query_vcvarsall
    raise ValueError(str(list(result.keys())))
ValueError: \[u'path'\]
Command exited with code 1

The issue seems caused by Visual Studio 2008. According to the official documentation – Windows images software, Visual C++ 2008 Express is not installed in the Visual Studio 2019 image. So I can use the Visual Studio 2019 image and set Visual Studio 2015 as the build tool to make it work:


Add the build script:

  - cmd: |
      "%PYTHON%/python.exe" -m pip install --upgrade pip
      "%PYTHON%/python.exe" -m pip install --upgrade setuptools wheel numpy

      cd src
      "%PYTHON%/python.exe" build

      copy ..\\bin\\\*.\* ..\\wheel\\dbr\\
      cd build\\\*\\
      copy \*.\* ..\\..\\..\\wheel\\dbr\\
      cd ..\\..\\..\\wheel\\

      "%PYTHON%/python.exe" bdist_wheel

Set the artifacts:

  - path: wheel\\dist\\\*.whl
    name: wheels

Add the scripts for PyPi deployment:

  - ps: |
      if($env:APPVEYOR_REPO_TAG -eq 'true') {
        Write-Output ("Deploying " + $env:APPVEYOR_REPO_TAG_NAME + " to PyPI...")
        &"${Env:PYTHON}/python.exe" -m pip install twine
        &"${Env:PYTHON}/python.exe" -m twine upload -u ${Env:USER} -p ${Env:PASS} --skip-existing dist/\*.whl
      } else {
        Write-Output "No tag for deployment"

When adding tags to the GitHub repository, the APPVEYOR_REPO_TAG value will be `true’. You can encrypt the username and password of the PyPi account by clicking Account > Encrypt YAML.

appveyor encrypt yaml

When building successfully, the *.whl files for Python 2.7, 3.5, 3.6, 3.7 and 3.8 will be uploaded to the PyPi website.

appveyor python build

appveyor pypi deployment


Source Code