diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 61a92d7dfaf7bb5a4c4e6b29e79b06837adfd978..5720727b3052b5fdb83fc80614697880d036c4f0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -24,12 +24,11 @@ build:
 test_obfs4proxy:
   stage: test
   script:
-    - docker run -v `pwd`:/opt/go-reproducer ${TEST_IMAGE} /bin/bash -c "./build-binary.py obfs4proxy && ./verify-binary.py obfs4proxy"
+    - docker run -v `pwd`/output:/opt/go-reproducer/output ${TEST_IMAGE} /bin/bash -c "./build-binary.py obfs4proxy && ./verify-binary.py obfs4proxy"
   allow_failure: true
   artifacts:
     paths:
-      - obfs4proxy-*.jar
-      - obfs4proxy-*.pom
+      - output/obfs4proxy
     expire_in: 1 week
     when: always
   except:
@@ -38,12 +37,11 @@ test_obfs4proxy:
 test_snowflake:
   stage: test
   script:
-    - docker run -v `pwd`:/opt/go-reproducer ${TEST_IMAGE} /bin/bash -c "./build-binary.py snowflake && ./verify-binary.py snowflake"
+    - docker run -v `pwd`/output:/opt/go-reproducer/output ${TEST_IMAGE} /bin/bash -c "./build-binary.py snowflake && ./verify-binary.py snowflake"
   allow_failure: true
   artifacts:
     paths:
-      - snowflake-*.jar
-      - snowflake-*.pom
+      - output/snowflake
     expire_in: 1 week
     when: always
   except:
@@ -52,16 +50,12 @@ test_snowflake:
 test_tag:
   stage: test
   script:
-    - docker run -v `pwd`:/opt/go-reproducer ${TEST_IMAGE} /bin/bash -c "./verify-binary.py obfs4proxy ${CI_BUILD_REF_NAME}"
-    - docker run -v `pwd`:/opt/go-reproducer ${TEST_IMAGE} /bin/bash -c "./verify-binary.py snowflake ${CI_BUILD_REF_NAME}"
+    - docker run -v `pwd`/output:/opt/go-reproducer/output ${TEST_IMAGE} /bin/bash -c "./verify-binary.py obfs4proxy ${CI_BUILD_REF_NAME}"
+    - docker run -v `pwd`/output:/opt/go-reproducer/output ${TEST_IMAGE} /bin/bash -c "./verify-binary.py snowflake ${CI_BUILD_REF_NAME}"
   artifacts:
     paths:
-    - obfs4proxy-*.zip
-    - obfs4proxy-*.pom
-    - obfs4proxy-*-sources.jar
-    - snowflake-*.zip
-    - snowflake-*.pom
-    - snowflake-*-sources.jar
+    - output/obfs4proxy
+    - output/snowflake
     expire_in: 1 week
     when: always
   only:
diff --git a/build-binary.py b/build-binary.py
index 38db13eab3728af61f5e86cc2ae438ea8dfa34c6..affc707e690b0f366e9949ad63f9d7418e4780a6 100755
--- a/build-binary.py
+++ b/build-binary.py
@@ -1,11 +1,11 @@
 #!/usr/bin/env python3
-import os
+import os, shutil
 from glob import glob
 from subprocess import check_call
 
 from utils import get_build_versions, ex, get_sha256, zip_files, get_final_file_name, \
     get_sources_file_name, get_pom_file_name, reset_time, get_version_number, check_go_version, \
-    get_version_and_tool, GO_PATH, GO_ROOT, NDK_DIR
+    get_version_and_tool, get_output_dir, get_platform_output_dir, GO_PATH, GO_ROOT, NDK_DIR
 
 
 def main():
@@ -25,6 +25,11 @@ def main():
     # Checkout source at specific version
     checkout_source_repo(tool, versions)
 
+    # Create the output directory, deleting it first if it already exists
+    output_dir = get_output_dir(tool)
+    if os.path.exists(output_dir): shutil.rmtree(output_dir)
+    os.mkdir(output_dir)
+
     # Build and package for various platforms and architectures
     build_android(tool, versions)
     package_android(tool, versions)
