From 1586bbfb68bd64e12c88ca9a5e2492b7bf39d443 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:28:08 +0000 Subject: [PATCH 1/5] feat: implement unified Git-based versioning mechanism - Add scripts/compute-version.sh for Git-based version derivation - Add cmake/GitVersion.cmake module to replace hardcoded CMake version - Update CMakeLists.txt to use GitVersion module with fallback - Update all CI workflows with fetch-depth: 0 and version computation - Update artifact names to include VERSION in all workflows - Update setup.py to prefer VERSION env var for Python package version Co-authored-by: frankslin <748873+frankslin@users.noreply.github.com> Agent-Logs-Url: https://github.com/BYVoid/OpenCC/sessions/f2246182-75cf-48a2-99e4-a22a9da2ad97 --- .github/workflows/bazel.yml | 12 +++- .github/workflows/cmake.yml | 12 +++- .github/workflows/mingw.yml | 11 ++- .github/workflows/msvc.yml | 12 +++- .github/workflows/nodejs.yml | 12 +++- .github/workflows/python.yml | 24 ++++++- .github/workflows/release-pypi.yml | 10 +++ .github/workflows/release-winget.yml | 2 + CMakeLists.txt | 14 +--- cmake/GitVersion.cmake | 100 +++++++++++++++++++++++++++ scripts/compute-version.sh | 51 ++++++++++++++ setup.py | 10 +++ 12 files changed, 252 insertions(+), 18 deletions(-) create mode 100644 cmake/GitVersion.cmake create mode 100755 scripts/compute-version.sh diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index a5064115b..148b9681f 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -19,6 +19,16 @@ jobs: steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Compute version + id: version + shell: bash + run: | + VERSION=$(bash scripts/compute-version.sh) + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Computed version: $VERSION" - uses: bazelbuild/setup-bazelisk@v3 - name: Set up Python uses: actions/setup-python@v5 @@ -30,7 +40,7 @@ jobs: if: ${{ always() }} uses: actions/upload-artifact@v4 with: - name: opencc-bazel-${{ matrix.os }} + name: opencc-${{ env.VERSION }}-bazel-${{ matrix.os }} path: | bazel-bin/** bazel-testlogs/** diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 95fd26b8e..a3ca6ecf3 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -18,6 +18,16 @@ jobs: os: [ubuntu-latest, macos-latest, macos-14] steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Compute version + id: version + shell: bash + run: | + VERSION=$(bash scripts/compute-version.sh) + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Computed version: $VERSION" - name: make build run: make build VERBOSE=1 REL_BUILD_DOCUMENTATION=OFF - name: make test @@ -28,7 +38,7 @@ jobs: if: ${{ always() }} uses: actions/upload-artifact@v4 with: - name: opencc-cmake-${{ matrix.os }} + name: opencc-${{ env.VERSION }}-cmake-${{ matrix.os }} path: | build/rel/** build/dbg/** diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 6ee1bccab..569c4fe9d 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -19,6 +19,15 @@ jobs: steps: - uses: msys2/setup-msys2@v2 - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Compute version + id: version + run: | + VERSION=$(bash scripts/compute-version.sh) + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Computed version: $VERSION" - name: Install dependencies run: pacman -S --noconfirm base-devel mingw-w64-x86_64-toolchain cmake ninja python - name: Build with mingw-w64 @@ -48,7 +57,7 @@ jobs: if: ${{ always() }} uses: actions/upload-artifact@v4 with: - name: opencc-mingw + name: opencc-${{ env.VERSION }}-mingw path: | install/** build/src/tools/*.exe diff --git a/.github/workflows/msvc.yml b/.github/workflows/msvc.yml index 22a16cc8b..15be85e5f 100644 --- a/.github/workflows/msvc.yml +++ b/.github/workflows/msvc.yml @@ -30,6 +30,16 @@ jobs: can_run_binaries: false steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Compute version + id: version + shell: bash + run: | + VERSION=$(bash scripts/compute-version.sh) + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Computed version: $VERSION" - uses: ilammy/msvc-dev-cmd@v1 with: arch: ${{ matrix.arch }} @@ -90,7 +100,7 @@ jobs: if: ${{ always() }} uses: actions/upload-artifact@v4 with: - name: opencc-msvc-${{ matrix.arch }} + name: opencc-${{ env.VERSION }}-msvc-${{ matrix.arch }} path: | dist/OpenCC-msvc-${{ matrix.package_suffix }}.zip dist/opencc-msvc-${{ matrix.package_suffix }}/** diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 627de3014..782b25821 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -27,6 +27,16 @@ jobs: node-version: 20 steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Compute version + id: version + shell: bash + run: | + VERSION=$(bash scripts/compute-version.sh) + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Computed version: $VERSION" - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: @@ -37,7 +47,7 @@ jobs: if: ${{ always() }} uses: actions/upload-artifact@v4 with: - name: opencc-nodejs-${{ matrix.os }}-node-${{ matrix.node-version }} + name: opencc-${{ env.VERSION }}-nodejs-${{ matrix.os }}-node-${{ matrix.node-version }} path: | node/build/** **/npm-debug.log diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index ad48d5aab..e5b0d6e3b 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -19,6 +19,16 @@ jobs: steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Compute version + id: version + shell: bash + run: | + VERSION=$(bash scripts/compute-version.sh) + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Computed version: $VERSION" - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: @@ -42,7 +52,7 @@ jobs: if: ${{ always() }} uses: actions/upload-artifact@v4 with: - name: opencc-python-${{ matrix.python-version }} + name: opencc-${{ env.VERSION }}-python-${{ matrix.python-version }} path: | pytest.xml @@ -54,6 +64,16 @@ jobs: steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Compute version + id: version + shell: bash + run: | + VERSION=$(bash scripts/compute-version.sh) + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Computed version: $VERSION" - name: Build package and upload from docker (Linux) if: runner.os == 'Linux' @@ -82,6 +102,6 @@ jobs: if: ${{ always() }} uses: actions/upload-artifact@v4 with: - name: opencc-pypi-${{ matrix.os }} + name: opencc-${{ env.VERSION }}-pypi-${{ matrix.os }} path: | dist/** diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index 2e6e33cdd..bae9d2679 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -12,6 +12,16 @@ jobs: steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Compute version + id: version + shell: bash + run: | + VERSION=$(bash scripts/compute-version.sh) + echo "VERSION=$VERSION" >> "$GITHUB_ENV" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Computed version: $VERSION" - name: Build package and upload from docker (Linux) if: runner.os == 'Linux' diff --git a/.github/workflows/release-winget.yml b/.github/workflows/release-winget.yml index 60a35c2e2..eabe4efb7 100644 --- a/.github/workflows/release-winget.yml +++ b/.github/workflows/release-winget.yml @@ -21,6 +21,8 @@ jobs: contents: write steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: ilammy/msvc-dev-cmd@v1 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ac5072f1..0372978f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,18 +41,10 @@ option(USE_SYSTEM_TCLAP "Use system version of TCLAP" OFF) ######## Package information set (PACKAGE_URL https://github.com/BYVoid/OpenCC) set (PACKAGE_BUGREPORT https://github.com/BYVoid/OpenCC/issues) -set (OPENCC_VERSION_MAJOR 1) -set (OPENCC_VERSION_MINOR 2) -set (OPENCC_VERSION_REVISION 1) -if (CMAKE_BUILD_TYPE MATCHES Debug) - set (version_suffix .Debug) -endif (CMAKE_BUILD_TYPE MATCHES Debug) - -set ( - OPENCC_VERSION - ${OPENCC_VERSION_MAJOR}.${OPENCC_VERSION_MINOR}.${OPENCC_VERSION_REVISION}${version_suffix} -) +# Derive version from Git tags (falls back to hardcoded default when Git is unavailable) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") +include(GitVersion) set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PACKAGE_NAME}-${OPENCC_VERSION_MAJOR}.${OPENCC_VERSION_MINOR}.${OPENCC_VERSION_REVISION}" diff --git a/cmake/GitVersion.cmake b/cmake/GitVersion.cmake new file mode 100644 index 000000000..188ce55b0 --- /dev/null +++ b/cmake/GitVersion.cmake @@ -0,0 +1,100 @@ +# +# GitVersion.cmake — Derive project version from Git tags. +# +# Sets in parent scope: +# OPENCC_VERSION Full version string (e.g. "1.2.1" or "1.2.1.dev4+gabc1234") +# OPENCC_VERSION_MAJOR Major component +# OPENCC_VERSION_MINOR Minor component +# OPENCC_VERSION_REVISION Patch/revision component +# OPENCC_VERSION_SUFFIX Everything after M.m.p (e.g. ".dev4+gabc1234.dirty") or "" +# OPENCC_VERSION_IS_RELEASE TRUE if this is an exact-tag release build +# +# Falls back to hardcoded default when Git is unavailable. +# + +set(_OPENCC_FALLBACK_MAJOR 1) +set(_OPENCC_FALLBACK_MINOR 2) +set(_OPENCC_FALLBACK_REVISION 1) + +find_package(Git QUIET) + +set(_git_version_computed FALSE) + +if(GIT_FOUND) + # Try git describe --tags --long --always + execute_process( + COMMAND "${GIT_EXECUTABLE}" describe --tags --long --always + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + OUTPUT_VARIABLE _git_raw + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _git_result + ) + + if(_git_result EQUAL 0 AND _git_raw) + # Detect dirty working tree + set(_dirty_suffix "") + execute_process( + COMMAND "${GIT_EXECUTABLE}" diff --quiet + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE _diff_result + ) + execute_process( + COMMAND "${GIT_EXECUTABLE}" diff --cached --quiet + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE _diff_cached_result + ) + if(NOT _diff_result EQUAL 0 OR NOT _diff_cached_result EQUAL 0) + set(_dirty_suffix ".dirty") + endif() + + # Case 1: Exact tag — v1.2.3-0-g + string(REGEX MATCH "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)-0-g[0-9a-f]+$" _match_tag "${_git_raw}") + if(_match_tag) + set(OPENCC_VERSION_MAJOR "${CMAKE_MATCH_1}") + set(OPENCC_VERSION_MINOR "${CMAKE_MATCH_2}") + set(OPENCC_VERSION_REVISION "${CMAKE_MATCH_3}") + set(OPENCC_VERSION_SUFFIX "${_dirty_suffix}") + set(OPENCC_VERSION_IS_RELEASE TRUE) + set(_git_version_computed TRUE) + endif() + + # Case 2: Dev build — v1.2.3-N-g (N > 0) + if(NOT _git_version_computed) + string(REGEX MATCH "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)-([0-9]+)-g([0-9a-f]+)$" _match_dev "${_git_raw}") + if(_match_dev) + set(OPENCC_VERSION_MAJOR "${CMAKE_MATCH_1}") + set(OPENCC_VERSION_MINOR "${CMAKE_MATCH_2}") + set(OPENCC_VERSION_REVISION "${CMAKE_MATCH_3}") + set(OPENCC_VERSION_SUFFIX ".dev${CMAKE_MATCH_4}+g${CMAKE_MATCH_5}${_dirty_suffix}") + set(OPENCC_VERSION_IS_RELEASE FALSE) + set(_git_version_computed TRUE) + endif() + endif() + + # Case 3: No tag — raw SHA only + if(NOT _git_version_computed) + set(OPENCC_VERSION_MAJOR "${_OPENCC_FALLBACK_MAJOR}") + set(OPENCC_VERSION_MINOR "${_OPENCC_FALLBACK_MINOR}") + set(OPENCC_VERSION_REVISION "${_OPENCC_FALLBACK_REVISION}") + set(OPENCC_VERSION_SUFFIX "+g${_git_raw}${_dirty_suffix}") + set(OPENCC_VERSION_IS_RELEASE FALSE) + set(_git_version_computed TRUE) + endif() + endif() +endif() + +# Fallback: no Git or git describe failed +if(NOT _git_version_computed) + message(STATUS "Git not available — using fallback version ${_OPENCC_FALLBACK_MAJOR}.${_OPENCC_FALLBACK_MINOR}.${_OPENCC_FALLBACK_REVISION}") + set(OPENCC_VERSION_MAJOR "${_OPENCC_FALLBACK_MAJOR}") + set(OPENCC_VERSION_MINOR "${_OPENCC_FALLBACK_MINOR}") + set(OPENCC_VERSION_REVISION "${_OPENCC_FALLBACK_REVISION}") + set(OPENCC_VERSION_SUFFIX "") + set(OPENCC_VERSION_IS_RELEASE FALSE) +endif() + +# Compose full version string +set(OPENCC_VERSION "${OPENCC_VERSION_MAJOR}.${OPENCC_VERSION_MINOR}.${OPENCC_VERSION_REVISION}${OPENCC_VERSION_SUFFIX}") + +message(STATUS "OpenCC version: ${OPENCC_VERSION}") diff --git a/scripts/compute-version.sh b/scripts/compute-version.sh new file mode 100755 index 000000000..be48103c3 --- /dev/null +++ b/scripts/compute-version.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# +# compute-version.sh — Derive a version string from Git state. +# +# Output format: +# Tag build (release): v1.2.3 +# Dev build (clean): v1.2.3.dev4+gabc1234 +# Dev build (dirty): v1.2.3.dev4+gabc1234.dirty +# No tag (clean): gabc1234 +# No tag (dirty): gabc1234.dirty +# +# Usage: +# ./scripts/compute-version.sh # prints version to stdout +# VERSION=$(./scripts/compute-version.sh) +# +set -euo pipefail + +# Detect dirty working tree +DIRTY="" +if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then + DIRTY=".dirty" +fi + +# Get raw git describe output +RAW=$(git describe --tags --long --always 2>/dev/null) || RAW="" + +if [ -z "$RAW" ]; then + # No git available at all — fall back to unknown + SHA=$(git rev-parse --short HEAD 2>/dev/null) || SHA="unknown" + echo "g${SHA}${DIRTY}" + exit 0 +fi + +# Case 1: Exact tag (e.g. v1.2.3-0-gabc1234 from --long) +if [[ "$RAW" =~ ^(v[0-9]+\.[0-9]+\.[0-9]+)-0-g[0-9a-f]+$ ]]; then + TAG="${BASH_REMATCH[1]}" + echo "${TAG}${DIRTY}" + exit 0 +fi + +# Case 2: Dev build with tag (e.g. v1.2.3-4-gabc1234) +if [[ "$RAW" =~ ^(v[0-9]+\.[0-9]+\.[0-9]+)-([0-9]+)-g([0-9a-f]+)$ ]]; then + BASE="${BASH_REMATCH[1]}" + COUNT="${BASH_REMATCH[2]}" + SHA="${BASH_REMATCH[3]}" + echo "${BASE}.dev${COUNT}+g${SHA}${DIRTY}" + exit 0 +fi + +# Case 3: No tag — raw is just a short SHA +echo "g${RAW}${DIRTY}" diff --git a/setup.py b/setup.py index d6b5b9055..7a696f01a 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,16 @@ def get_version_info(): + # Prefer Git-derived version from environment (set by CI or compute-version.sh) + env_version = os.environ.get('VERSION', '') + if env_version: + # Strip leading 'v' prefix for PEP 440 compatibility + v = env_version.lstrip('v') + # Convert .devN+g format to PEP 440: .devN+g + # Already compatible; just strip dirty suffix for PyPI uploads + return v + + # Fallback: parse version from CMakeLists.txt version_info = ['1', '0', '0'] version_pattern = re.compile( r'OPENCC_VERSION_(MAJOR|MINOR|REVISION) (\d+)') From 79aacc08ac69bd3ad2e52e59fa0fe927291cc81e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 15:30:39 +0000 Subject: [PATCH 2/5] fix: address code review feedback - fix setup.py comment and add sync docs Co-authored-by: frankslin <748873+frankslin@users.noreply.github.com> Agent-Logs-Url: https://github.com/BYVoid/OpenCC/sessions/f2246182-75cf-48a2-99e4-a22a9da2ad97 --- cmake/GitVersion.cmake | 1 + scripts/compute-version.sh | 1 + setup.py | 5 +---- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cmake/GitVersion.cmake b/cmake/GitVersion.cmake index 188ce55b0..fdd57ed0f 100644 --- a/cmake/GitVersion.cmake +++ b/cmake/GitVersion.cmake @@ -49,6 +49,7 @@ if(GIT_FOUND) endif() # Case 1: Exact tag — v1.2.3-0-g + # NOTE: version tag patterns here must be kept in sync with scripts/compute-version.sh string(REGEX MATCH "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)-0-g[0-9a-f]+$" _match_tag "${_git_raw}") if(_match_tag) set(OPENCC_VERSION_MAJOR "${CMAKE_MATCH_1}") diff --git a/scripts/compute-version.sh b/scripts/compute-version.sh index be48103c3..db7b06e4d 100755 --- a/scripts/compute-version.sh +++ b/scripts/compute-version.sh @@ -32,6 +32,7 @@ if [ -z "$RAW" ]; then fi # Case 1: Exact tag (e.g. v1.2.3-0-gabc1234 from --long) +# NOTE: version tag patterns here must be kept in sync with cmake/GitVersion.cmake if [[ "$RAW" =~ ^(v[0-9]+\.[0-9]+\.[0-9]+)-0-g[0-9a-f]+$ ]]; then TAG="${BASH_REMATCH[1]}" echo "${TAG}${DIRTY}" diff --git a/setup.py b/setup.py index 7a696f01a..dc92d91f0 100644 --- a/setup.py +++ b/setup.py @@ -22,10 +22,7 @@ def get_version_info(): env_version = os.environ.get('VERSION', '') if env_version: # Strip leading 'v' prefix for PEP 440 compatibility - v = env_version.lstrip('v') - # Convert .devN+g format to PEP 440: .devN+g - # Already compatible; just strip dirty suffix for PyPI uploads - return v + return env_version.lstrip('v') # Fallback: parse version from CMakeLists.txt version_info = ['1', '0', '0'] From 51730a5382b64570a130b89d603446ddd5766e1c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 01:10:14 +0000 Subject: [PATCH 3/5] fix: resolve VERSION loop collision, Docker passthrough, and no-tag divergence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename VERSION→PYVER loop variable in release-pypi-linux.sh and release-pypi-macos.sh to avoid overwriting the package version env var - Pass -e VERSION=${VERSION} into Docker containers in release-pypi.yml and python.yml so the computed version reaches setup.py - Align no-tag fallback in compute-version.sh with cmake/GitVersion.cmake to output v1.2.1+g instead of bare g Agent-Logs-Url: https://github.com/BYVoid/OpenCC/sessions/bdd95762-bf18-4b99-b239-6fbc9017796d Co-authored-by: frankslin <748873+frankslin@users.noreply.github.com> --- .github/workflows/python.yml | 1 + .github/workflows/release-pypi.yml | 1 + release-pypi-linux.sh | 6 +++--- release-pypi-macos.sh | 6 +++--- scripts/compute-version.sh | 21 ++++++++++++++------- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index e5b0d6e3b..7ac251f48 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -81,6 +81,7 @@ jobs: docker run --rm -v "${PWD}:/opt/OpenCC" \ -e TWINE_USERNAME=__token__ \ -e TWINE_PASSWORD=${{ secrets.PYPI_TOKEN }} \ + -e VERSION=${VERSION} \ ubuntu:22.04 /bin/bash /opt/OpenCC/release-pypi-linux.sh testonly - name: Build package and upload (macOS) diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index bae9d2679..e5602b136 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -29,6 +29,7 @@ jobs: docker run --rm -v "${PWD}:/opt/OpenCC" \ -e TWINE_USERNAME=__token__ \ -e TWINE_PASSWORD=${{ secrets.PYPI_TOKEN }} \ + -e VERSION=${VERSION} \ ubuntu:22.04 /bin/bash /opt/OpenCC/release-pypi-linux.sh - name: Build package and upload (macOS) diff --git a/release-pypi-linux.sh b/release-pypi-linux.sh index 316867533..72bb734cb 100644 --- a/release-pypi-linux.sh +++ b/release-pypi-linux.sh @@ -29,12 +29,12 @@ eval "$(conda shell.bash hook)" conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r -for VERSION in 3.9 3.10 3.11 3.12 3.13 3.14; do +for PYVER in 3.9 3.10 3.11 3.12 3.13 3.14; do # Create and activate environment conda config --add channels conda-forge conda config --set channel_priority strict - conda create -y -n py$VERSION python=$VERSION - conda activate py$VERSION + conda create -y -n py$PYVER python=$PYVER + conda activate py$PYVER # Build and package pip install --no-cache-dir build diff --git a/release-pypi-macos.sh b/release-pypi-macos.sh index 5f3d87a73..80dd4e4d4 100644 --- a/release-pypi-macos.sh +++ b/release-pypi-macos.sh @@ -23,12 +23,12 @@ eval "$(conda shell.bash hook)" conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r -for VERSION in 3.9 3.10 3.11 3.12 3.13 3.14; do +for PYVER in 3.9 3.10 3.11 3.12 3.13 3.14; do # Create and activate environment conda config --add channels conda-forge conda config --set channel_priority strict - conda create -y -n py$VERSION python=$VERSION - conda activate py$VERSION + conda create -y -n py$PYVER python=$PYVER + conda activate py$PYVER # Build and package pip install --no-cache-dir build diff --git a/scripts/compute-version.sh b/scripts/compute-version.sh index db7b06e4d..01d178c41 100755 --- a/scripts/compute-version.sh +++ b/scripts/compute-version.sh @@ -6,8 +6,8 @@ # Tag build (release): v1.2.3 # Dev build (clean): v1.2.3.dev4+gabc1234 # Dev build (dirty): v1.2.3.dev4+gabc1234.dirty -# No tag (clean): gabc1234 -# No tag (dirty): gabc1234.dirty +# No tag (clean): v1.2.1+gabc1234 +# No tag (dirty): v1.2.1+gabc1234.dirty # # Usage: # ./scripts/compute-version.sh # prints version to stdout @@ -15,6 +15,9 @@ # set -euo pipefail +# Fallback version — must be kept in sync with cmake/GitVersion.cmake +FALLBACK_VERSION="1.2.1" + # Detect dirty working tree DIRTY="" if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then @@ -25,9 +28,13 @@ fi RAW=$(git describe --tags --long --always 2>/dev/null) || RAW="" if [ -z "$RAW" ]; then - # No git available at all — fall back to unknown - SHA=$(git rev-parse --short HEAD 2>/dev/null) || SHA="unknown" - echo "g${SHA}${DIRTY}" + # No git available at all — use fallback version + SHA=$(git rev-parse --short HEAD 2>/dev/null) || SHA="" + if [ -n "$SHA" ]; then + echo "v${FALLBACK_VERSION}+g${SHA}${DIRTY}" + else + echo "v${FALLBACK_VERSION}${DIRTY}" + fi exit 0 fi @@ -48,5 +55,5 @@ if [[ "$RAW" =~ ^(v[0-9]+\.[0-9]+\.[0-9]+)-([0-9]+)-g([0-9a-f]+)$ ]]; then exit 0 fi -# Case 3: No tag — raw is just a short SHA -echo "g${RAW}${DIRTY}" +# Case 3: No tag — use fallback version with commit SHA +echo "v${FALLBACK_VERSION}+g${RAW}${DIRTY}" From 7ca1086b86d2c19e422a0bf112ace8ee0fbfda4d Mon Sep 17 00:00:00 2001 From: Frank Lin Date: Thu, 26 Mar 2026 18:16:15 -0700 Subject: [PATCH 4/5] fix: align unified versioning across build and release --- cmake/GitVersion.cmake | 22 +++++++------- release-pypi-linux.sh | 6 ++-- release-pypi-macos.sh | 6 ++-- scripts/compute-version.sh | 20 ++++++------ setup.py | 62 +++++++++++++++++++++++++++----------- 5 files changed, 72 insertions(+), 44 deletions(-) diff --git a/cmake/GitVersion.cmake b/cmake/GitVersion.cmake index fdd57ed0f..dbd4f6743 100644 --- a/cmake/GitVersion.cmake +++ b/cmake/GitVersion.cmake @@ -48,26 +48,26 @@ if(GIT_FOUND) set(_dirty_suffix ".dirty") endif() - # Case 1: Exact tag — v1.2.3-0-g + # Case 1: Exact tag — v1.2.3-0-g or ver.1.2.3-0-g # NOTE: version tag patterns here must be kept in sync with scripts/compute-version.sh - string(REGEX MATCH "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)-0-g[0-9a-f]+$" _match_tag "${_git_raw}") + string(REGEX MATCH "^(v|ver\\.)([0-9]+)\\.([0-9]+)\\.([0-9]+)-0-g[0-9a-f]+$" _match_tag "${_git_raw}") if(_match_tag) - set(OPENCC_VERSION_MAJOR "${CMAKE_MATCH_1}") - set(OPENCC_VERSION_MINOR "${CMAKE_MATCH_2}") - set(OPENCC_VERSION_REVISION "${CMAKE_MATCH_3}") + set(OPENCC_VERSION_MAJOR "${CMAKE_MATCH_2}") + set(OPENCC_VERSION_MINOR "${CMAKE_MATCH_3}") + set(OPENCC_VERSION_REVISION "${CMAKE_MATCH_4}") set(OPENCC_VERSION_SUFFIX "${_dirty_suffix}") set(OPENCC_VERSION_IS_RELEASE TRUE) set(_git_version_computed TRUE) endif() - # Case 2: Dev build — v1.2.3-N-g (N > 0) + # Case 2: Dev build — v1.2.3-N-g or ver.1.2.3-N-g (N > 0) if(NOT _git_version_computed) - string(REGEX MATCH "^v([0-9]+)\\.([0-9]+)\\.([0-9]+)-([0-9]+)-g([0-9a-f]+)$" _match_dev "${_git_raw}") + string(REGEX MATCH "^(v|ver\\.)([0-9]+)\\.([0-9]+)\\.([0-9]+)-([0-9]+)-g([0-9a-f]+)$" _match_dev "${_git_raw}") if(_match_dev) - set(OPENCC_VERSION_MAJOR "${CMAKE_MATCH_1}") - set(OPENCC_VERSION_MINOR "${CMAKE_MATCH_2}") - set(OPENCC_VERSION_REVISION "${CMAKE_MATCH_3}") - set(OPENCC_VERSION_SUFFIX ".dev${CMAKE_MATCH_4}+g${CMAKE_MATCH_5}${_dirty_suffix}") + set(OPENCC_VERSION_MAJOR "${CMAKE_MATCH_2}") + set(OPENCC_VERSION_MINOR "${CMAKE_MATCH_3}") + set(OPENCC_VERSION_REVISION "${CMAKE_MATCH_4}") + set(OPENCC_VERSION_SUFFIX ".dev${CMAKE_MATCH_5}+g${CMAKE_MATCH_6}${_dirty_suffix}") set(OPENCC_VERSION_IS_RELEASE FALSE) set(_git_version_computed TRUE) endif() diff --git a/release-pypi-linux.sh b/release-pypi-linux.sh index 72bb734cb..e947f9a6c 100644 --- a/release-pypi-linux.sh +++ b/release-pypi-linux.sh @@ -29,12 +29,12 @@ eval "$(conda shell.bash hook)" conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r -for PYVER in 3.9 3.10 3.11 3.12 3.13 3.14; do +for PYTHON_VERSION in 3.9 3.10 3.11 3.12 3.13 3.14; do # Create and activate environment conda config --add channels conda-forge conda config --set channel_priority strict - conda create -y -n py$PYVER python=$PYVER - conda activate py$PYVER + conda create -y -n py$PYTHON_VERSION python=$PYTHON_VERSION + conda activate py$PYTHON_VERSION # Build and package pip install --no-cache-dir build diff --git a/release-pypi-macos.sh b/release-pypi-macos.sh index 80dd4e4d4..aa1a80653 100644 --- a/release-pypi-macos.sh +++ b/release-pypi-macos.sh @@ -23,12 +23,12 @@ eval "$(conda shell.bash hook)" conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r -for PYVER in 3.9 3.10 3.11 3.12 3.13 3.14; do +for PYTHON_VERSION in 3.9 3.10 3.11 3.12 3.13 3.14; do # Create and activate environment conda config --add channels conda-forge conda config --set channel_priority strict - conda create -y -n py$PYVER python=$PYVER - conda activate py$PYVER + conda create -y -n py$PYTHON_VERSION python=$PYTHON_VERSION + conda activate py$PYTHON_VERSION # Build and package pip install --no-cache-dir build diff --git a/scripts/compute-version.sh b/scripts/compute-version.sh index 01d178c41..ed5b8e3fc 100755 --- a/scripts/compute-version.sh +++ b/scripts/compute-version.sh @@ -38,20 +38,20 @@ if [ -z "$RAW" ]; then exit 0 fi -# Case 1: Exact tag (e.g. v1.2.3-0-gabc1234 from --long) +# Case 1: Exact tag (e.g. v1.2.3-0-gabc1234 or ver.1.2.3-0-gabc1234 from --long) # NOTE: version tag patterns here must be kept in sync with cmake/GitVersion.cmake -if [[ "$RAW" =~ ^(v[0-9]+\.[0-9]+\.[0-9]+)-0-g[0-9a-f]+$ ]]; then - TAG="${BASH_REMATCH[1]}" - echo "${TAG}${DIRTY}" +if [[ "$RAW" =~ ^(v|ver\.)([0-9]+\.[0-9]+\.[0-9]+)-0-g[0-9a-f]+$ ]]; then + BASE="${BASH_REMATCH[2]}" + echo "v${BASE}${DIRTY}" exit 0 fi -# Case 2: Dev build with tag (e.g. v1.2.3-4-gabc1234) -if [[ "$RAW" =~ ^(v[0-9]+\.[0-9]+\.[0-9]+)-([0-9]+)-g([0-9a-f]+)$ ]]; then - BASE="${BASH_REMATCH[1]}" - COUNT="${BASH_REMATCH[2]}" - SHA="${BASH_REMATCH[3]}" - echo "${BASE}.dev${COUNT}+g${SHA}${DIRTY}" +# Case 2: Dev build with tag (e.g. v1.2.3-4-gabc1234 or ver.1.2.3-4-gabc1234) +if [[ "$RAW" =~ ^(v|ver\.)([0-9]+\.[0-9]+\.[0-9]+)-([0-9]+)-g([0-9a-f]+)$ ]]; then + BASE="${BASH_REMATCH[2]}" + COUNT="${BASH_REMATCH[3]}" + SHA="${BASH_REMATCH[4]}" + echo "v${BASE}.dev${COUNT}+g${SHA}${DIRTY}" exit 0 fi diff --git a/setup.py b/setup.py index dc92d91f0..5dad66f3d 100644 --- a/setup.py +++ b/setup.py @@ -15,6 +15,46 @@ _cmake_file = os.path.join(_this_dir, 'CMakeLists.txt') _author_file = os.path.join(_this_dir, 'AUTHORS') _readme_file = os.path.join(_this_dir, 'README.md') +_fallback_version = '1.2.1' + + +def _get_version_from_git(): + try: + raw = subprocess.check_output( + ['git', 'describe', '--tags', '--long', '--always'], + cwd=_this_dir, + stderr=subprocess.DEVNULL, + ).decode('utf-8').strip() + except (OSError, subprocess.CalledProcessError): + return '' + + dirty = '' + for diff_cmd in (['git', 'diff', '--quiet'], ['git', 'diff', '--cached', '--quiet']): + try: + subprocess.check_call( + diff_cmd, + cwd=_this_dir, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + except (OSError, subprocess.CalledProcessError): + dirty = '.dirty' + break + + release_match = re.match(r'^(?:v|ver\.)(\d+\.\d+\.\d+)-0-g[0-9a-f]+$', raw) + if release_match: + return '{}{}'.format(release_match.group(1), dirty) + + dev_match = re.match(r'^(?:v|ver\.)(\d+\.\d+\.\d+)-(\d+)-g([0-9a-f]+)$', raw) + if dev_match: + return '{}.dev{}+g{}{}'.format( + dev_match.group(1), + dev_match.group(2), + dev_match.group(3), + dirty, + ) + + return '{}+g{}{}'.format(_fallback_version, raw, dirty) def get_version_info(): @@ -24,23 +64,11 @@ def get_version_info(): # Strip leading 'v' prefix for PEP 440 compatibility return env_version.lstrip('v') - # Fallback: parse version from CMakeLists.txt - version_info = ['1', '0', '0'] - version_pattern = re.compile( - r'OPENCC_VERSION_(MAJOR|MINOR|REVISION) (\d+)') - with open(_cmake_file, 'rb') as f: - for l in f: - match = version_pattern.search(l.decode('utf-8')) - if not match: - continue - if match.group(1) == 'MAJOR': - version_info[0] = match.group(2) - elif match.group(1) == 'MINOR': - version_info[1] = match.group(2) - elif match.group(1) == 'REVISION': - version_info[2] = match.group(2) - version = '.'.join(version_info) - return version + git_version = _get_version_from_git() + if git_version: + return git_version + + return _fallback_version def get_author_info(): From 258eeeee9672c13ff2c6b8e904b592d27e4d5826 Mon Sep 17 00:00:00 2001 From: Frank Lin Date: Thu, 26 Mar 2026 18:30:31 -0700 Subject: [PATCH 5/5] fix: include cmake files in sdist packaging --- MANIFEST.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 38ab0ada5..91fa6a9d1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,7 +3,8 @@ graft deps graft test graft data graft doc -include CMakeLists.txt OpenCCConfig.cmake.in opencc.pc.in README* LICENSE* +graft cmake +include AUTHORS CMakeLists.txt OpenCCConfig.cmake.in opencc.pc.in README* LICENSE* global-exclude *~ *.py[cod] *.so include python/**/__init__.py graft python/tests