mirror of
https://github.com/onyx-and-iris/nvda-addon-voicemeeter.git
synced 2026-04-18 09:03:30 +00:00
first commit
This commit is contained in:
332
sconstruct
Normal file
332
sconstruct
Normal file
@@ -0,0 +1,332 @@
|
||||
# NVDA add-on template SCONSTRUCT file
|
||||
# Copyright (C) 2012-2023 Rui Batista, Noelia Martinez, Joseph Lee
|
||||
# This file is covered by the GNU General Public License.
|
||||
# See the file COPYING.txt for more details.
|
||||
|
||||
import codecs
|
||||
import gettext
|
||||
import os
|
||||
import os.path
|
||||
import zipfile
|
||||
import sys
|
||||
|
||||
# While names imported below are available by default in every SConscript
|
||||
# Linters aren't aware about them.
|
||||
# To avoid Flake8 F821 warnings about them they are imported explicitly.
|
||||
# When using other Scons functions please add them to the line below.
|
||||
from SCons.Script import BoolVariable, Builder, Copy, Environment, Variables
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
# Bytecode should not be written for build vars module to keep the repository root folder clean.
|
||||
import buildVars # NOQA: E402
|
||||
|
||||
|
||||
def md2html(source, dest):
|
||||
import markdown
|
||||
# Use extensions if defined.
|
||||
mdExtensions = buildVars.markdownExtensions
|
||||
lang = os.path.basename(os.path.dirname(source)).replace('_', '-')
|
||||
localeLang = os.path.basename(os.path.dirname(source))
|
||||
try:
|
||||
_ = gettext.translation("nvda", localedir=os.path.join("addon", "locale"), languages=[localeLang]).gettext
|
||||
summary = _(buildVars.addon_info["addon_summary"])
|
||||
except Exception:
|
||||
summary = buildVars.addon_info["addon_summary"]
|
||||
title = "{addonSummary} {addonVersion}".format(
|
||||
addonSummary=summary, addonVersion=buildVars.addon_info["addon_version"]
|
||||
)
|
||||
headerDic = {
|
||||
"[[!meta title=\"": "# ",
|
||||
"\"]]": " #",
|
||||
}
|
||||
with codecs.open(source, "r", "utf-8") as f:
|
||||
mdText = f.read()
|
||||
for k, v in headerDic.items():
|
||||
mdText = mdText.replace(k, v, 1)
|
||||
htmlText = markdown.markdown(mdText, extensions=mdExtensions)
|
||||
# Optimization: build resulting HTML text in one go instead of writing parts separately.
|
||||
docText = "\n".join([
|
||||
"<!DOCTYPE html>",
|
||||
"<html lang=\"%s\">" % lang,
|
||||
"<head>",
|
||||
"<meta charset=\"UTF-8\">"
|
||||
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">",
|
||||
"<link rel=\"stylesheet\" type=\"text/css\" href=\"../style.css\" media=\"screen\">",
|
||||
"<title>%s</title>" % title,
|
||||
"</head>\n<body>",
|
||||
htmlText,
|
||||
"</body>\n</html>"
|
||||
])
|
||||
with codecs.open(dest, "w", "utf-8") as f:
|
||||
f.write(docText)
|
||||
|
||||
|
||||
def mdTool(env):
|
||||
mdAction = env.Action(
|
||||
lambda target, source, env: md2html(source[0].path, target[0].path),
|
||||
lambda target, source, env: 'Generating % s' % target[0],
|
||||
)
|
||||
mdBuilder = env.Builder(
|
||||
action=mdAction,
|
||||
suffix='.html',
|
||||
src_suffix='.md',
|
||||
)
|
||||
env['BUILDERS']['markdown'] = mdBuilder
|
||||
|
||||
|
||||
def validateVersionNumber(key, val, env):
|
||||
# Used to make sure version major.minor.patch are integers to comply with NV Access add-on store.
|
||||
# Ignore all this if version number is not specified, in which case json generator will validate this info.
|
||||
if val == "0.0.0":
|
||||
return
|
||||
versionNumber = val.split(".")
|
||||
if len(versionNumber) < 3:
|
||||
raise ValueError("versionNumber must have three parts (major.minor.patch)")
|
||||
if not all([part.isnumeric() for part in versionNumber]):
|
||||
raise ValueError("versionNumber (major.minor.patch) must be integers")
|
||||
|
||||
|
||||
vars = Variables()
|
||||
vars.Add("version", "The version of this build", buildVars.addon_info["addon_version"])
|
||||
vars.Add("versionNumber", "Version number of the form major.minor.patch", "0.0.0", validateVersionNumber)
|
||||
vars.Add(BoolVariable("dev", "Whether this is a daily development version", False))
|
||||
vars.Add("channel", "Update channel for this build", buildVars.addon_info["addon_updateChannel"])
|
||||
|
||||
env = Environment(variables=vars, ENV=os.environ, tools=['gettexttool', mdTool])
|
||||
env.Append(**buildVars.addon_info)
|
||||
|
||||
if env["dev"]:
|
||||
import datetime
|
||||
buildDate = datetime.datetime.now()
|
||||
year, month, day = str(buildDate.year), str(buildDate.month), str(buildDate.day)
|
||||
versionTimestamp = "".join([year, month.zfill(2), day.zfill(2)])
|
||||
env["addon_version"] = f"{versionTimestamp}.0.0"
|
||||
env["versionNumber"] = f"{versionTimestamp}.0.0"
|
||||
env["channel"] = "dev"
|
||||
elif env["version"] is not None:
|
||||
env["addon_version"] = env["version"]
|
||||
if "channel" in env and env["channel"] is not None:
|
||||
env["addon_updateChannel"] = env["channel"]
|
||||
|
||||
buildVars.addon_info["addon_version"] = env["addon_version"]
|
||||
buildVars.addon_info["addon_updateChannel"] = env["addon_updateChannel"]
|
||||
|
||||
addonFile = env.File("${addon_name}-${addon_version}.nvda-addon")
|
||||
|
||||
|
||||
def addonGenerator(target, source, env, for_signature):
|
||||
action = env.Action(
|
||||
lambda target, source, env: createAddonBundleFromPath(source[0].abspath, target[0].abspath) and None,
|
||||
lambda target, source, env: "Generating Addon %s" % target[0]
|
||||
)
|
||||
return action
|
||||
|
||||
|
||||
def manifestGenerator(target, source, env, for_signature):
|
||||
action = env.Action(
|
||||
lambda target, source, env: generateManifest(source[0].abspath, target[0].abspath) and None,
|
||||
lambda target, source, env: "Generating manifest %s" % target[0]
|
||||
)
|
||||
return action
|
||||
|
||||
|
||||
def translatedManifestGenerator(target, source, env, for_signature):
|
||||
dir = os.path.abspath(os.path.join(os.path.dirname(str(source[0])), ".."))
|
||||
lang = os.path.basename(dir)
|
||||
action = env.Action(
|
||||
lambda target, source, env: generateTranslatedManifest(source[1].abspath, lang, target[0].abspath) and None,
|
||||
lambda target, source, env: "Generating translated manifest %s" % target[0]
|
||||
)
|
||||
return action
|
||||
|
||||
|
||||
env['BUILDERS']['NVDAAddon'] = Builder(generator=addonGenerator)
|
||||
env['BUILDERS']['NVDAManifest'] = Builder(generator=manifestGenerator)
|
||||
env['BUILDERS']['NVDATranslatedManifest'] = Builder(generator=translatedManifestGenerator)
|
||||
|
||||
|
||||
def createAddonHelp(dir):
|
||||
docsDir = os.path.join(dir, "doc")
|
||||
if os.path.isfile("style.css"):
|
||||
cssPath = os.path.join(docsDir, "style.css")
|
||||
cssTarget = env.Command(cssPath, "style.css", Copy("$TARGET", "$SOURCE"))
|
||||
env.Depends(addon, cssTarget)
|
||||
if os.path.isfile("readme.md"):
|
||||
readmePath = os.path.join(docsDir, buildVars.baseLanguage, "readme.md")
|
||||
readmeTarget = env.Command(readmePath, "readme.md", Copy("$TARGET", "$SOURCE"))
|
||||
env.Depends(addon, readmeTarget)
|
||||
|
||||
|
||||
def createAddonBundleFromPath(path, dest):
|
||||
""" Creates a bundle from a directory that contains an addon manifest file."""
|
||||
basedir = os.path.abspath(path)
|
||||
with zipfile.ZipFile(dest, 'w', zipfile.ZIP_DEFLATED) as z:
|
||||
# FIXME: the include/exclude feature may or may not be useful. Also python files can be pre-compiled.
|
||||
for dir, dirnames, filenames in os.walk(basedir):
|
||||
relativePath = os.path.relpath(dir, basedir)
|
||||
for filename in filenames:
|
||||
pathInBundle = os.path.join(relativePath, filename)
|
||||
absPath = os.path.join(dir, filename)
|
||||
if pathInBundle not in buildVars.excludedFiles:
|
||||
z.write(absPath, pathInBundle)
|
||||
createAddonStoreJson(dest)
|
||||
return dest
|
||||
|
||||
|
||||
def createAddonStoreJson(bundle):
|
||||
"""Creates add-on store JSON file from an add-on package and manifest data."""
|
||||
import json
|
||||
import hashlib
|
||||
# Set different json file names and version number properties based on version number parsing results.
|
||||
if env["versionNumber"] == "0.0.0":
|
||||
env["versionNumber"] = buildVars.addon_info["addon_version"]
|
||||
versionNumberParsed = env["versionNumber"].split(".")
|
||||
if all([part.isnumeric() for part in versionNumberParsed]):
|
||||
if len(versionNumberParsed) == 1:
|
||||
versionNumberParsed += ["0", "0"]
|
||||
elif len(versionNumberParsed) == 2:
|
||||
versionNumberParsed.append("0")
|
||||
else:
|
||||
versionNumberParsed = []
|
||||
if len(versionNumberParsed):
|
||||
major, minor, patch = [int(part) for part in versionNumberParsed]
|
||||
jsonFilename = f'{major}.{minor}.{patch}.json'
|
||||
else:
|
||||
jsonFilename = f'{buildVars.addon_info["addon_version"]}.json'
|
||||
major, minor, patch = 0, 0, 0
|
||||
print('Generating % s' % jsonFilename)
|
||||
sha256 = hashlib.sha256()
|
||||
with open(bundle, "rb") as f:
|
||||
for byte_block in iter(lambda: f.read(65536), b""):
|
||||
sha256.update(byte_block)
|
||||
hashValue = sha256.hexdigest()
|
||||
try:
|
||||
minimumNVDAVersion = buildVars.addon_info["addon_minimumNVDAVersion"].split(".")
|
||||
except AttributeError:
|
||||
minimumNVDAVersion = [0, 0, 0]
|
||||
minMajor, minMinor = minimumNVDAVersion[:2]
|
||||
minPatch = minimumNVDAVersion[-1] if len(minimumNVDAVersion) == 3 else "0"
|
||||
try:
|
||||
lastTestedNVDAVersion = buildVars.addon_info["addon_lastTestedNVDAVersion"].split(".")
|
||||
except AttributeError:
|
||||
lastTestedNVDAVersion = [0, 0, 0]
|
||||
lastTestedMajor, lastTestedMinor = lastTestedNVDAVersion[:2]
|
||||
lastTestedPatch = lastTestedNVDAVersion[-1] if len(lastTestedNVDAVersion) == 3 else "0"
|
||||
channel = buildVars.addon_info["addon_updateChannel"]
|
||||
if channel is None:
|
||||
channel = "stable"
|
||||
addonStoreEntry = {
|
||||
"addonId": buildVars.addon_info["addon_name"],
|
||||
"displayName": buildVars.addon_info["addon_summary"],
|
||||
"URL": "",
|
||||
"description": buildVars.addon_info["addon_description"],
|
||||
"sha256": hashValue,
|
||||
"homepage": buildVars.addon_info["addon_url"],
|
||||
"addonVersionName": buildVars.addon_info["addon_version"],
|
||||
"addonVersionNumber": {
|
||||
"major": major,
|
||||
"minor": minor,
|
||||
"patch": patch
|
||||
},
|
||||
"minNVDAVersion": {
|
||||
"major": int(minMajor),
|
||||
"minor": int(minMinor),
|
||||
"patch": int(minPatch)
|
||||
},
|
||||
"lastTestedVersion": {
|
||||
"major": int(lastTestedMajor),
|
||||
"minor": int(lastTestedMinor),
|
||||
"patch": int(lastTestedPatch)
|
||||
},
|
||||
"channel": channel,
|
||||
"publisher": "",
|
||||
"sourceURL": buildVars.addon_info["addon_sourceURL"],
|
||||
"license": buildVars.addon_info["addon_license"],
|
||||
"licenseURL": buildVars.addon_info["addon_licenseURL"],
|
||||
}
|
||||
with open(jsonFilename, "w") as addonStoreJson:
|
||||
json.dump(addonStoreEntry, addonStoreJson, indent="\t")
|
||||
|
||||
|
||||
def generateManifest(source, dest):
|
||||
addon_info = buildVars.addon_info
|
||||
with codecs.open(source, "r", "utf-8") as f:
|
||||
manifest_template = f.read()
|
||||
manifest = manifest_template.format(**addon_info)
|
||||
with codecs.open(dest, "w", "utf-8") as f:
|
||||
f.write(manifest)
|
||||
|
||||
|
||||
def generateTranslatedManifest(source, language, out):
|
||||
_ = gettext.translation("nvda", localedir=os.path.join("addon", "locale"), languages=[language]).gettext
|
||||
vars = {}
|
||||
for var in ("addon_summary", "addon_description"):
|
||||
vars[var] = _(buildVars.addon_info[var])
|
||||
with codecs.open(source, "r", "utf-8") as f:
|
||||
manifest_template = f.read()
|
||||
result = manifest_template.format(**vars)
|
||||
with codecs.open(out, "w", "utf-8") as f:
|
||||
f.write(result)
|
||||
|
||||
|
||||
def expandGlobs(files):
|
||||
return [f for pattern in files for f in env.Glob(pattern)]
|
||||
|
||||
|
||||
addon = env.NVDAAddon(addonFile, env.Dir('addon'))
|
||||
|
||||
langDirs = [f for f in env.Glob(os.path.join("addon", "locale", "*"))]
|
||||
|
||||
# Allow all NVDA's gettext po files to be compiled in source/locale, and manifest files to be generated
|
||||
for dir in langDirs:
|
||||
poFile = dir.File(os.path.join("LC_MESSAGES", "nvda.po"))
|
||||
moFile = env.gettextMoFile(poFile)
|
||||
env.Depends(moFile, poFile)
|
||||
translatedManifest = env.NVDATranslatedManifest(
|
||||
dir.File("manifest.ini"),
|
||||
[moFile, os.path.join("manifest-translated.ini.tpl")]
|
||||
)
|
||||
env.Depends(translatedManifest, ["buildVars.py"])
|
||||
env.Depends(addon, [translatedManifest, moFile])
|
||||
|
||||
pythonFiles = expandGlobs(buildVars.pythonSources)
|
||||
for file in pythonFiles:
|
||||
env.Depends(addon, file)
|
||||
|
||||
# Convert markdown files to html
|
||||
# We need at least doc in English and should enable the Help button for the add-on in Add-ons Manager
|
||||
createAddonHelp("addon")
|
||||
for mdFile in env.Glob(os.path.join('addon', 'doc', '*', '*.md')):
|
||||
htmlFile = env.markdown(mdFile)
|
||||
try: # It is possible that no moFile was set, because an add-on has no translations.
|
||||
moFile
|
||||
except NameError: # Runs if there is no moFile
|
||||
env.Depends(htmlFile, mdFile)
|
||||
else: # Runs if there is a moFile
|
||||
env.Depends(htmlFile, [mdFile, moFile])
|
||||
env.Depends(addon, htmlFile)
|
||||
|
||||
# Pot target
|
||||
i18nFiles = expandGlobs(buildVars.i18nSources)
|
||||
gettextvars = {
|
||||
'gettext_package_bugs_address': 'nvda-translations@groups.io',
|
||||
'gettext_package_name': buildVars.addon_info['addon_name'],
|
||||
'gettext_package_version': buildVars.addon_info['addon_version']
|
||||
}
|
||||
|
||||
pot = env.gettextPotFile("${addon_name}.pot", i18nFiles, **gettextvars)
|
||||
env.Alias('pot', pot)
|
||||
env.Depends(pot, i18nFiles)
|
||||
mergePot = env.gettextMergePotFile("${addon_name}-merge.pot", i18nFiles, **gettextvars)
|
||||
env.Alias('mergePot', mergePot)
|
||||
env.Depends(mergePot, i18nFiles)
|
||||
|
||||
# Generate Manifest path
|
||||
manifest = env.NVDAManifest(os.path.join("addon", "manifest.ini"), os.path.join("manifest.ini.tpl"))
|
||||
# Ensure manifest is rebuilt if buildVars is updated.
|
||||
env.Depends(manifest, "buildVars.py")
|
||||
|
||||
env.Depends(addon, manifest)
|
||||
env.Default(addon)
|
||||
env.Clean(addon, ['.sconsign.dblite', 'addon/doc/' + buildVars.baseLanguage + '/'])
|
||||
Reference in New Issue
Block a user