@@ -76,25 +81,27 @@ def checkout_source_repo(tool, versions):
 
 
 def build_android(tool, versions):
+    os.mkdir(get_platform_output_dir(tool, 'android'))
+
     env = os.environ.copy()
     env['GOARCH'] = "arm"
     env['GOARM'] = "7"
-    build_android_arch(tool, versions, env, "arm-linux-androideabi", "arm")
+    build_android_arch(tool, versions, env, "arm-linux-androideabi", "arm", "armeabi-v7a")
 
     env = os.environ.copy()
     env['GOARCH'] = "arm64"
-    build_android_arch(tool, versions, env, "aarch64-linux-android", "arm64")
+    build_android_arch(tool, versions, env, "aarch64-linux-android", "arm64", "arm64-v8a")
 
     env = os.environ.copy()
     env['GOARCH'] = "386"
-    build_android_arch(tool, versions, env, "i686-linux-android", "x86")
+    build_android_arch(tool, versions, env, "i686-linux-android", "x86", "x86")
 
     env = os.environ.copy()
     env['GOARCH'] = "amd64"
-    build_android_arch(tool, versions, env, "x86_64-linux-android", "x86_64")
+    build_android_arch(tool, versions, env, "x86_64-linux-android", "x86_64", "x86_64")
 
 
-def build_android_arch(tool, versions, env, clang_arch, ndk_arch):
+def build_android_arch(tool, versions, env, clang_arch, ndk_arch, abi):
     toolchain = os.path.join("toolchain", ndk_arch)
     if not os.path.isdir(toolchain):
         toolchain_maker = os.path.join(NDK_DIR, "build", "tools", "make-standalone-toolchain.sh")
@@ -108,67 +115,78 @@ def build_android_arch(tool, versions, env, clang_arch, ndk_arch):
     build_mode = "pie"
     extldflags = " -extldflags=-pie"
 
-    filename = "%s_%s_pie.zip" % (tool, ndk_arch)
-    print("Building %s" % filename)
+    print("Building %s for Android %s" % (tool, abi))
+    output_dir = get_platform_output_dir(tool, 'android')
+    arch_dir = os.path.join(output_dir, abi)
+    os.mkdir(arch_dir)
+    tool_path = os.path.join(arch_dir, "lib%s.so" % tool)
 
     output_file = os.path.abspath(os.path.join(os.path.curdir, tool))
     go_flags = ['-asmflags', '-trimpath', '-o', output_file]
     repo_dir = get_repo_dir(versions)
     ex(['go', 'build', '-buildmode=%s' % build_mode, '-ldflags', '-w -s' + extldflags] + go_flags +
        [os.path.join('.', versions['build_path'])], env=env, cwd=repo_dir)
-
-    zip_files([tool], filename, versions)
-    os.remove(tool)
+    shutil.copy(output_file, tool_path)
 
 
 def build_linux(tool, versions):
+    os.mkdir(get_platform_output_dir(tool, 'linux'))
     build_desktop_arch(tool, versions, 'linux', 'armhf', 'arm', '7')
     build_desktop_arch(tool, versions, 'linux', 'aarch64', 'arm64')
     build_desktop_arch(tool, versions, 'linux', 'x86_64', 'amd64')
 
 
 def build_windows(tool, versions):
+    os.mkdir(get_platform_output_dir(tool, 'windows'))
     build_desktop_arch(tool, versions, 'windows', 'x86_64', 'amd64')
 
 
-def build_desktop_arch(tool, versions, goos, arch, goarch, goarm=None):
+def build_desktop_arch(tool, versions, platform, arch, goarch, goarm=None):
     env = os.environ.copy()
     env['CGO_ENABLED'] = "0"
-    env['GOOS'] = goos
+    env['GOOS'] = platform
     env['GOARCH'] = goarch
     if goarm: env['GOARM'] = goarm
     build_path = os.path.join('.', versions['build_path'])
