diff --git a/.gitignore b/.gitignore index f3b87f932..122030a78 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,9 @@ schemas/bin/ schemas/build schemas/build/* target/* +windows-installer/build +windows-installer/build/* +windows-installer/.gradle/* \.orig\..*$ \.orig$ \.chg\..*$ diff --git a/build.gradle b/build.gradle index 72b54068e..5f5d6bcfd 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,30 @@ +//------------------------------------------------------------------------------------------------// +// // +// : b u i l d . g r a d l e // +// // +//-------------------------------------------------------------------------------------------------- +// +// Audiveris root project. +// +// Sub-projects: +// - schemas +// - windows-installer +// - linux-installer (not yet implemented) +// +//-------------------------------------------------------------------------------------------------- + apply plugin: 'application' project.version = '5.4-alpha' -sourceCompatibility = '1.17' -[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' + +///sourceCompatibility = '1.17' +def minJavaVersion = '17' +sourceCompatibility = JavaLanguageVersion.of(minJavaVersion) + +// Central location for the specific versions of tesseract dependencies +ext.jcppVersion = '1.5.9' +ext.leptVersion = '1.83.0' +ext.tessVersion = '5.3.1' ext.programName = 'Audiveris' ext.programId = 'audiveris' @@ -10,28 +32,38 @@ ext.programVersion = "$project.version" ext.companyName = "$programName Ltd." ext.companyId = "${programName}Ltd" -ext.jcppVersion = '1.5.9' -ext.leptVersion = '1.83.0' -ext.tessVersion = '5.3.1' - -// this code is required in order to adapt values of os.name and os.arch to the -// conventions used by Javacpp's dependencies -ext.targetOSName = System.getProperty('os.name').toLowerCase()\ +/* Name of host OS */ +ext.hostOSName = System.getProperty('os.name').toLowerCase()\ .startsWith('mac os x') ? 'macosx' :\ System.getProperty('os.name').split(' ')[0].toLowerCase() -ext.targetOSArch = ["i386":"x86", "i486":"x86", "i586":"x86", "i686":"x86", "x86":"x86", + +/* Architecture of host OS +* Mapping required to adapt values of os.name and os.arch to the conventions used by Javacpp's dependencies +*/ +ext.hostOSArch = ["i386":"x86", "i486":"x86", "i586":"x86", "i686":"x86", "x86":"x86", "amd64":"x86_64", "x86-64":"x86_64", "x86_64":"x86_64", "arm":"armhf", "aarch64":"arm64"]\ [System.getProperty('os.arch').toLowerCase()] -ext.targetOS = "${project.ext.targetOSName}-${project.ext.targetOSArch}" -println "targetOS=${project.ext.targetOS}" -ext.makensisPath = null - -if (!hasProperty('mainClass')) { +/* Full host OS identification as "name-arch" */ +ext.hostOS = "${project.ext.hostOSName}-${project.ext.hostOSArch}" +println "hostOS=${project.ext.hostOS}" + +/* Name of Java main class. +* The caller may have set the property on the gradle command line via a -PmainClass=foobar +*/ +if (project.hasProperty('mainClass')) { + println "property mainClass:" + project.property('mainClass') + ext.mainClass = project.property('mainClass') +} else { ext.mainClass = ext.programName } -mainClassName = ext.mainClass +application { + mainClass.set("$project.ext.mainClass") +} + +// Perhaps no longer needed? +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' // Useful for turning on deprecation warnings // Just uncomment the appropriate option @@ -42,26 +74,28 @@ allprojects { } } -// To circumvent limitations brought by Jigsaw +// This is needed to circumvent the limitations brought by Java 9 and above (the Jigsaw cuts) applicationDefaultJvmArgs = ["--add-exports=java.desktop/sun.awt.image=ALL-UNNAMED"] +// Run default configuration. +// Heap size (and other stuff) can be modified via a 'jvmLineArgs' on gradle command line run { minHeapSize = '512m' maxHeapSize = '1g' - // Retrieve CLI arguments from cmdLineArgs property if any - if (project.hasProperty("cmdLineArgs")) { - if (cmdLineArgs) { - args(cmdLineArgs.split(',')) - } - } - // Retrieve JVM arguments from jvmLineArgs property if any if (project.hasProperty("jvmLineArgs")) { if (jvmLineArgs) { jvmArgs(jvmLineArgs.split(',')) } } + + // Retrieve CLI arguments from cmdLineArgs property if any + if (project.hasProperty("cmdLineArgs")) { + if (cmdLineArgs) { + args(cmdLineArgs.split(',')) + } + } } repositories { @@ -127,8 +161,8 @@ dependencies { ) runtimeOnly( - [group: 'org.bytedeco', name: 'leptonica', version: "${leptVersion}-${jcppVersion}", classifier: "${project.ext.targetOS}"], - [group: 'org.bytedeco', name: 'tesseract', version: "${tessVersion}-${jcppVersion}", classifier: "${project.ext.targetOS}"] + [group: 'org.bytedeco', name: 'leptonica', version: "${leptVersion}-${jcppVersion}", classifier: "${project.ext.hostOS}"], + [group: 'org.bytedeco', name: 'tesseract', version: "${tessVersion}-${jcppVersion}", classifier: "${project.ext.hostOS}"] ) testImplementation( @@ -143,13 +177,7 @@ configurations { implementationConfig.extendsFrom implementation } -// Specific configurations for specific OS dependencies -['windows-x86', 'windows-x86_64'].each { os -> - configurations.create("runtime-$os") - dependencies.add("runtime-$os", [group: 'org.bytedeco', name: 'leptonica', version: "${leptVersion}-${jcppVersion}", classifier: "$os"]) - dependencies.add("runtime-$os", [group: 'org.bytedeco', name: 'tesseract', version: "${tessVersion}-${jcppVersion}", classifier: "$os"]) -} - +// Generate audiveris.jar jar { // override default output archive name archiveFileName = "audiveris.jar" @@ -172,14 +200,7 @@ jar { } } -// Defining 'debug' task allows to set its arguments later -task(debug, dependsOn: 'classes', type: JavaExec) { - mainClass = mainClassName - classpath = sourceSets.main.runtimeClasspath - debug true -} - -// retrieve the abbreviated hash for the latest commit from Git +// Retrieve the abbreviated hash for the latest commit from Git task "git_build"(type:Exec) { commandLine "git rev-parse --short HEAD".split(' ') standardOutput = new ByteArrayOutputStream() @@ -189,6 +210,7 @@ task "git_build"(type:Exec) { } } +// Generate a 'ProgramId.java' source file, based on Git data task generateProgramId(dependsOn: git_build) { group "build" description "Generates ProgramId source" @@ -203,8 +225,7 @@ task generateProgramId(dependsOn: git_build) { gSrc.append(" * This code has been automatically generated by Gradle.\n */\n") gSrc.append("public abstract class $className {") - ["company_name", "company_id", "program_name", "program_id", "program_version",\ - "program_build"].each { str -> + ["company_name", "company_id", "program_name", "program_id", "program_version", "program_build"].each { str -> def strParts = str.split("_") def propName = strParts[0] + strParts[1].capitalize() gSrc.append("\n /** Precise ${strParts[0]} ${strParts[1]}: {@value} */") @@ -217,86 +238,21 @@ task generateProgramId(dependsOn: git_build) { compileJava.dependsOn("generateProgramId") -// Make sure we have an NSIS compiler available and store it into project.ext.makensisPath -task findNsisCompiler { - description "Find path to NSIS compiler" - onlyIf {"windows" == "${project.ext.targetOSName}"} - - doLast { - // First test on PATH, then on standard program files locations - ["", "C:/Program Files (x86)/NSIS/", "C:/Program Files/NSIS/"].each { prefix -> - if (project.ext.makensisPath == null) { - try { - def path = "${prefix}makensis.exe" - def proc = [path, "/VERSION"].execute() - proc.waitFor() - println "NSIS compiler $path found, version " + proc.in.text.trim() - project.ext.makensisPath = path - } catch (ignored) { - } - } - } - - if (project.ext.makensisPath == null) { - throw new RuntimeException("Cannot find NSIS compiler (makensis.exe)!") - } - } -} - -// All installers -task installers { - group "Installers" - description "Builds all installers" -} - -// Windows installers for 32-bit and 64-bit -['windows-x86', 'windows-x86_64'].each { os -> - task "installer_$os"(type: Exec, dependsOn: [findNsisCompiler, jar, startScripts]) { - group "Installers" - description "Builds installer for $os" - onlyIf {"windows" == "${project.ext.targetOSName}"} - - // Filter line that starts with "CLASSPATH=" or "set CLASSPATH=" findClasspath, - // and update os-based classifier information. - def updateClassPath = { findClasspath, line -> - line.startsWith("${findClasspath}=") ? line.replaceAll("-${project.ext.targetOS}.jar", "-${os}.jar") : line - } - - doFirst { - mkdir "$buildDir/installers" - - // Fresh population of os-dependent folder with bin+lib items - delete "$buildDir/installers/$os" - - // bin: Scripts and icon - copy { - into "$buildDir/installers/$os/bin" - from("$buildDir/scripts/Audiveris") {filter updateClassPath.curry('CLASSPATH') } - from("$buildDir/scripts/Audiveris.bat") {filter updateClassPath.curry('set CLASSPATH')} - from "$projectDir/res/icon-256.ico" - } - - // lib: Libraries - copy { - into "$buildDir/installers/$os/lib" - from "$buildDir/jar" // audiveris.jar - from configurations.implementationConfig // Compile jars - from configurations."runtime-$os" // OS-specific jars - } - - // Set NSIS compiler command line - def suffix = (os == "windows-x86_64")? "" : "32" - commandLine "cmd", "/c",\ - "${project.ext.makensisPath}",\ - "/DPRODUCT_VERSION=$project.version",\ - "/DPROJECT_DIR=$projectDir",\ - "/DTARGET_OS=$os",\ - "/DSUFFIX=$suffix",\ - "$projectDir/dev/installer-windows/Installer.nsi" +// Customization of start scripts to check Java version at run time +tasks.withType(CreateStartScripts) { + doFirst{ + // Copy our own templates while updating THE_MIN_JAVA_VERSION token + copy { + into "$buildDir" + filter { line -> line.replaceAll('THE_MIN_JAVA_VERSION', minJavaVersion) } + from 'dev/scripts/custom-unixStartScript.txt' + from 'dev/scripts/custom-windowsStartScript.txt' } } - installers.dependsOn("installer_$os") + // Make the script generators use our templates + unixStartScriptGenerator.template = resources.text.fromFile("$buildDir/custom-unixStartScript.txt") + windowsStartScriptGenerator.template = resources.text.fromFile("$buildDir/custom-windowsStartScript.txt") } javadoc { @@ -322,14 +278,21 @@ if (JavaVersion.current().isJava8Compatible()) { } } +// Tell gradle to log standard output and error streams when running tests +test { + testLogging.showStandardStreams = true +} + // Ability to include private tasks (such as save) fileTree("$projectDir/private").include('*.gradle').each { file -> apply from: file } -// Tell gradle to log standard output and error streams when running tests -test { - testLogging.showStandardStreams = true +// Defining 'debug' task allows to set its arguments later +task(debug, dependsOn: 'classes', type: JavaExec) { + mainClass = "$project.ext.mainClass" + classpath = sourceSets.main.runtimeClasspath + debug true } // Utility task to print out head templates diff --git a/dev/scripts/custom-unixStartScript.txt b/dev/scripts/custom-unixStartScript.txt new file mode 100644 index 000000000..e8019e899 --- /dev/null +++ b/dev/scripts/custom-unixStartScript.txt @@ -0,0 +1,303 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# ${applicationName} start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh ${applicationName} +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «\$var», «\${var}», «\${var:-default}», «\${var+SET}», +# «\${var#prefix}», «\${var%suffix}», and «\$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "\$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and ${optsEnvironmentVar}) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +#<% /* +# ... and if you're reading this, this IS the template just mentioned. +# +# This template is processed by +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/java/org/gradle/api/internal/plugins/UnixStartScriptGenerator.java +# +# Gradle is a meta-build system used by the project that you're building +# or installing. It's like autoconf but for projects that are written in +# Java and related languages. It's also used to build parts of the Gradle +# project itself. +# +# The Groovy template language is run in two phases. +# +# 1. Any character following \ is passed unmodified through to the +# next phase, while the \ is removed. Any other $ followed by +# varName or {varName} is replaced by the value of that variable. +# +# 2. The result of the first phase is parsed and run in a similar +# manner to JSP or MASON or PHP: anything within < % ... % > is a +# code block, anything else is sent as output, subject to the +# flow imposed by any code segments. +# +# 3. The "output" is a POSIX shell script, which has its own ideas about +# escaping with backslashes, so to get «\» you need to write «\\\\» +# and to get «$» you need to write «\\\$». +# +# For more details about the Groovy Template Engine, see +# https://docs.groovy-lang.org/next/html/documentation/ section §3.15 +# (Template Engines) for details. +# +# (An example invocation of this template is from +# https://github.com/gradle/gradle/blob/HEAD/subprojects/build-init/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java +# within the Gradle project, which builds "gradlew".) +# */ %> +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: \$0 may be a link +app_path=\$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=\${app_path%"\${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "\$app_path" ] +do + ls=\$( ls -ld "\$app_path" ) + link=\${ls#*' -> '} + case \$link in #( + /*) app_path=\$link ;; #( + *) app_path=\$APP_HOME\$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=\${0##*/} +# Discard cd standard output in case \$CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=\$( cd "\${APP_HOME:-./}${appHomeRelativePath}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "\$*" +} >&2 + +die () { + echo + echo "\$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "\$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$classpath +<% if ( mainClassName.startsWith('--module ') ) { %> +MODULE_PATH=$modulePath +<% } %> + +# Determine the Java command to use to start the JVM. +if [ -n "\$JAVA_HOME" ] ; then + if [ -x "\$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=\$JAVA_HOME/jre/sh/java + else + JAVACMD=\$JAVA_HOME/bin/java + fi + if [ ! -x "\$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: \$JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "\$cygwin" && ! "\$darwin" && ! "\$nonstop" ; then + case \$MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=\$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case \$MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "\$MAX_FD" || + warn "Could not set maximum file descriptor limit to \$MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and ${optsEnvironmentVar} environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "\$cygwin" || "\$msys" ; then + APP_HOME=\$( cygpath --path --mixed "\$APP_HOME" ) + CLASSPATH=\$( cygpath --path --mixed "\$CLASSPATH" ) +<% if ( mainClassName.startsWith('--module ') ) { %> MODULE_PATH=\$( cygpath --path --mixed "\$MODULE_PATH" )<% } %> + JAVACMD=\$( cygpath --unix "\$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case \$arg in #( + -*) false ;; # don't mess with options #( + /?*) t=\${arg#/} t=/\${t%%/*} # looks like a POSIX filepath + [ -e "\$t" ] ;; #( + *) false ;; + esac + then + arg=\$( cygpath --path --ignore --mixed "\$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "\$@" "\$arg" # push replacement arg + done +fi + +# Start script customization --- + +# Actual value for min_java_version is to be provided by script post-processing +min_java_version="THE_MIN_JAVA_VERSION" +java_version=\$("\$JAVACMD" -version 2>&1 | head -1 | cut -d'"' -f2 | sed 's/^1\\.//' | cut -d'.' -f1) + +if [ \$java_version -lt \$min_java_version ] +then + die "Java \$min_java_version or higher is required. You have version \$java_version" +fi + +# Stop script customization --- + +<% /* +# The DEFAULT_JVM_OPTS variable is intentionally defined here to allow using cygwin-processed APP_HOME. +# So far the only way to inject APP_HOME reference into DEFAULT_JVM_OPTS is to post-process the start script; the declaration is a good anchor to do that. +*/ %> +# Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script. +DEFAULT_JVM_OPTS=${defaultJvmOpts} + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect \${Hostname} to be expanded, as it is an environment variable and will be +# treated as '\${Hostname}' itself on the command line. + +set -- \\ +<% if ( appNameSystemProperty ) { + %> "-D${appNameSystemProperty}=\$APP_BASE_NAME" \\ +<% } %> -classpath "\$CLASSPATH" \\ +<% if ( mainClassName.startsWith('--module ') ) { + %> --module-path "\$MODULE_PATH" \\ +<% } %> ${mainClassName} \\ + "\$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"\$var" ) && +# set -- "\${ARGS[@]}" "\$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- \$( + printf '%s\\n' "\$DEFAULT_JVM_OPTS \$JAVA_OPTS \$${optsEnvironmentVar}" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' | + tr '\\n' ' ' + )" '"\$@"' + +exec "\$JAVACMD" "\$@" diff --git a/dev/scripts/custom-windowsStartScript.txt b/dev/scripts/custom-windowsStartScript.txt new file mode 100644 index 000000000..9a3159df2 --- /dev/null +++ b/dev/scripts/custom-windowsStartScript.txt @@ -0,0 +1,116 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem ${applicationName} startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=.\ + +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME%${appHomeRelativePath} + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script. +set DEFAULT_JVM_OPTS=${defaultJvmOpts} + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute + +@rem Start script customization --- + +@rem Actual value for min_java_version is to be provided by script post-processing +set /a min_java_version=THE_MIN_JAVA_VERSION + +for /f tokens^=2-5^ delims^=.-_^" %%j in ('"%JAVA_EXE%" -fullversion 2^>^&1') do ( + set "full_version=%%j.%%k.%%l-%%m" + set "version=%%j" +) + +if %version% LSS %min_java_version% ( + echo WARNING: + echo WARNING: Current Java version %version% is lower than required %min_java_version% + echo WARNING: Please install Java version %min_java_version% or higher + echo WARNING: +@rem start cmd /c "@echo off & mode con cols=50 lines=10 & echo // & echo // Audiveris WARNING: & echo // & echo // Your Java version is %version% (%full_version%) & echo // Please, install Java %min_java_version% or above. & echo // & pause" + pause + goto fail +) + +@rem Stop script customization --- + +@rem Setup the command line + +set CLASSPATH=$classpath +<% if ( mainClassName.startsWith('--module ') ) { %>set MODULE_PATH=$modulePath<% } %> + +@rem Execute ${applicationName} +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %${optsEnvironmentVar}% <% if ( appNameSystemProperty ) { %>"-D${appNameSystemProperty}=%APP_BASE_NAME%"<% } %> -classpath "%CLASSPATH%" <% if ( mainClassName.startsWith('--module ') ) { %>--module-path "%MODULE_PATH%" <% } %>${mainClassName} %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable ${exitEnvironmentVar} if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%${exitEnvironmentVar}%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/dev/tessdata/deu.traineddata b/dev/tessdata/deu.traineddata new file mode 100644 index 000000000..36f623a0b Binary files /dev/null and b/dev/tessdata/deu.traineddata differ diff --git a/dev/tessdata/eng.traineddata b/dev/tessdata/eng.traineddata new file mode 100644 index 000000000..f4744c201 Binary files /dev/null and b/dev/tessdata/eng.traineddata differ diff --git a/dev/tessdata/fra.traineddata b/dev/tessdata/fra.traineddata new file mode 100644 index 000000000..250c7749b Binary files /dev/null and b/dev/tessdata/fra.traineddata differ diff --git a/dev/tessdata/ita.traineddata b/dev/tessdata/ita.traineddata new file mode 100644 index 000000000..92ef30079 Binary files /dev/null and b/dev/tessdata/ita.traineddata differ diff --git a/settings.gradle b/settings.gradle index 692ad76a1..f0a7a4668 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,3 @@ rootProject.name = 'Audiveris' -include 'schemas' \ No newline at end of file +include 'schemas' +include 'windows-installer' \ No newline at end of file diff --git a/src/main/org/audiveris/omr/text/tesseract/TesseractOCR.java b/src/main/org/audiveris/omr/text/tesseract/TesseractOCR.java index 595b593b5..09ce6f448 100644 --- a/src/main/org/audiveris/omr/text/tesseract/TesseractOCR.java +++ b/src/main/org/audiveris/omr/text/tesseract/TesseractOCR.java @@ -123,17 +123,18 @@ private Path findOcrFolder () } } - // Second, scan OS typical TESSDATA locations + // Second, scan OS typical TESSDATA locations, beginning by user Audiveris config folder if (WellKnowns.WINDOWS) { return scanOcrLocations(new String[] { + WellKnowns.CONFIG_FOLDER.toString(), Paths.get(System.getenv("ProgramFiles")).resolve("tesseract-ocr").toString(), Paths.get(System.getenv("ProgramFiles(x86)")).resolve("tesseract-ocr") - .toString() // - }); + .toString() }); } else if (WellKnowns.LINUX) { return scanOcrLocations(new String[] { + WellKnowns.CONFIG_FOLDER.toString(), "/usr/share/tesseract-ocr", // Debian, Ubuntu and derivatives "/usr/share", // OpenSUSE "/usr/share/tesseract" // Fedora @@ -141,6 +142,7 @@ private Path findOcrFolder () } else if (WellKnowns.MAC_OS_X) { return scanOcrLocations(new String[] { + WellKnowns.CONFIG_FOLDER.toString(), "/opt/local/share", // Macports "/usr/local/opt/tesseract/share" // Homebrew }); @@ -240,6 +242,7 @@ public Path getOcrFolder () if (!OCR_FOLDER_SEARCHED) { OCR_FOLDER_SEARCHED = true; OCR_FOLDER = findOcrFolder(); + logger.info("OCR folder: {}", OCR_FOLDER); } return OCR_FOLDER; diff --git a/windows-installer/build.gradle b/windows-installer/build.gradle new file mode 100644 index 000000000..07bae99f0 --- /dev/null +++ b/windows-installer/build.gradle @@ -0,0 +1,130 @@ +//------------------------------------------------------------------------------------------------// +// // +// w i n d o w s - i n s t a l l e r : b u i l d . g r a d l e // +// // +//-------------------------------------------------------------------------------------------------- +// +// This 'windows-installer' sub-project is based on the NSIS compiler. +// +// It was part of the root 'audiveris' project and is now a sub-project for the sake of modularity +// +// Improvements: +// +// 1/ Correct handling of Tesseract language data files, to bundle a few languages by default +// +// 2/ Run-time test of Java version, to warn the user via a persistent dialog. +// (this is actually performed via the startScripts task of the root project) +// +//-------------------------------------------------------------------------------------------------- + +apply plugin: 'base' + +def rootDir = project(":").projectDir +def hostOS = project(":").property('hostOS') +def hostOSName = project(":").property('hostOSName') +def rootVersion = project(":").property('version') + +repositories { + mavenLocal() + mavenCentral() +} + +// Configurations for specific OS dependencies +['windows-x86', 'windows-x86_64'].each { os -> + configurations.create("runtime-$os") + dependencies.add("runtime-$os", [group: 'org.bytedeco', name: 'leptonica', version: "${leptVersion}-${jcppVersion}", classifier: "$os"]) + dependencies.add("runtime-$os", [group: 'org.bytedeco', name: 'tesseract', version: "${tessVersion}-${jcppVersion}", classifier: "$os"]) +} + +// Make sure we have an NSIS compiler available and store it into project.ext.makensisPath + +ext.makensisPath = null + +task findNsisCompiler { + description "Find path to NSIS compiler" + onlyIf {"windows" == "$hostOSName"} + + doLast { + // First test on PATH, then on standard program files locations + ["", "C:/Program Files (x86)/NSIS/", "C:/Program Files/NSIS/"].each { prefix -> + if (project.ext.makensisPath == null) { + try { + def path = "${prefix}makensis.exe" + def proc = [path, "/VERSION"].execute() + proc.waitFor() + println "NSIS compiler $path found, version " + proc.in.text.trim() + project.ext.makensisPath = path + } catch (ignored) { + } + } + } + + if (project.ext.makensisPath == null) { + throw new RuntimeException("Cannot find NSIS compiler (makensis.exe)!") + } + } +} + +// All installers +task installers { + group "Installers" + description "Builds all installers" +} + +// Windows installers for 32-bit and 64-bit targetted OSes +['windows-x86', 'windows-x86_64'].each { targetOS -> + task "installer_$targetOS"(type: Exec) { + dependsOn findNsisCompiler + dependsOn project(":").jar + dependsOn project(":").startScripts + group "Installers" + description "Builds installer for $targetOS" + onlyIf {"windows" == "$hostOSName"} + + // Filter line that starts with "CLASSPATH=" or "set CLASSPATH=" findClasspath, + // and update targetOS-based classifiers. + def updateClassPath = { findClasspath, line -> + line.startsWith("${findClasspath}=") ? line.replaceAll("-${hostOS}.jar", "-${targetOS}.jar") : line + } + + doFirst { + mkdir "$buildDir/installers" + + // Fresh population of targetOS-dependent folder with bin+lib items + delete "$buildDir/installers/$targetOS" + + // bin: Scripts and icon + copy { + into "$buildDir/installers/$targetOS/bin" + + from("$rootDir/build/scripts/Audiveris") {filter updateClassPath.curry('CLASSPATH') } + from("$rootDir/build/scripts/Audiveris.bat") {filter updateClassPath.curry('set CLASSPATH')} + from "$rootDir/res/icon-256.ico" + } + + // lib: Libraries + copy { + into "$buildDir/installers/$targetOS/lib" + + from "$rootDir/build/jar" // audiveris.jar + from project(":").configurations.implementationConfig // Compile jars + from configurations."runtime-$targetOS" // targetOS-specific jars + } + + // Set NSIS compiler command line + def suffix = (targetOS == "windows-x86_64")? "" : "32" + commandLine "cmd", "/c",\ + "${project.ext.makensisPath}",\ + "/DPRODUCT_VERSION=$rootVersion",\ + "/DPROJECT_DIR=$projectDir",\ + "/DICON=$rootDir/res/icon-256.ico",\ + "/DLICENSE=$rootDir/LICENSE",\ + "/DTARGET_OS=$targetOS",\ + "/DTESSDATA_SOURCE=$rootDir/dev",\ + "/DSUFFIX=$suffix",\ + "$projectDir/dev/Installer.nsi" + } + } + + installers.dependsOn("installer_$targetOS") +} diff --git a/windows-installer/dev/FileAssociationWithIcon.nsh b/windows-installer/dev/FileAssociationWithIcon.nsh new file mode 100644 index 000000000..57b76337d --- /dev/null +++ b/windows-installer/dev/FileAssociationWithIcon.nsh @@ -0,0 +1,202 @@ +/* +_____________________________________________________________________________ + + File Association With Icon +_____________________________________________________________________________ + + Based on code taken from http://nsis.sourceforge.net/File_Association + + Added icon as a 4th parameter in RegisterExtension, since the executable may + not always be a .exe, and thus we need a separate way to pass the precise + icon container, be it a .exe, a .dll, a .ico, etc, + optionally with an icon index within the icon container. + + Usage in script: + 1. !include "FileAssociation.nsh" + 2. [Section|Function] + ${FileAssociationFunction} "Param1" "Param2" "..." $var + [SectionEnd|FunctionEnd] + + FileAssociationFunction=[RegisterExtension|UnRegisterExtension] + +_____________________________________________________________________________ + + ${RegisterExtension} "[executable]" "[extension]" "[description]" "[icon]" + +"[executable]" ; executable which opens the file format + ; +"[extension]" ; extension, which represents the file format to open + ; +"[description]" ; description for the extension. This will be display in Windows Explorer. + ; +"[icon]" ; icon container[,index]. + ; + + + ${UnRegisterExtension} "[extension]" "[description]" + +"[extension]" ; extension, which represents the file format to open + ; +"[description]" ; description for the extension. This will be display in Windows Explorer. + ; + +_____________________________________________________________________________ + + Macros +_____________________________________________________________________________ + + Change log window verbosity (default: 3=no script) + + Example: + !include "FileAssociation.nsh" + !insertmacro RegisterExtension + ${FileAssociation_VERBOSE} 4 # all verbosity + !insertmacro UnRegisterExtension + ${FileAssociation_VERBOSE} 3 # no script +*/ + + +!ifndef FileAssociation_INCLUDED +!define FileAssociation_INCLUDED + +!include Util.nsh + +!verbose push +!verbose 3 +!ifndef _FileAssociation_VERBOSE + !define _FileAssociation_VERBOSE 3 +!endif +!verbose ${_FileAssociation_VERBOSE} +!define FileAssociation_VERBOSE `!insertmacro FileAssociation_VERBOSE` +!verbose pop + +!macro FileAssociation_VERBOSE _VERBOSE + !verbose push + !verbose 3 + !undef _FileAssociation_VERBOSE + !define _FileAssociation_VERBOSE ${_VERBOSE} + !verbose pop +!macroend + + + +!macro RegisterExtensionCall _EXECUTABLE _EXTENSION _DESCRIPTION _ICON + !verbose push + !verbose ${_FileAssociation_VERBOSE} + Push `${_ICON}` + Push `${_DESCRIPTION}` + Push `${_EXTENSION}` + Push `${_EXECUTABLE}` + ${CallArtificialFunction} RegisterExtension_ + !verbose pop +!macroend + +!macro UnRegisterExtensionCall _EXTENSION _DESCRIPTION + !verbose push + !verbose ${_FileAssociation_VERBOSE} + Push `${_EXTENSION}` + Push `${_DESCRIPTION}` + ${CallArtificialFunction} UnRegisterExtension_ + !verbose pop +!macroend + + + +!define RegisterExtension `!insertmacro RegisterExtensionCall` +!define un.RegisterExtension `!insertmacro RegisterExtensionCall` + +!macro RegisterExtension +!macroend + +!macro un.RegisterExtension +!macroend + +!macro RegisterExtension_ + !verbose push + !verbose ${_FileAssociation_VERBOSE} + + Exch $R3 ;exe + Exch + Exch $R2 ;ext + Exch + Exch 2 + Exch $R1 ;desc + Exch 2 + Exch 3 + Exch $R0 ;icon + Exch 3 + Push $0 + Push $1 + + ReadRegStr $1 HKCR $R2 "" ; read current file association + StrCmp "$1" "" NoBackup ; is it empty + StrCmp "$1" "$R1" NoBackup ; is it our own + WriteRegStr HKCR $R2 "backup_val" "$1" ; backup current value +NoBackup: + WriteRegStr HKCR $R2 "" "$R1" ; set our file association + + ReadRegStr $0 HKCR $R1 "" + StrCmp $0 "" 0 Skip + WriteRegStr HKCR "$R1" "" "$R1" + WriteRegStr HKCR "$R1\shell" "" "open" + WriteRegStr HKCR "$R1\DefaultIcon" "" "$R0" ; set icon +Skip: + WriteRegStr HKCR "$R1\shell\open\command" "" '"$R3" "%1"' + WriteRegStr HKCR "$R1\shell\edit" "" "Edit $R1" + WriteRegStr HKCR "$R1\shell\edit\command" "" '"$R3" "%1"' + + Pop $1 + Pop $0 + Pop $R3 + Pop $R2 + Pop $R1 + Pop $R0 + + !verbose pop +!macroend + + + +!define UnRegisterExtension `!insertmacro UnRegisterExtensionCall` +!define un.UnRegisterExtension `!insertmacro UnRegisterExtensionCall` + +!macro UnRegisterExtension +!macroend + +!macro un.UnRegisterExtension +!macroend + +!macro UnRegisterExtension_ + !verbose push + !verbose ${_FileAssociation_VERBOSE} + + Exch $R1 ;desc + Exch + Exch $R0 ;ext + Exch + Push $0 + Push $1 + + ReadRegStr $1 HKCR $R0 "" + StrCmp $1 $R1 0 NoOwn ; only do this if we own it + ReadRegStr $1 HKCR $R0 "backup_val" + StrCmp $1 "" 0 Restore ; if backup="" then delete the whole key + DeleteRegKey HKCR $R0 + Goto NoOwn + +Restore: + WriteRegStr HKCR $R0 "" $1 + DeleteRegValue HKCR $R0 "backup_val" + DeleteRegKey HKCR $R1 ;Delete key with association name settings + +NoOwn: + + Pop $1 + Pop $0 + Pop $R1 + Pop $R0 + + !verbose pop +!macroend + +!endif # !FileAssociation_INCLUDED diff --git a/windows-installer/dev/Installer.nsi b/windows-installer/dev/Installer.nsi new file mode 100644 index 000000000..9af6f5a01 --- /dev/null +++ b/windows-installer/dev/Installer.nsi @@ -0,0 +1,189 @@ +; Script generated by the HM NIS Edit Script Wizard. + +; Expected symbols +; ---------------- +; PRODUCT_VERSION: Product version +; PROJECT_DIR: Path to sub-project directory +; ICON: Path to application icon +; LICENSE: Path to application license +; TARGET_OS: OS name and architecture (windows-x86 or windows-x86_64) +; SUFFIX: Optional suffix ("32" or "") to cope with 32/64 coexisting installations +; TESSDATA_SOURCE Tessdata source folder (to be read from on host machine) +!ifndef SUFFIX + !define SUFFIX "" +!endif + +; HM NIS Edit Wizard helper defines +!define PRODUCT_NAME "Audiveris" +!define PRODUCT_PUBLISHER "Audiveris Team" +!define PRODUCT_WEB_SITE "http://www.audiveris.org" +!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}${SUFFIX}" +!define PRODUCT_UNINST_ROOT_KEY "HKLM" +!define PRODUCT_STARTMENU_REGVAL "NSIS:StartMenuDir" + +!include "LogicLib.nsh" + +; MUI 1.67 compatible ------ +!include "MUI.nsh" + +; code derived from "http://nsis.sourceforge.net/File_Association" +!include "FileAssociationWithIcon.nsh" + +; MUI Settings +!define MUI_ABORTWARNING +!define MUI_ICON "${ICON}" +!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico" + +; Welcome page +!insertmacro MUI_PAGE_WELCOME + +; License page +!insertmacro MUI_PAGE_LICENSE "${LICENSE}" + +; Directory page +!insertmacro MUI_PAGE_DIRECTORY + +; Start menu page +var ICONS_GROUP +!define MUI_STARTMENUPAGE_NODISABLE +!define MUI_STARTMENUPAGE_DEFAULTFOLDER "Audiveris${SUFFIX}" +!define MUI_STARTMENUPAGE_REGISTRY_ROOT "${PRODUCT_UNINST_ROOT_KEY}" +!define MUI_STARTMENUPAGE_REGISTRY_KEY "${PRODUCT_UNINST_KEY}" +!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "${PRODUCT_STARTMENU_REGVAL}" +!insertmacro MUI_PAGE_STARTMENU Application $ICONS_GROUP + +; Instfiles page +!insertmacro MUI_PAGE_INSTFILES + +; Finish page +!define MUI_FINISHPAGE_RUN "$INSTDIR\bin\Audiveris.bat" +!define MUI_FINISHPAGE_RUN_TEXT "Launch Audiveris" +!insertmacro MUI_PAGE_FINISH + +; Uninstaller pages +!insertmacro MUI_UNPAGE_INSTFILES + +; Language files +!insertmacro MUI_LANGUAGE "English" + +; MUI end ------ + +Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" +OutFile "${PROJECT_DIR}\build\installers\Audiveris_Setup-${PRODUCT_VERSION}-${TARGET_OS}.exe" +ShowInstDetails show +ShowUnInstDetails show + +Function .onInit + ${If} $INSTDIR == "" ; /D= was not used on the command line + ${If} "${SUFFIX}" == "32" + StrCpy $INSTDIR "$PROGRAMFILES\Audiveris${SUFFIX}" + ${Else} + StrCpy $INSTDIR "$PROGRAMFILES64\Audiveris${SUFFIX}" + ${EndIf} + ${EndIf} +FunctionEnd + +Section "Hauptgruppe" SEC01 + SetOutPath "$INSTDIR" + SetOverwrite ifnewer + File /r "${PROJECT_DIR}\build\installers\${TARGET_OS}\*" + + ; Pre-populate a few Tesseract language files + ; Source is the Audiveris dev/tessdata folder on host machine + ; Target is a tessdata folder in user config folder on target machine + SetOutPath "$AppData\AudiverisLtd\audiveris\config\tessdata" + SetOverwrite on + File /r "${TESSDATA_SOURCE}\tessdata\*.traineddata" + + ; Shortcuts + !insertmacro MUI_STARTMENU_WRITE_BEGIN Application + !insertmacro MUI_STARTMENU_WRITE_END + + MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON1 "Do you want to associate '.omr' file extension with Audiveris?" IDNO noAsso + ${registerExtension} "$INSTDIR\bin\${PRODUCT_NAME}.bat" ".omr" "OpticalMusicRecognition_File" "$INSTDIR\bin\icon-256.ico" + noAsso: +SectionEnd + +Section -AdditionalIcons + !insertmacro MUI_STARTMENU_WRITE_BEGIN Application + WriteIniStr "$INSTDIR\${PRODUCT_NAME}.url" "InternetShortcut" "URL" "${PRODUCT_WEB_SITE}" + CreateDirectory "$SMPROGRAMS\$ICONS_GROUP" + CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\Audiveris.lnk" "$INSTDIR\bin\${PRODUCT_NAME}.bat" "" "$INSTDIR\bin\icon-256.ico" + CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\Website.lnk" "$INSTDIR\${PRODUCT_NAME}.url" + CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\Uninstall.lnk" "$INSTDIR\uninst.exe" + !insertmacro MUI_STARTMENU_WRITE_END +SectionEnd + +Section -Post + WriteUninstaller "$INSTDIR\uninst.exe" + WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)" + WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe" + WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}" + WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}" + WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}" +SectionEnd + + +Function un.onUninstSuccess + HideWindow + MessageBox MB_ICONINFORMATION|MB_OK "$(^Name) has been properly uninstalled." +FunctionEnd + +Function un.onInit + MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Do you want to uninstall $(^Name)?" IDYES +2 + Abort + MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Do you want to keep the configuration data?" IDYES +2 + RmDir /r "$AppData\AudiverisLtd\audiveris" +FunctionEnd + +!macro BadPathsCheck +StrCpy $R0 $INSTDIR "" -2 +StrCmp $R0 ":\" bad +StrCpy $R0 $INSTDIR "" -14 +StrCmp $R0 "\Program Files" bad +StrCpy $R0 $INSTDIR "" -19 +StrCmp $R0 "\Program Files(x86)" bad +StrCpy $R0 $INSTDIR "" -8 +StrCmp $R0 "\Windows" bad +StrCpy $R0 $INSTDIR "" -6 +StrCmp $R0 "\WinNT" bad +StrCpy $R0 $INSTDIR "" -9 +StrCmp $R0 "\system32" bad +StrCpy $R0 $INSTDIR "" -8 +StrCmp $R0 "\Desktop" bad +StrCpy $R0 $INSTDIR "" -23 +StrCmp $R0 "\Documents and Settings" bad +StrCpy $R0 $INSTDIR "" -13 +StrCmp $R0 "\My Documents" bad done +bad: + MessageBox MB_OK|MB_ICONSTOP "Install path invalid!" + Abort +done: +!macroend + +Section Uninstall + ; Check that the uninstall isn't dangerous. + !insertmacro BadPathsCheck + + ; Delete installed software + Delete "$INSTDIR\${PRODUCT_NAME}.url" + Delete "$INSTDIR\uninst.exe" + Delete "$INSTDIR\lib\*" + Delete "$INSTDIR\bin\*" + RMDir "$INSTDIR\lib" + RMDir "$INSTDIR\bin" + RMDir "$INSTDIR" + + ; Retrieve start menu folder from registry then delete folder and registry key + !insertmacro MUI_STARTMENU_GETFOLDER Application $ICONS_GROUP + Delete "$SMPROGRAMS\$ICONS_GROUP\Uninstall.lnk" + Delete "$SMPROGRAMS\$ICONS_GROUP\Website.lnk" + Delete "$SMPROGRAMS\$ICONS_GROUP\Audiveris.lnk" + RMDir "$SMPROGRAMS\$ICONS_GROUP" + DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" + + ${unregisterExtension} ".omr" "OpticalMusicRecognition_File" + DeleteRegKey HKCR "OpticalMusicRecognition_File" ; brute force delete + + SetAutoClose true +SectionEnd \ No newline at end of file