---
layout: contribute
title: Building Python on Windows
---
# Building Python on Windows

**Status**: Implemented

**Author**: [Yun Peng](mailto:pcloudy@google.com)

**Relevant changes**:

1. [zipper now can specify actual path a file is added to](https://bazel-review.googlesource.com/4243)
2. [Create Python executable zip file](https://bazel-review.googlesource.com/4244)
3. [Make python executable zip a real self-extracting binary](https://bazel-review.googlesource.com/4263)
4. [Using stub\_template.txt as \_\_main\_\_.py and zip header in python executable zip](https://bazel-review.googlesource.com/5310)
5. [Get rid of python executable zip file header](https://bazel-review.googlesource.com/5350)
6. [Put runfiles tree under 'runfiles' directory to avoid conflict](https://bazel-review.googlesource.com/5351)

As we keep finding new problems and coming up with new solutions, you
can see some of the implementation in the previous changes has been
deprecated in the latter ones. Here we only present the final solution.

## Motivation

After providing basic support for C++ and Java on Windows (although not
perfect for C++ due to the wrapper scripts we use), Python becomes the
last language we need to fix in order to make Bazel no longer
experimental on Windows.

## Problem

Currently, as described on [bazel.build](/docs/be/python.html#py_binary),
the way py\_binary works on Unix is:

_A py\_binary is an executable Python program consisting of a collection
of `.py` source files (possibly belonging to other py\_library rules),
a `*.runfiles` directory tree containing all the code and data needed by
the program at run-time, and a [stub script](https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/bazel/rules/python/stub_template.txt)
that starts up the program with the correct initial environment and data._

This doesn’t work on Windows, because we don’t have runfiles tree on Windows.
A runfiles client library (which maps a runfile from runfiles path to
its real path) doesn’t solve the whole problem, because we also need
`__init__.py` file under every python source directory to make the
directory a recognizable python package. And it doesn’t make sense to
create `__init__.py` files outside of runfiles tree. Therefore, how to
find a way to run python binary properly on Windows is our main problem.

## Solution

Python has the ability to execute zip file as scripts since version 2.6.
When invoking the python interpreter with a zip file as the first argument,
it executes the `__main__.py` file in the root directory of the archive.

The idea of the solution is to create a self-extracting zip file which
packages everything supposed to be in the original runfiles tree and add
the stub script as the `__main__.py` file. We tell the stub script whether
it’s in a zip file or not. If it is, it first extracts the zip file to a
temporary directory as the runfiles tree, then set the correct environment
variables and runs the main python script. At the end of the execution,
it deletes the temporary directory.

We did three more things to achieve this goal:

#### 1. Implement new feature in zipper for packaging runfiles tree easily

Creating the zip file is not a trivial thing, since we don’t have runfiles
tree at all. We should not only archive every runfile into the right path,
but also adding `__init__.py` file to every directory. To make things
easier, we introduce a new feature in zipper (a custom zip tool of Bazel)
which makes users able to specify the actual path a file is archived into.
Zipper now supports the following semantics:

`zipper cC x.zip [<zip_path>=][<file>]`

Examples:

```bash
$ zipper cC x.zip a/b/lib.py                  # Add file a/b/lib.py
$ zipper cC x.zip a/b/__init__.py=            # Add an empty file at a/b/__init__.py
$ zipper cC x.zip a/b/main.py=foo/bar/bin.py  # Add file foo/bar/bin.py as a/b/main.py
```

With the help of this feature, we can easily control the directory
structure in the zip file.

#### 2. Build the final python binary

The final python binary is the zip file with a `#!/usr/bin/env python`
shebang. By doing this, we can run the binary in the following two ways:

```bash
$ ./bazel-bin/foo/bar/bin
$ python ./bazel-bin/foo/bar/bin
```

And we can use the second way to run the binary from Windows native
command line(cmd.exe). Since the zip file packages everything in runfiles
tree, it can be copied to anywhere as a self-contained executable binary.

#### 3. Add `--build_python_zip` flag

This flag can be used to tell Bazel whether or not it should build a zip file
as the final binary. By default, it’s enabled on Windows,
disabled on other platform.