-    filename = "%s_%s-%s.zip" % (tool, goos, arch)
-    print("Building %s" % filename)
+
+    print("Building %s for %s %s" % (tool, platform, arch))
+    output_dir = get_platform_output_dir(tool, platform)
+    arch_dir = os.path.join(output_dir, arch)
+    os.mkdir(arch_dir)
+    extension = '.exe' if platform == 'windows' else ''
+    tool_path = os.path.join(arch_dir, "%s%s" % (tool, extension))
+
     output_file = os.path.abspath(os.path.join(os.path.curdir, tool))
     go_flags = ['-asmflags', '-trimpath', '-o', output_file]
     repo_dir = get_repo_dir(versions)
     ex(['go', 'build', '-ldflags', '-w -s'] + go_flags + [build_path], env=env, cwd=repo_dir)
-    zip_files([tool], filename, versions)
-    os.remove(tool)
+    shutil.copy(output_file, tool_path)
 
 
 def package_android(tool, versions):
+    output_dir = get_platform_output_dir(tool, 'android')
     file_list = [
-        '%s_arm_pie.zip' % tool,
-        '%s_arm64_pie.zip' % tool,
-        '%s_x86_pie.zip' % tool,
-        '%s_x86_64_pie.zip' % tool
+        os.path.join(output_dir, 'armeabi-v7a', 'lib%s.so' % tool),
+        os.path.join(output_dir, 'arm64-v8a', 'lib%s.so' % tool),
+        os.path.join(output_dir, 'x86', 'lib%s.so' % tool),
+        os.path.join(output_dir, 'x86_64', 'lib%s.so' % tool),
     ]
     package(tool, versions, file_list, 'android')
 
 
 def package_linux(tool, versions):
+    output_dir = get_platform_output_dir(tool, 'linux')
     file_list = [
-        '%s_linux-armhf.zip' % tool,
-        '%s_linux-aarch64.zip' % tool,
-        '%s_linux-x86_64.zip' % tool
+        os.path.join(output_dir, 'armhf', tool),
+        os.path.join(output_dir, 'aarch64', tool),
+        os.path.join(output_dir, 'x86_64', tool),
     ]
     package(tool, versions, file_list, 'linux')
 
 
 def package_windows(tool, versions):
-    file_list = ['%s_windows-x86_64.zip' % tool]
+    output_dir = get_platform_output_dir(tool, 'windows')
+    file_list = [os.path.join(output_dir, 'x86_64', '%s.exe' % tool)]
     package(tool, versions, file_list, 'windows')
 
 
diff --git a/output/.gitkeep b/output/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/utils.py b/utils.py
index d096cb6d2ebdfc6221192f9f7f06da5021018c73..9d9860e9fb0a5f2c3b7b5acd02714217b4e6d7c1 100644
--- a/utils.py
+++ b/utils.py
@@ -76,18 +76,28 @@ def get_version_number(versions):
     return versions['tag']
 
 
-def get_file_suffix(versions, platform):
+def get_output_file(tool, versions, platform, suffix):
+    output_dir = get_platform_output_dir(tool, platform)
     version = get_version_number(versions)
-    return "%s-%s" % (platform, version)
+    filename = '%s-%s-%s%s' % (tool, platform, version, suffix)
+    return os.path.join(output_dir, filename)
 
 
 def get_final_file_name(tool, versions, platform):
-    return '%s-%s.jar' % (tool, get_file_suffix(versions, platform))
+    return get_output_file(tool, versions, platform, '.jar')
 
 
 def get_sources_file_name(tool, versions, platform):
-    return '%s-%s-sources.jar' % (tool, get_file_suffix(versions, platform))
+    return get_output_file(tool, versions, platform, '-sources.jar')
 
 
 def get_pom_file_name(tool, versions, platform):
-    return '%s-%s.pom' % (tool, get_file_suffix(versions, platform))
+    return get_output_file(tool, versions, platform, '.pom')
+
+
+def get_output_dir(tool):
+    return os.path.abspath(os.path.join('output', tool))
+
+
+def get_platform_output_dir(tool, platform):
+    return os.path.abspath(os.path.join('output', tool, platform))