Merge pull request #221229 from euank/anki-native

This commit is contained in:
Sandro 2023-04-11 23:43:41 +02:00 committed by GitHub
commit 0754f14961
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 5236 additions and 183 deletions

4849
pkgs/games/anki/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,188 +1,258 @@
{ stdenv
, buildPythonApplication
, lib
, python
, fetchurl
{ lib
, stdenv
, buildEnv
, fetchFromGitHub
, fetchpatch
, fetchYarnDeps
, fixup_yarn_lock
, installShellFiles
, lame
, mpv-unwrapped
, libpulseaudio
, pyqtwebengine
, decorator
, beautifulsoup4
, sqlalchemy
, pyaudio
, requests
, markdown
, matplotlib
, mock
, pytest
, glibcLocales
, nose
, jsonschema
, setuptools
, send2trash
, ninja
, nodejs
, nodejs-slim
, protobuf
, python3
, qt6
, rsync
, rustPlatform
, writeShellScriptBin
, yarn
, CoreAudio
# This little flag adds a huge number of dependencies, but we assume that
# everyone wants Anki to draw plots with statistics by default.
, plotsSupport ? true
# manual
, asciidoc
}:
let
# when updating, also update rev-manual to a recent version of
# https://github.com/ankitects/anki-docs
# The manual is distributed independently of the software.
version = "2.1.15";
sha256-pkg = "12dvyf3j9df4nrhhnqbzd9b21rpzkh4i6yhhangn2zf7ch0pclss";
rev-manual = "8f6387867ac37ef3fe9d0b986e70f898d1a49139";
sha256-manual = "0pm5slxn78r44ggvbksz7rv9hmlnsvn9z811r6f63dsc8vm6mfml";
pname = "anki";
version = "2.1.60";
rev = "76d8807315fcc2675e7fa44d9ddf3d4608efc487";
manual = stdenv.mkDerivation {
pname = "anki-manual";
inherit version;
src = fetchFromGitHub {
owner = "ankitects";
repo = "anki-docs";
rev = rev-manual;
sha256 = sha256-manual;
src = fetchFromGitHub {
owner = "ankitects";
repo = "anki";
rev = version;
hash = "sha256-hNrf6asxF7r7QK2XO150yiRjyHAYKN8OFCFYX0SAiwA=";
fetchSubmodules = true;
};
cargoDeps = rustPlatform.importCargoLock {
lockFile = ./Cargo.lock;
outputHashes = {
"csv-1.1.6" = "sha256-w728ffOVkI+IfK6FbmkGhr0CjuyqgJnPB1kutMJIUYg=";
"linkcheck-0.4.1-alpha.0" = "sha256-Fiom8oHW9y7vV2RLXW0ClzHOdIlBq3Z9jLP+p6Sk4GI=";
};
dontInstall = true;
nativeBuildInputs = [ asciidoc ];
patchPhase = ''
# rsync isnt needed
# WEB is the PREFIX
# We remove any special ankiweb output generation
# and rename every .mako to .html
sed -e 's/rsync -a/cp -a/g' \
-e "s|\$(WEB)/docs|$out/share/doc/anki/html|" \
-e '/echo asciidoc/,/mv $@.tmp $@/c \\tasciidoc -b html5 -o $@ $<' \
-e 's/\.mako/.html/g' \
-i Makefile
# patch absolute links to the other language manuals
sed -e 's|https://apps.ankiweb.net/docs/|link:./|g' \
-i {manual.txt,manual.*.txt}
# theres an artifact in most input files
sed -e '/<%def.*title.*/d' \
-i *.txt
mkdir -p $out/share/doc/anki/html
};
anki-build-python = python3.withPackages (ps: with ps; [
pip
mypy-protobuf
]);
# anki shells out to git to check its revision, and also to update submodules
# We don't actually need the submodules, so we stub that out
fakeGit = writeShellScriptBin "git" ''
case "$*" in
"rev-parse --short=8 HEAD")
echo ${builtins.substring 0 8 rev}
;;
*"submodule update "*)
exit 0
;;
*)
echo "Unrecognized git: $@"
exit 1
;;
esac
'';
# We don't want to run pip-sync, it does network-io
fakePipSync = writeShellScriptBin "pip-sync" ''
exit 0
'';
offlineYarn = writeShellScriptBin "yarn" ''
[[ "$1" == "install" ]] && exit 0
exec ${yarn}/bin/yarn --offline "$@"
'';
pyEnv = buildEnv {
name = "anki-pyenv-${version}";
paths = with python3.pkgs; [
pip
fakePipSync
anki-build-python
];
pathsToLink = [ "/bin" ];
};
yarnOfflineCache = fetchYarnDeps {
yarnLock = "${src}/yarn.lock";
hash = "sha256-bAtmMGWi5ETIidFFnG3jzJg2mSBnH5ONO2/Lr9A3PpQ=";
};
# https://discourse.nixos.org/t/mkyarnpackage-lockfile-has-incorrect-entry/21586/3
anki-nodemodules = stdenv.mkDerivation {
pname = "anki-nodemodules";
inherit version src yarnOfflineCache;
nativeBuildInputs = [
fixup_yarn_lock
yarn
nodejs-slim
];
configurePhase = ''
export HOME=$NIX_BUILD_TOP
yarn config --offline set yarn-offline-mirror $yarnOfflineCache
fixup_yarn_lock yarn.lock
yarn install --offline --frozen-lockfile --ignore-scripts --no-progress --non-interactive
patchShebangs node_modules/
yarn run postinstall --offline
'';
installPhase = ''
mv node_modules $out
'';
};
in
buildPythonApplication rec {
pname = "anki";
inherit version;
format = "other";
src = fetchurl {
urls = [
"https://apps.ankiweb.net/downloads/current/${pname}-${version}-source.tgz"
# "https://apps.ankiweb.net/downloads/current/${name}-source.tgz"
# "http://ankisrs.net/download/mirror/${name}.tgz"
# "http://ankisrs.net/download/mirror/archive/${name}.tgz"
];
sha256 = sha256-pkg;
};
python3.pkgs.buildPythonApplication {
inherit pname version src;
outputs = [ "out" "doc" "man" ];
propagatedBuildInputs = [
pyqtwebengine
sqlalchemy
beautifulsoup4
send2trash
pyaudio
requests
decorator
markdown
jsonschema
setuptools
]
++ lib.optional plotsSupport matplotlib
++ lib.optionals stdenv.isDarwin [ CoreAudio ]
;
nativeCheckInputs = [ pytest glibcLocales mock nose ];
nativeBuildInputs = [ pyqtwebengine.wrapQtAppsHook ];
buildInputs = [ lame mpv-unwrapped libpulseaudio ];
patches = [
# Disable updated version check.
./no-version-check.patch
(fetchpatch {
name = "fix-mpv-args.patch";
url = "https://sources.debian.org/data/main/a/anki/2.1.15+dfsg-3/debian/patches/fix-mpv-args.patch";
sha256 = "1dimnnawk64m5bbdbjrxw5k08q95l728n94cgkrrwxwavmmywaj2";
})
(fetchpatch {
name = "anki-2.1.15-unescape.patch";
url = "https://795309.bugs.gentoo.org/attachment.cgi?id=715200";
sha256 = "14rz864kdaba4fd1marwkyz9n1jiqnbjy4al8bvwlhpvp0rm1qk6";
})
./patches/gl-fixup.patch
./patches/no-update-check.patch
# Upstreamed in https://github.com/ankitects/anki/pull/2446
# We can drop these once we update to an anki version that includes them
# already
./patches/0001-Don-t-download-nodejs-if-NODE_BINARY-is-set.patch
./patches/0002-Allow-setting-YARN_BINARY-for-the-build-system.patch
# Not upstreamed
./patches/0003-Skip-formatting-python-code.patch
];
# Anki does not use setup.py
dontBuild = true;
inherit cargoDeps;
postPatch = ''
# Remove QT translation files. We'll use the standard QT ones.
rm "locale/"*.qm
nativeBuildInputs = [
fakeGit
fixup_yarn_lock
offlineYarn
# hitting F1 should open the local manual
substituteInPlace anki/consts.py \
--replace 'HELP_SITE="http://ankisrs.net/docs/manual.html"' \
'HELP_SITE="${manual}/share/doc/anki/html/manual.html"'
installShellFiles
rustPlatform.rust.cargo
rustPlatform.cargoSetupHook
ninja
qt6.wrapQtAppsHook
rsync
];
nativeCheckInputs = with python3.pkgs; [ pytest mock astroid ];
buildInputs = [
qt6.qtbase
];
propagatedBuildInputs = with python3.pkgs; [
# This rather long list came from running:
# grep --no-filename -oE "^[^ =]*" python/{requirements.base.txt,requirements.bundle.txt,requirements.qt6_4.txt} | \
# sort | uniq | grep -v "^#$"
# in their repo at the git tag for this version
# There's probably a more elegant way, but the above extracted all the
# names, without version numbers, of their python dependencies. The hope is
# that nixpkgs versions are "close enough"
# I then removed the ones the check phase failed on (pythonCatchConflictsPhase)
beautifulsoup4
certifi
charset-normalizer
click
colorama
decorator
distro
flask
flask-cors
idna
importlib-metadata
itsdangerous
jinja2
jsonschema
markdown
markupsafe
orjson
pep517
python3.pkgs.protobuf
pyparsing
pyqt6
pyqt6-sip
pyqt6-webengine
pyrsistent
pysocks
requests
send2trash
six
soupsieve
urllib3
waitress
werkzeug
zipp
] ++ lib.optionals stdenv.isDarwin [ CoreAudio ];
# Activate optimizations
RELEASE = true;
PROTOC_BINARY = lib.getExe protobuf;
NODE_BINARY = lib.getExe nodejs;
YARN_BINARY = lib.getExe offlineYarn;
PYTHON_BINARY = lib.getExe python3;
inherit yarnOfflineCache;
dontUseNinjaInstall = false;
buildPhase = ''
export RUST_BACKTRACE=1
export RUST_LOG=debug
mkdir -p out/pylib/anki \
.git
echo ${builtins.substring 0 8 rev} > out/buildhash
touch out/env
touch .git/HEAD
ln -vsf ${pyEnv} ./out/pyenv
rsync --chmod +w -avP ${anki-nodemodules}/ out/node_modules/
ln -vsf out/node_modules node_modules
export HOME=$NIX_BUILD_TOP
yarn config --offline set yarn-offline-mirror $yarnOfflineCache
fixup_yarn_lock yarn.lock
patchShebangs ./ninja
PIP_USER=1 ./ninja build wheels
'';
# UTF-8 locale needed for testing
LC_ALL = "en_US.UTF-8";
# tests fail with to many open files
# TODO: verify if this is still true (I can't, no mac)
doCheck = !stdenv.isDarwin;
# - Anki writes some files to $HOME during tests
# - Skip tests using network
# mimic https://github.com/ankitects/anki/blob/76d8807315fcc2675e7fa44d9ddf3d4608efc487/build/ninja_gen/src/python.rs#L232-L250
checkPhase = ''
HOME=$TMP pytest --ignore tests/test_sync.py
HOME=$TMP ANKI_TEST_MODE=1 PYTHONPATH=$PYTHONPATH:$PWD/out/pylib \
pytest -p no:cacheprovider pylib/tests
HOME=$TMP ANKI_TEST_MODE=1 PYTHONPATH=$PYTHONPATH:$PWD/out/pylib:$PWD/pylib:$PWD/out/qt \
pytest -p no:cacheprovider qt/tests
'';
installPhase = ''
pp=$out/lib/${python.libPrefix}/site-packages
mkdir -p $out/bin
mkdir -p $out/share/applications
mkdir -p $doc/share/doc/anki
mkdir -p $man/share/man/man1
mkdir -p $out/share/mime/packages
mkdir -p $out/share/pixmaps
mkdir -p $pp
cat > $out/bin/anki <<EOF
#!${python}/bin/python
import aqt
aqt.run()
EOF
chmod 755 $out/bin/anki
cp -v anki.desktop $out/share/applications/
cp -v README* LICENSE* $doc/share/doc/anki/
cp -v anki.1 $man/share/man/man1/
cp -v anki.xml $out/share/mime/packages/
cp -v anki.{png,xpm} $out/share/pixmaps/
cp -rv locale $out/share/
cp -rv anki aqt web $pp/
# copy the manual into $doc
cp -r ${manual}/share/doc/anki/html $doc/share/doc/anki
preInstall = ''
mkdir dist
mv out/wheels/* dist
'';
postInstall = ''
install -D -t $out/share/applications qt/bundle/lin/anki.desktop
install -D -t $doc/share/doc/anki README* LICENSE*
install -D -t $out/share/mime/packages qt/bundle/lin/anki.xml
install -D -t $out/share/pixmaps qt/bundle/lin/anki.{png,xpm}
installManPage qt/bundle/lin/anki.1
'';
# now wrapPythonPrograms from postFixup will add both python and qt env variables
dontWrapQtApps = true;
preFixup = ''
makeWrapperArgs+=(
"''${qtWrapperArgs[@]}"
@ -190,10 +260,6 @@ buildPythonApplication rec {
)
'';
passthru = {
inherit manual;
};
meta = with lib; {
homepage = "https://apps.ankiweb.net/";
description = "Spaced repetition flashcard program";
@ -211,6 +277,6 @@ buildPythonApplication rec {
'';
license = licenses.agpl3Plus;
platforms = platforms.mesaPlatforms;
maintainers = with maintainers; [ oxij Profpatsch ];
maintainers = with maintainers; [ oxij Profpatsch euank ];
};
}

View File

@ -1,13 +0,0 @@
diff -Nurp anki-2.0.33.orig/aqt/main.py anki-2.0.33/aqt/main.py
--- anki-2.0.33.orig/aqt/main.py 2016-01-05 21:37:53.904533750 +0100
+++ anki-2.0.33/aqt/main.py 2016-01-05 21:39:11.469175976 +0100
@@ -820,6 +820,9 @@ title="%s">%s</button>''' % (
##########################################################################
def setupAutoUpdate(self):
+ # Don't check for latest version since the versions are
+ # managed in Nixpkgs.
+ return
import aqt.update
self.autoUpdate = aqt.update.LatestVersionFinder(self)
self.connect(self.autoUpdate, SIGNAL("newVerAvail"), self.newVerAvail)

View File

@ -0,0 +1,53 @@
From 53740ca75d167fab5c403a462e21ecd717b1dafa Mon Sep 17 00:00:00 2001
From: Euan Kemp <euank@euank.com>
Date: Fri, 17 Mar 2023 22:38:04 +0900
Subject: [PATCH 1/2] Don't download nodejs if NODE_BINARY is set
Some build environments, such as nixpkgs, restrict network access and
thus would prefer to not download anything at all. Setting PROTOC_BINARY
and friends makes the build system not download stuff, and the same
should be true for nodejs
---
build/ninja_gen/src/node.rs | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/build/ninja_gen/src/node.rs b/build/ninja_gen/src/node.rs
index df05e149d..d08c7011e 100644
--- a/build/ninja_gen/src/node.rs
+++ b/build/ninja_gen/src/node.rs
@@ -105,16 +105,6 @@ pub fn setup_node(
binary_exports: &[&'static str],
mut data_exports: HashMap<&str, Vec<Cow<str>>>,
) -> Result<()> {
- download_and_extract(
- build,
- "node",
- archive,
- hashmap! {
- "bin" => vec![if cfg!(windows) { "node.exe" } else { "bin/node" }],
- "npm" => vec![if cfg!(windows) { "npm.cmd " } else { "bin/npm" }]
- },
- )?;
-
let node_binary = match std::env::var("NODE_BINARY") {
Ok(path) => {
assert!(
@@ -124,6 +114,15 @@ pub fn setup_node(
path.into()
}
Err(_) => {
+ download_and_extract(
+ build,
+ "node",
+ archive,
+ hashmap! {
+ "bin" => vec![if cfg!(windows) { "node.exe" } else { "bin/node" }],
+ "npm" => vec![if cfg!(windows) { "npm.cmd " } else { "bin/npm" }]
+ },
+ )?;
inputs![":extract:node:bin"]
}
};
--
2.39.2

View File

@ -0,0 +1,36 @@
From 16af7d4cabcf10797bd110c905a9d7694bde0fb4 Mon Sep 17 00:00:00 2001
From: Euan Kemp <euank@euank.com>
Date: Fri, 17 Mar 2023 23:07:05 +0900
Subject: [PATCH 2/2] Allow setting YARN_BINARY for the build system
---
build/ninja_gen/src/node.rs | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/build/ninja_gen/src/node.rs b/build/ninja_gen/src/node.rs
index d08c7011e..c1e2ce1b3 100644
--- a/build/ninja_gen/src/node.rs
+++ b/build/ninja_gen/src/node.rs
@@ -129,7 +129,18 @@ pub fn setup_node(
let node_binary = build.expand_inputs(node_binary);
build.variable("node_binary", &node_binary[0]);
- build.add("yarn", YarnSetup {})?;
+ match std::env::var("YARN_BINARY") {
+ Ok(path) => {
+ assert!(
+ Utf8Path::new(&path).is_absolute(),
+ "YARN_BINARY must be absolute"
+ );
+ build.add_resolved_files_to_group("yarn:bin", &vec![path]);
+ },
+ Err(_) => {
+ build.add("yarn", YarnSetup {})?;
+ },
+ };
for binary in binary_exports {
data_exports.insert(
--
2.39.2

View File

@ -0,0 +1,31 @@
From ed5090b237bca768dbf7dfc3b4414b955879f15e Mon Sep 17 00:00:00 2001
From: Euan Kemp <euank@euank.com>
Date: Fri, 7 Apr 2023 20:22:34 +0900
Subject: [PATCH 3/3] Skip formatting python code
---
pylib/tools/hookslib.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pylib/tools/hookslib.py b/pylib/tools/hookslib.py
index 6361c633e..95ecb64a2 100644
--- a/pylib/tools/hookslib.py
+++ b/pylib/tools/hookslib.py
@@ -82,7 +82,7 @@ class Hook:
code = f"""\
class {self.classname()}:
{classdoc}{self.list_code()}
-
+
def append(self, callback: {self.callable()}) -> None:
'''{appenddoc}'''
self._hooks.append(callback)
@@ -208,4 +208,4 @@ def write_file(path: str, hooks: list[Hook], prefix: str, suffix: str):
os.environ["USERPROFILE"] = os.environ["HOME"]
with open(path, "wb") as file:
file.write(code.encode("utf8"))
- subprocess.run([sys.executable, "-m", "black", "-q", path], check=True)
+ # subprocess.run([sys.executable, "-m", "black", "-q", path], check=True)
--
2.39.2

View File

@ -0,0 +1,17 @@
diff --git a/qt/aqt/__init__.py b/qt/aqt/__init__.py
index 352848cfd..3fd5d0769 100644
--- a/qt/aqt/__init__.py
+++ b/qt/aqt/__init__.py
@@ -402,12 +402,6 @@ def parseArgs(argv: list[str]) -> tuple[argparse.Namespace, list[str]]:
def setupGL(pm: aqt.profiles.ProfileManager) -> None:
driver = pm.video_driver()
- # work around pyqt loading wrong GL library
- if is_lin:
- import ctypes
-
- ctypes.CDLL("libGL.so.1", ctypes.RTLD_GLOBAL)
-
# catch opengl errors
def msgHandler(category: Any, ctx: Any, msg: Any) -> None:
if category == QtMsgType.QtDebugMsg:

View File

@ -0,0 +1,13 @@
diff --git a/qt/aqt/main.py b/qt/aqt/main.py
index 0f2764f66..c42a88402 100644
--- a/qt/aqt/main.py
+++ b/qt/aqt/main.py
@@ -1395,6 +1395,8 @@ title="{}" {}>{}</button>""".format(
##########################################################################
def setupAutoUpdate(self) -> None:
+ # nixpkgs patch; updates are managed by nix
+ return
import aqt.update
self.autoUpdate = aqt.update.LatestVersionFinder(self)

View File

@ -1,7 +1,6 @@
{ lib
, fetchFromGitHub
, python3
, anki
}:
python3.pkgs.buildPythonApplication rec {
@ -11,7 +10,8 @@ python3.pkgs.buildPythonApplication rec {
owner = "ankicommunity";
repo = "anki-sync-server";
rev = version;
sha256 = "196xhd6vzp1ncr3ahz0bv0gp1ap2s37j8v48dwmvaywzayakqdab";
hash = "sha256-RXrdJGJ+HMSpDGQBuVPPqsh3+uwAgE6f7ZJ0yFRMI8I=";
fetchSubmodules = true;
};
format = "other";
@ -21,6 +21,7 @@ python3.pkgs.buildPythonApplication rec {
mkdir -p $out/${python3.sitePackages}
cp -r ankisyncd utils ankisyncd.conf $out/${python3.sitePackages}
cp -r anki-bundled/anki $out/${python3.sitePackages}
mkdir $out/share
cp ankisyncctl.py $out/share/
@ -28,7 +29,7 @@ python3.pkgs.buildPythonApplication rec {
'';
fixupPhase = ''
PYTHONPATH="$PYTHONPATH:$out/${python3.sitePackages}:${anki}"
PYTHONPATH="$PYTHONPATH:$out/${python3.sitePackages}"
makeWrapper "${python3.interpreter}" "$out/bin/ankisyncd" \
--set PYTHONPATH $PYTHONPATH \
@ -46,14 +47,14 @@ python3.pkgs.buildPythonApplication rec {
buildInputs = [ ];
propagatedBuildInputs = [ anki ];
propagatedBuildInputs = with python3.pkgs; [
decorator
requests
];
checkPhase = ''
# Exclude tests that require sqlite's sqldiff command, since
# it isn't yet packaged for NixOS, although 2 PRs exist:
# - https://github.com/NixOS/nixpkgs/pull/69112
# - https://github.com/NixOS/nixpkgs/pull/75784
# Once this is merged, these tests can be run as well.
# skip these tests, our files are too young:
# tests/test_web_media.py::SyncAppFunctionalMediaTest::test_sync_mediaChanges ValueError: ZIP does not support timestamps before 1980
pytest --ignore tests/test_web_media.py tests/
'';

View File

@ -35630,7 +35630,7 @@ with pkgs;
angband = callPackage ../games/angband { };
anki = python39Packages.callPackage ../games/anki {
anki = callPackage ../games/anki {
inherit (darwin.apple_sdk.frameworks) CoreAudio;
};
anki-bin = callPackage ../games/anki/bin.nix { buildFHSUserEnv = buildFHSUserEnvBubblewrap; };