diff --git a/hpvm/tools/py-approxhpvm/CMakeLists.txt b/hpvm/tools/py-approxhpvm/CMakeLists.txt
index 1e5eeb891d35fc028d1aa85c5e5e679902a4dad7..e0640cdf9ff09335a0245eb09dfd3e37be594dda 100644
--- a/hpvm/tools/py-approxhpvm/CMakeLists.txt
+++ b/hpvm/tools/py-approxhpvm/CMakeLists.txt
@@ -1,6 +1,11 @@
 # This file is very tightly coupled with main.py.in.
 # Watch out and keep them in sync.
-# main.py.in (to become approxhpvm.py) requires the following variables:
+
+# CMake fills in some variables in main.py.in and generate it into a python package:
+# `hpvmpy`, which is the main entry point and Python API for HPVM.
+
+# ---[ Define variables for main.py.in
+# main.py.in requires the following variables:
 # LLVM_PROJECT_DIR, LLVM_BUILD_DIR
 # TRT_PATH, TRT_INCLUDE_DIRS, TRT_LINK_DIRS, TRT_LINK_LIBS
 # DIRECT_LINK_LIBS
@@ -34,21 +39,37 @@ set(
     LLVMGenHPVM
 )
 
+# ---[ Create package folder structure
+# This sounds crazy but since main.py.in is generated into another file under build/ dir,
+# to make a python package around it, we'll have to generate the whole package structure
+# in build/ as well.
+# Target dir structure:
+# ${CMAKE_CURRENT_BINARY_DIR}
+#   hpvmpy/
+#     __init__.py  <- generated from main.py.in
+#   setup.py       <- copied from setup.py
+file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/hpvmpy)
+file(COPY setup.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+
+# ---[ Generate main.py.in to hpvmpy/__init__.py
+set(init_path ${CMAKE_CURRENT_BINARY_DIR}/hpvmpy/__init__.py)
 # First resolve all `@symbol@` by configuring the file
 configure_file(main.py.in ${CMAKE_CURRENT_BINARY_DIR}/main.py.conf)
 # Then resolve all generator expressions we configured into the previous file
 file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/main.py INPUT ${CMAKE_CURRENT_BINARY_DIR}/main.py.conf)
 # Delibrately create an extra step of moving file
 # which is carried out at build time (as a target)
-# so we can set these dependencies on it
-set(
-    DEPS
-    tensor_runtime hpvm-rt-bc clang opt llvm-link ${AVAILABLE_PASSES}
-)
+# so we can set these dependencies on it.
+set(DEPS tensor_runtime hpvm-rt-bc clang opt llvm-link ${AVAILABLE_PASSES})
 add_custom_command(
-    OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/approxhpvm.py
-    COMMAND cp ${CMAKE_CURRENT_BINARY_DIR}/main.py ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/approxhpvm.py
-    COMMAND chmod +x ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/approxhpvm.py
+    OUTPUT ${init_path}
+    COMMAND cp ${CMAKE_CURRENT_BINARY_DIR}/main.py ${init_path}
     DEPENDS ${DEPS} ${CMAKE_CURRENT_BINARY_DIR}/main.py
 )
-add_custom_target(approxhpvm.py ALL DEPENDS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/approxhpvm.py)
+
+# ---[ Call python3 -m pip to install this package.
+add_custom_target(
+    hpvmpy
+    COMMAND python3 -m pip install ./
+    DEPENDS ${init_path} setup.py
+)
diff --git a/hpvm/tools/py-approxhpvm/main.py.in b/hpvm/tools/py-approxhpvm/main.py.in
index 7b211911643c64d8bf2c34ef8a43e3ac98cdd88a..4062d047a40473776fe2ff5b331eb2a618681083 100644
--- a/hpvm/tools/py-approxhpvm/main.py.in
+++ b/hpvm/tools/py-approxhpvm/main.py.in
@@ -183,5 +183,9 @@ def parse_args():
     return args
 
 
-if __name__ == "__main__":
+def main():
     compile_hpvm_c(**vars(parse_args()))
+
+
+if __name__ == "__main__":
+    main()
diff --git a/hpvm/tools/py-approxhpvm/setup.py b/hpvm/tools/py-approxhpvm/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..75262a069de32e6f5de4bff89738e0011dd91326
--- /dev/null
+++ b/hpvm/tools/py-approxhpvm/setup.py
@@ -0,0 +1,15 @@
+import setuptools
+
+setuptools.setup(
+    name="hpvmpy",
+    version="1.0",
+    author="Yifan Zhao",
+    author_email="yifanz16@illinois.edu",
+    description="HPVM Python API",
+    packages=["hpvmpy"],
+    entry_points={
+        "console_scripts": [
+            "hpvm-clang = hpvmpy:main",
+        ],
+    },
+)