diff --git a/.github/workflows/pullRequest.yml b/.github/workflows/pullRequest.yml new file mode 100644 index 0000000..535a519 --- /dev/null +++ b/.github/workflows/pullRequest.yml @@ -0,0 +1,29 @@ +name: check for pull request into master + +on: + pull_request: + branches: + - master + +jobs: + build: + runs-on: windows-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + architecture: x86 + python-version: 3.8 + cache: pip + + - name: Install requirements + run: | + python -m pip install -r requirements.txt + + - name: run scons + run: scons + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..70de669 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,50 @@ +name: Build and release for official + +on: + push: + tags: + - "*.*.*" + +jobs: + build: + uses: ./.github/workflows/testAndBuild.yml + with: + official_release: true + + deploy: + needs: build + runs-on: windows-latest + + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: ${{ github.event.repository.name }} + path: ./ + + - name: Deploy to GitHub + uses: softprops/action-gh-release@v1 + with: + body: ${{ github.event.repository.name }} official release + draft: true + files: | + ./${{ github.event.repository.name }}-*.zip + ./*-*.nvda-addon + ./${{ github.event.repository.name }}-*.json + + error_notify: + runs-on: ubuntu-latest + needs: deploy + if: ${{ failure() }} + steps: + - name: Send GitHub Action trigger data to Slack workflow + uses: slackapi/slack-github-action@v1 + with: + payload: | + { + "text": "Github actions build failed! <${{ github.server_url }}/${{ github.repository }}|${{ github.event.repository.name }}>のofficial releaseビルドが失敗しました。\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|対象のrun>お確認し、対応着手時・完了後は、本チャンネルにて経緯を報告ください。" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_ALERT_WEBHOOK_URL }} + SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK + diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml new file mode 100644 index 0000000..214588a --- /dev/null +++ b/.github/workflows/snapshot.yml @@ -0,0 +1,84 @@ +name: Build and release for snapshot + +on: + push: + branches: + - master + +jobs: + build: + uses: ./.github/workflows/testAndBuild.yml + + deploy: + needs: build + runs-on: windows-latest + + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: ${{ github.event.repository.name }} + path: ./ + + - name: Re-create the tag + uses: actions/github-script@v7 + with: + script: | + const { owner, repo } = context.repo + const tagName = repo + "-latestcommit" + try { + // Fetch the release by its tag + const { data: release } = await github.rest.repos.getReleaseByTag({ owner, repo, tag: tagName }) + // Delete the release if exists + await github.rest.repos.deleteRelease({ owner, repo, release_id: release.id }) + console.log("deleted release"); + } catch(err) { + if(err.status !== 404){ + throw err; + } + console.log('No release found for deletion'); + } + try { + await github.rest.git.deleteRef({owner, repo, ref: "tags/" + tagName}) + console.log("deleted tag"); + } catch(err) { + console.log('Failed to delete tag'+err.message); + } + try { + await github.rest.git.createRef({owner, repo, ref: "refs/tags/" + tagName, sha: context.sha}) + console.log("created tag"); + } catch(err) { + console.log('Failed to create tag'+err.message); + } + + - name: Deploy to GitHub + uses: softprops/action-gh-release@v1 + with: + name: Snapshot + tag_name: ${{ github.event.repository.name }}-latestcommit + body: Automatic build from master branch + files: | + ./${{ github.event.repository.name }}-*.zip + ./*-*.nvda-addon + ./${{ github.event.repository.name }}-*.json + + - name: register snapshot to actlab site + run: | + curl "https://actlab.org/api/addAlphaVersion?repo_name=${{ github.repository }}&commit_hash=${{ github.sha }}&version=${{ needs.build.outputs.build_version }}&password=${{ secrets.SCRIPT_PASSWORD }}" + + error_notify: + runs-on: ubuntu-latest + needs: deploy + if: ${{ failure() }} + steps: + - name: Send GitHub Action trigger data to Slack workflow + uses: slackapi/slack-github-action@v1 + with: + payload: | + { + "text": "Github actions build failed! <${{ github.server_url }}/${{ github.repository }}|${{ github.event.repository.name }}>のビルドが失敗しました。\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|対象のrun>お確認し、対応着手時・完了後は、本チャンネルにて経緯を報告ください。" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_ALERT_WEBHOOK_URL }} + SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK + diff --git a/.github/workflows/testAndBuild.yml b/.github/workflows/testAndBuild.yml new file mode 100644 index 0000000..1a6a4ca --- /dev/null +++ b/.github/workflows/testAndBuild.yml @@ -0,0 +1,68 @@ +name: Test and build + +on: + workflow_call: + inputs: + official_release: + description: Whether this is an official release + default: false + type: boolean + outputs: + build_version: + description: Version of the built package + value: ${{ jobs.build.outputs.build_version }} + +jobs: + build: + runs-on: windows-latest + outputs: + build_version: ${{ steps.output_version.outputs.version }} + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + architecture: x86 + python-version: 3.8 + cache: pip + + - name: Install requirements + run: | + python -m pip install -r requirements.txt + + - name: Test + run: | + echo no tests + + - name: Set tag name if This is an official release + run: echo "TAG_NAME=$($env:GITHUB_REF.Replace('refs/tags/', ''))" >> $env:GITHUB_ENV + if: ${{ inputs.official_release }} + + - name: Build + run: | + python tools\build.py + env: + COMMIT_TIMESTAMP: ${{ github.event.head_commit.timestamp}} + + - name: output version + id: output_version + shell: python + run: | + import os, sys + sys.path.append(os.getcwd()) + import buildVars + with open(os.environ["GITHUB_OUTPUT"], mode = "a") as f: + f.write("version="+buildVars.ADDON_VERSION) + + - name: Archive production artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ github.event.repository.name }} + path: | + ./${{ github.event.repository.name }}-*.zip + ./*-*.nvda-addon + ./${{ github.event.repository.name }}-*.json + diff --git a/tools/build.py b/tools/build.py index f4ce452..fee5b60 100644 --- a/tools/build.py +++ b/tools/build.py @@ -1,138 +1,117 @@ -# -*- coding: utf-8 -*- -#app build tool -#Copyright (C) 2019 Yukio Nozawa -#Copyright (C) 2019-2020 guredora -#Copyright (C) 2021 yamahubuki -#Copyright (C) 2021 Hiroki Fujii - -#constantsのimport前に必要 -from audioop import add -import os -import sys -sys.path.append(os.getcwd()) - -import datetime -import glob -import hashlib -import json -import math -import shutil -import subprocess -import urllib.request - -import buildVars -from tools import bumpup - -class build: - def __init__(self): - # appVeyorかどうかを判別し、処理をスタート - appveyor = self.setAppVeyor() - print("Starting build for %s (appveyor mode=%s)" % (buildVars.ADDON_KEYWORD, appveyor,)) - - # パッケージのパスとファイル名を決定 - package_path = "output\\" - if 'APPVEYOR_REPO_TAG_NAME' in os.environ: - build_filename = os.environ['APPVEYOR_REPO_TAG_NAME'] - # タグ名とバージョンが違ったらエラー - if build_filename != buildVars.ADDON_VERSION: - print("Unexpected tag name. expecting %s." %(buildVars.ADDON_VERSION,)) - exit(-1) - else: - build_filename = 'snapshot' - print("Will be built as %s" % build_filename) - - # addonフォルダの存在を確認 - if not os.path.exists("addon"): - print("Error: no addon folder found. Your working directory must be the root of the project. You shouldn't cd to tools and run this script.") - exit(-1) - - # 前のビルドをクリーンアップ - self.clean(package_path) - - # appveyorでのスナップショットの場合はバージョン番号を一時的に書き換え - # バージョン番号をセット - if build_filename == "snapshot" and appveyor: - self.version_number = self.makeSnapshotVersionNumber() - elif build_filename == "snapshot": - self.version_number = buildVars.ADDON_VERSION - else: - self.version_number = build_filename - - # ビルド - self.build(package_path, build_filename) - archive_name = "%s-%s.zip" % (buildVars.ADDON_KEYWORD, build_filename,) - addon_filename = "%s-%s.nvda-addon" % (buildVars.ADDON_KEYWORD, self.version_number) - shutil.copyfile(addon_filename, package_path + addon_filename) - print("Compressing into package...") - shutil.make_archive("%s-%s" % (buildVars.ADDON_KEYWORD, build_filename,),'zip',package_path) - - self.makePackageInfo(archive_name, addon_filename, self.version_number, build_filename) - print("Build finished!") - - def runcmd(self,cmd): - proc=subprocess.Popen(cmd.split(), shell=True, stdout=1, stderr=2) - proc.communicate() - return proc.poll() - - def setAppVeyor(self): - if len(sys.argv)>=2 and sys.argv[1]=="--appveyor": - return True - return False - - def clean(self,package_path): - if os.path.isdir(package_path): - print("Clearling previous build...") - shutil.rmtree("output\\") - - def makeSnapshotVersionNumber(self): - #日本標準時オブジェクト - JST = datetime.timezone(datetime.timedelta(hours=+9)) - #Pythonは世界標準時のZに対応していないので文字列処理で乗り切り、それを日本標準時に変換 - dt = datetime.datetime.fromisoformat(os.environ["APPVEYOR_REPO_COMMIT_TIMESTAMP"][0:19]+"+00:00").astimezone(JST) - major = str(dt.year)[2:4]+str(dt.month).zfill(2) - minor = str(dt.day) - patch = str(int(math.floor((dt.hour*3600+dt.minute*60+dt.second)/86400*1000))) - bumpup.bumpup(major+"."+minor+"."+patch, str(dt.date())) - return major+"."+minor+"."+patch - - - def build(self, package_path, build_filename): - print("Building...") - os.mkdir(package_path) - shutil.copyfile("addon\\doc\\ja\\readme.md", os.path.join(package_path, "readme.txt")) - shutil.copyfile("license", os.path.join(package_path, "license.txt")) - ret = self.runcmd("scons") - print("build finished with status %d" % ret) - if ret != 0: - sys.exit(ret) - - def makePackageInfo(self, archive_name, addon_filename, addon_version, build_filename): - if "APPVEYOR_REPO_COMMIT_TIMESTAMP" in os.environ: - #日本標準時オブジェクト - JST = datetime.timezone(datetime.timedelta(hours=+9)) - #Pythonは世界標準時のZに対応していないので文字列処理で乗り切り、それを日本標準時に変換 - dt = datetime.datetime.fromisoformat(os.environ["APPVEYOR_REPO_COMMIT_TIMESTAMP"][0:19]+"+00:00").astimezone(JST) - dateStr = "%s-%s-%s" % (str(dt.year), str(dt.month).zfill(2), str(dt.day).zfill(2)) - else: - dateStr = "this is a local build." - - print("computing hash...") - with open(archive_name, mode = "rb") as f: - content = f.read() - package_hash = hashlib.sha1(content).hexdigest() - with open(addon_filename, mode = "rb") as f: - content = f.read() - addon_hash = hashlib.sha1(content).hexdigest() - print("creating package info...") - info = {} - info["package_hash"] = package_hash - info["patch_filename"] = addon_filename - info["patch_hash"] = addon_hash - info["version"] = addon_version - info["released_date"] = dateStr - with open("%s-%s_info.json" % (buildVars.ADDON_KEYWORD, build_filename), mode = "w") as f: - json.dump(info, f) - - -if __name__ == "__main__": - build() +# -*- coding: utf-8 -*- +#app build tool +#Copyright (C) 2019 Yukio Nozawa +#Copyright (C) 2019-2024 guredora +#Copyright (C) 2021 yamahubuki +#Copyright (C) 2021 Hiroki Fujii + +#constantsのimport前に必要 +from audioop import add +import os +import sys +sys.path.append(os.getcwd()) + +import datetime +import glob +import hashlib +import json +import math +import shutil +import subprocess +import urllib.request + +import buildVars +from tools import bumpup + +class build: + def __init__(self): + # Github actionsなどの自動実行かどうかを判別し、処理をスタート + automated = self.setAutomated() + print("Starting build for %s(automated mode=%s)" % (buildVars.ADDON_KEYWORD, automated,)) + + # パッケージのパスとファイル名を決定 + package_path = "output\\" + build_filename = os.environ.get('TAG_NAME', 'snapshot') + # snapshotではなかった場合は、タグ名とバージョンが違ったらエラー + if (build_filename != "snapshot") and (build_filename != buildVars.ADDON_VERSION): + print("Unexpected tag name. expecting %s." %(buildVars.ADDON_VERSION,)) + exit(-1) + print("Will be built as %s" % build_filename) + + # addonフォルダの存在を確認 + if not os.path.exists("addon"): + print("Error: no addon folder found. Your working directory must be the root of the project. You shouldn't cd to tools and run this script.") + exit(-1) + + # 前のビルドをクリーンアップ + self.clean(package_path) + + # 自動実行でのスナップショットの場合はバージョン番号を一時的に書き換え + if build_filename == "snapshot" and automated: + self.makeSnapshotVersionNumber() + + # ビルド + self.build(package_path, build_filename) + archive_name = "%s-%s.zip" % (buildVars.ADDON_KEYWORD, build_filename,) + addon_filename = "%s-%s.nvda-addon" % (buildVars.ADDON_NAME, buildVars.ADDON_VERSION,) + shutil.copyfile(package_path + addon_filename, addon_filename) + self.makePackageInfo(archive_name, addon_filename, build_filename) + print("Build finished!") + + def runcmd(self,cmd): + proc=subprocess.Popen(cmd.split(), shell=True, stdout=1, stderr=2) + proc.communicate() + return proc.poll() + + def setAutomated(self): + return os.environ.get("GITHUB_ACTIONS", "false") == "true" + + def clean(self,package_path): + if os.path.isdir(package_path): + print("Clearling previous build...") + shutil.rmtree("output\\") + + def makeSnapshotVersionNumber(self): + #日本標準時オブジェクト + JST = datetime.timezone(datetime.timedelta(hours=+9)) + dt = datetime.datetime.fromisoformat(os.environ["COMMIT_TIMESTAMP"]).astimezone(JST) + major = f"{dt.year % 100:02d}{dt.month:02d}" + minor = str(dt.day) + patch = str(int(math.floor((dt.hour*3600+dt.minute*60+dt.second)/86400*1000))) + buildVars.ADDON_VERSION = major+"."+minor+"."+patch + buildVars.ADDON_RELEASE_DATE = str(dt.date()) + bumpup.bumpup(major+"."+minor+"."+patch, str(dt.date())) + return major+"."+minor+"."+patch + + def build(self, package_path, build_filename): + print("Building...") + shutil.copyfile("addon\\doc\\ja\\readme.md", "public\\readme.md") + shutil.copytree("public", package_path) + ret = self.runcmd("scons") + print("build finished with status %d" % ret) + if ret != 0: + sys.exit(ret) + print("Compressing into package...") + shutil.make_archive("%s-%s" % (buildVars.ADDON_KEYWORD, build_filename,),'zip',package_path) + + def makePackageInfo(self, archive_name, addon_filename, build_filename): + print("Calculating hash...") + with open(archive_name, mode = "rb") as f: + content = f.read() + package_hash = hashlib.sha1(content).hexdigest() + with open(addon_filename, mode = "rb") as f: + content = f.read() + addon_hash = hashlib.sha1(content).hexdigest() + print("creating package info...") + info = {} + info["package_hash"] = package_hash + info["patch_filename"] = addon_filename + info["patch_hash"] = addon_hash + info["version"] = buildVars.ADDON_VERSION + info["released_date"] = buildVars.ADDON_RELEASE_DATE + with open("%s-%s_info.json" % (buildVars.ADDON_KEYWORD, build_filename,), mode = "w") as f: + json.dump(info, f) + + +if __name__ == "__main__": + build()