diff --git a/.gitignore b/.gitignore
index a157fa6..aadfbbd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
/build
+/install
/.idea
/src/.idea
/src/Pipfile.lock
diff --git a/Jenkinsfile b/Jenkinsfile
index c724079..842c263 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -11,6 +11,7 @@ import com.esri.zrh.jenkins.ce.CityEnginePipelineLibrary
import com.esri.zrh.jenkins.ce.PrtAppPipelineLibrary
import com.esri.zrh.jenkins.PslFactory
import com.esri.zrh.jenkins.psl.UploadTrackingPsl
+import com.esri.zrh.jenkins.ToolInfo
@Field def psl = PslFactory.create(this, UploadTrackingPsl.ID)
@Field def cepl = new CityEnginePipelineLibrary(this, psl)
@@ -46,6 +47,8 @@ import com.esri.zrh.jenkins.psl.UploadTrackingPsl
[ os: cepl.CFG_OS_WIN10, bc: cepl.CFG_BC_REL, tc: cepl.CFG_TC_VC1427, cc: cepl.CFG_CC_OPT, arch: cepl.CFG_ARCH_X86_64, houdini: '19.5' ],
]
+@Field final List INSTALLER_CONFIG = [ [ os: cepl.CFG_OS_WIN10, bc: cepl.CFG_BC_REL, tc: cepl.CFG_TC_VC1427, cc: cepl.CFG_CC_OPT, arch: cepl.CFG_ARCH_X86_64 ] ]
+@Field final List INSTALLER_HOUDINI_VERS = [ '18.5', '19.0', '19.5' ]
// -- SETUP
@@ -68,6 +71,10 @@ stage('build') {
cepl.runParallel(taskGenBuild())
}
+stage('installer') {
+ cepl.runParallel(taskGenInstaller())
+}
+
papl.finalizeRun('palladio', env.BRANCH_NAME)
@@ -93,6 +100,12 @@ Map taskGenBuild() {
return tasks;
}
+Map taskGenInstaller() {
+ Map tasks = [:]
+ tasks << cepl.generateTasks('pld-msi', this.&taskCreateInstaller, INSTALLER_CONFIG)
+ return tasks;
+}
+
// -- TASK BUILDERS
@@ -106,7 +119,7 @@ def taskRunTest(cfg) {
cepl.cleanCurrentDir()
unstash(name: SOURCE_STASH)
dir(path: 'build') {
- papl.runCMakeBuild(SOURCE, 'build_and_run_tests', cfg, [])
+ papl.runCMakeBuild(SOURCE, 'build_and_run_tests', cfg, [], JenkinsTools.CMAKE319)
}
junit('build/test/palladio_test_report.xml')
}
@@ -121,7 +134,18 @@ def taskBuildPalladio(cfg) {
cepl.cleanCurrentDir()
unstash(name: SOURCE_STASH)
dir(path: 'build') {
- papl.runCMakeBuild(SOURCE, BUILD_TARGET, cfg, defs)
+ papl.runCMakeBuild(SOURCE, BUILD_TARGET, cfg, defs, JenkinsTools.CMAKE319)
+ }
+ final String artifactPattern = "palladio-*"
+
+ if(cfg.os == cepl.CFG_OS_WIN10){
+ dir(path: 'build'){
+ stashFile = cepl.findOneFile(artifactPattern)
+ echo("stashFile: '${stashFile}'")
+ stash(includes: artifactPattern, name: "houdini${cfg.houdini.replace('.', '_')}")
+ stashPath = "${stashFile.path}"
+ echo("file path to stash: '${stashPath}'")
+ }
}
def versionExtractor = { p ->
@@ -132,5 +156,51 @@ def taskBuildPalladio(cfg) {
def cls = (p =~ /.*palladio-.*\.(hdn.*)-(windows|linux)\..*/)
return cls[0][1] + '.' + cepl.getArchiveClassifier(cfg)
}
- papl.publish('palladio', env.BRANCH_NAME, "palladio-*", versionExtractor, cfg, classifierExtractor)
+ papl.publish('palladio', env.BRANCH_NAME, artifactPattern, versionExtractor, cfg, classifierExtractor)
}
+
+def taskCreateInstaller(cfg) {
+ final String appName = 'palladio-installer'
+ cepl.cleanCurrentDir()
+ unstash(name: SOURCE_STASH)
+
+ String pyArgs = ""
+
+ // fetch outputs from builds
+ dir(path: 'build'){
+ dir(path: 'tmp'){
+ INSTALLER_HOUDINI_VERS.each { mv ->
+ unstash(name: "houdini${mv.replace('.', '_')}")
+ def zipFile = cepl.findOneFile("*hdn${mv.replace('.', '-')}*.zip")
+ final String zipFileName = zipFile.name
+ unzip(zipFile:zipFileName)
+ pyArgs += "-h${mv.replace('.', '')} \"build\\tmp\\${zipFileName.take(zipFileName.lastIndexOf('.'))}\" "
+ }
+ }
+ }
+ pyArgs += "-bv ${env.BUILD_NUMBER} "
+ pyArgs += "-bd \"build\\build_msi\" "
+
+ // Toolchain definition for building MSI installers.
+ final JenkinsTools compiler = cepl.getToolchainTool(cfg)
+ final def toolchain = [
+ new ToolInfo(JenkinsTools.WIX, cfg),
+ new ToolInfo(compiler, cfg)
+ ]
+
+ // Setup environment according to above toolchain definition.
+ withTool(toolchain) {
+ psl.runCmd('python "palladio.git\\deploy\\build.py" ' + pyArgs)
+
+ def buildProps = papl.jpe.readProperties(file: 'build/build_msi/deployment.properties')
+
+ final String artifactPattern = "build_msi/${buildProps.package_file}"
+ final def artifactVersion = { p -> buildProps.package_version }
+
+ def classifierExtractor = { p ->
+ return cepl.getArchiveClassifier(cfg)
+ }
+
+ papl.publish(appName, env.BRANCH_NAME, artifactPattern, artifactVersion, cfg, classifierExtractor)
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 3a91a7b..8d08ce7 100644
--- a/README.md
+++ b/README.md
@@ -243,10 +243,12 @@ It can be useful to put RPKs into an `rpk` sub-directory of your current Houdini
* RedHat Enterprise Linux 7 or 8 and compatible (CentOS, Alma Linux, Rocky Linux, ...)
### Required Toolchain & Compiler
-* [cmake 3.13 or later](https://cmake.org/download)
+* [cmake 3.19 or later](https://cmake.org/download)
* [conan 1.20 or later](https://www.conan.io/downloads)
* Linux: GCC 9.3
* Windows: Visual Studio 2019 (MSVC 14.27)
+* [WiX Toolset 3.11.1 or later](https://wixtoolset.org/docs/wix3/): Optional, required for building .msi installers
+* Python 3.6 or later: Optional, required for building .msi installers
### Required Build Dependencies (Latest Release)
* Installation of Houdini 18.5, 19.0 or 19.5 (see https://sidefx.com/download)
@@ -314,6 +316,16 @@ See [Quick Start](#quick-start) how to launch Houdini with Palladio.
1. `nmake palladio_test`
1. Run `bin\palladio_test`
+### Building Windows MSI installer
+1. Open a MSVC 14.27 x64 shell (Visual Studio 2019) and `cd` to the Palladio git repository
+1. for each houdini version build the binary files to a local install folder:
+ 1. `mkdir build/installer_XXY` (i.e. `installer_195`)
+ 1. `cd build/installer_XXY`
+ 1. `cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="../../install/houdiniXX.Y" ../../src`
+ 1. `nmake install` (the plugin will be installed to `install/houdiniXX.Y`)
+1. From a terminal run `deploy\build.py` and provide the binary folders via `-hXXY "install/houdiniXX.Y"`
+1. The MSI installer should now be located in `build/build_msi/`
+
## Release Notes
### v2.0.0 Beta 1 (Okt 13, 2022)
@@ -328,6 +340,7 @@ Required CityEngine version: 2022.0 or older.
- CGA annotations will create different UI widgets, for example a color picker or file browser
- Seed parameter to change the seed value in the `pldAssign` node
- Icon to Palladio nodes
+- MSI windows installer
#### Changed:
- Updated Procedural Runtime (PRT) to 2.6.8300 (corresponds to CityEngine 2022.0)
@@ -337,6 +350,7 @@ Required CityEngine version: 2022.0 or older.
#### Development:
- Updated compilers (now using C++ 17)
+- Updated required cmake version to 3.19
- Linux version is now compiled with GCC 9.3 by default
- Updated test framework and build system
- Cleaned up CMake files
diff --git a/deploy/build.py b/deploy/build.py
new file mode 100644
index 0000000..8126ff3
--- /dev/null
+++ b/deploy/build.py
@@ -0,0 +1,241 @@
+import argparse
+import glob
+import subprocess
+import json
+import shutil
+import os
+from pathlib import Path
+from string import Template
+
+# Dirs and Paths
+SCRIPT_DIR = Path(__file__).resolve().parent
+CFG_PATH = SCRIPT_DIR / 'installer_config.json'
+RESOURCE_DIR = SCRIPT_DIR / 'resources'
+
+VERSION_JSON_PATH = SCRIPT_DIR / '../version.json'
+PACKAGE_JSON_PATH = SCRIPT_DIR / 'palladio.json'
+
+DEFAULT_BUILD_DIR = SCRIPT_DIR / '../build/build_msi'
+
+TEMPLATE_DIR = SCRIPT_DIR / 'templates'
+DEPLOYMENT_PROPERTIES_TEMPLATE_PATH = TEMPLATE_DIR / 'deployment.properties.in'
+
+WIX_TEMPLATE_PATH = TEMPLATE_DIR / 'palladio.wxs.in'
+WIX_DIRECTORY_TEMPLATE_PATH = TEMPLATE_DIR / 'palladio_directory.in'
+WIX_FEATURE_TEMPLATE_PATH = TEMPLATE_DIR / 'palladio_feature.in'
+
+PROJECT_NAME = 'Palladio'
+PLATFORM = 'win64'
+PACKAGE_VENDOR_ESCAPED = 'Esri R&D Center Zurich'
+
+
+def get_palladio_version_string(includePre=False, build_version=''):
+ version_map = {}
+ delimiter = '.'
+ with open(VERSION_JSON_PATH, 'r') as version_file:
+ version_map = json.load(version_file)
+ version_string = version_map['PLD_VERSION_MAJOR'] + delimiter + \
+ version_map['PLD_VERSION_MINOR'] + \
+ delimiter + version_map['PLD_VERSION_PATCH']
+ if includePre:
+ version_string += version_map['PLD_VERSION_PRE']
+ if len(build_version) > 0:
+ version_string += '+b' + build_version
+ return version_string
+
+
+def gen_installer_filename(build_version):
+ return PROJECT_NAME + '-installer-' + get_palladio_version_string(True, build_version) + '.msi'
+
+
+def rel_to_os_dir(path):
+ return Path(path).absolute()
+
+
+def clear_folder(path):
+ if path.exists():
+ shutil.rmtree(path)
+ os.makedirs(path)
+
+
+def run_wix_command(cmd, build_dir):
+ process = subprocess.run(args=[str(i) for i in cmd], cwd=build_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ universal_newlines=True)
+ print(process.args)
+ print(process.stdout)
+ print(process.stderr)
+
+
+def wix_harvest(houdini_version, binaries_install_dir, build_dir):
+ output_path = build_dir / f'houdini{houdini_version}.wxs'
+ wxs_file = output_path
+ wxs_var = f'-dPalladioInstallComponentDir{houdini_version}={binaries_install_dir}'
+ heat_cmd = ['heat', 'dir', binaries_install_dir, '-dr', f'PalladioInstallComponentDir{houdini_version}', '-ag',
+ '-cg', f'PalladioInstallComponent{houdini_version}', '-sfrag', '-template fragment', '-var', f'wix.PalladioInstallComponentDir{houdini_version}', '-srd', '-sreg', '-o', output_path]
+
+ run_wix_command(heat_cmd, build_dir)
+
+ return wxs_file, wxs_var
+
+
+def wix_compile(sources, vars, build_dir):
+ candle_cmd = ['candle', '-arch', 'x64',
+ build_dir / 'palladio.wxs'] + sources + vars
+ run_wix_command(candle_cmd, build_dir)
+
+ objects = glob.glob(str(build_dir / '*.wixobj'))
+ return objects
+
+
+def wix_link(objects, vars, build_dir, installer_filename):
+ light_cmd = ['light', f'-dWixUILicenseRtf={SCRIPT_DIR}\\resources\\license.rtf', '-dInstallDir=', '-ext', 'WixUIExtension',
+ '-cultures:en-us', '-ext', 'WixUtilExtension', '-o', f'{build_dir}\\' + installer_filename]
+ light_cmd.extend(objects)
+ light_cmd.extend(vars)
+ run_wix_command(light_cmd, build_dir)
+
+
+def create_installer(houdini_versions, binary_folders, build_dir, installer_filename):
+ wxs_files = []
+ wxs_vars = []
+ for houdini_version_key in binary_folders:
+ rel_path_string = binary_folders[houdini_version_key]
+ major = houdini_versions[houdini_version_key]['major']
+ minor = houdini_versions[houdini_version_key]['minor']
+
+ binary_path = rel_to_os_dir(rel_path_string)
+
+ print(binary_path, " ", houdini_version_key)
+ wxs_file, wxs_var = wix_harvest(
+ str(major) + str(minor), binary_path, build_dir)
+ wxs_files.append(wxs_file)
+ wxs_vars.append(wxs_var)
+
+ wix_objects = wix_compile(wxs_files, wxs_vars, build_dir)
+ wix_link(wix_objects, wxs_vars, build_dir, installer_filename)
+
+
+def copy_binaries(binary_folders, build_dir):
+ clear_folder(build_dir)
+
+ valid_binaries = {}
+ for binary_folder in binary_folders:
+ src_path = binary_folders[binary_folder]
+ if len(src_path) == 0:
+ continue
+ os_src_path = rel_to_os_dir(src_path)
+ if os_src_path.exists():
+ binary_path = build_dir / 'install' / Path(binary_folder)
+ print("copy files from ", os_src_path, " to ", binary_path)
+ shutil.copytree(os_src_path / 'packages' / 'palladio', binary_path)
+ shutil.copytree(os_src_path / 'dso', binary_path / 'dso')
+ shutil.copytree(os_src_path / 'config', binary_path / 'config')
+ shutil.copy(PACKAGE_JSON_PATH, build_dir)
+ valid_binaries[binary_folder] = binary_path
+ return valid_binaries
+
+
+def fill_template(src_path, token_value_dict):
+ result = ''
+ with open(src_path, 'r') as file:
+ src = Template(file.read())
+ result = src.substitute(token_value_dict)
+ return result
+
+
+def fill_template_to_file(src_path, dest_path, token_value_dict):
+ result = fill_template(src_path, token_value_dict)
+ with open(dest_path, 'w') as outfile:
+ outfile.write(result)
+
+
+def fill_wix_template(houdini_versions, copied_binaries, build_dir):
+ wix_directories = ''
+ wix_features = ''
+
+ for version in copied_binaries:
+ version_dict = {
+ 'HOU_MAJOR': houdini_versions[version]['major'],
+ 'HOU_MINOR': houdini_versions[version]['minor'],
+ 'HOU_PATCH': houdini_versions[version]['patch']
+ }
+ newline = ''
+ if len(wix_directories) > 0:
+ newline = '\n'
+ wix_directories += newline + \
+ fill_template(WIX_DIRECTORY_TEMPLATE_PATH, version_dict)
+ wix_features += newline + \
+ fill_template(WIX_FEATURE_TEMPLATE_PATH, version_dict)
+
+ wix_properties = {
+ 'PROJECT_NAME': PROJECT_NAME,
+ 'PLD_VERSION_MMP': get_palladio_version_string(),
+ 'PACKAGE_VENDOR_ESCAPED': PACKAGE_VENDOR_ESCAPED,
+ 'RESOURCE_FOLDER': RESOURCE_DIR,
+ 'HOUDINI_DIRECTORY': wix_directories,
+ 'PALLADIO_FOR_HOUDINI_FEATURE': wix_features
+ }
+ fill_template_to_file(WIX_TEMPLATE_PATH, build_dir /
+ 'palladio.wxs', wix_properties)
+
+
+def fill_deployment_properties_templates(installer_filename, build_dir, build_version):
+ deployment_properties = {
+ 'PACKAGE_NAME': PROJECT_NAME,
+ 'PLD_VERSION_BASE': get_palladio_version_string(),
+ 'PLD_VERSION': get_palladio_version_string(True, build_version),
+ 'PACKAGE_FILE_NAME': installer_filename,
+ 'PLD_PKG_OS': PLATFORM
+ }
+ fill_template_to_file(DEPLOYMENT_PROPERTIES_TEMPLATE_PATH, build_dir /
+ 'deployment.properties', deployment_properties)
+
+
+def parse_arguments(houdini_versions):
+ parser = argparse.ArgumentParser(
+ description='Build unified MSI installer for the Palladio Plugin')
+ for houdini_version in houdini_versions:
+ h_major = houdini_versions[houdini_version]['major']
+ h_minor = houdini_versions[houdini_version]['minor']
+ parser.add_argument('-h' + h_major + h_minor, '--' + houdini_version, default='',
+ help='path to binary location of Palladio build for ' + houdini_version)
+
+ parser.add_argument('-bv', '--build_version', default='',
+ help='build version for current Palladio build')
+
+ parser.add_argument('-bd', '--build_dir', default=str(DEFAULT_BUILD_DIR),
+ help='build directory where installer files are generated')
+
+ parsed_args = vars(parser.parse_args())
+ binary_folders = {}
+ for item in parsed_args:
+ if item.__contains__('houdini'):
+ binary_folders[item] = parsed_args[item]
+ return binary_folders, parsed_args['build_version'], Path(parsed_args['build_dir']).resolve()
+
+
+def main():
+ with open(CFG_PATH, 'r') as config_file:
+ installer_config = json.load(config_file)
+
+ houdini_versions = installer_config['houdini_versions']
+
+ binary_folders, build_version, build_dir = parse_arguments(houdini_versions)
+
+ copied_binaries = copy_binaries(binary_folders, build_dir)
+ installer_filename = gen_installer_filename(build_version)
+
+ if len(copied_binaries) == 0:
+ print('Please provide at least one palladio binary folder, see "build.py --help" for help')
+ return
+
+ fill_deployment_properties_templates(
+ installer_filename, build_dir, build_version)
+ fill_wix_template(houdini_versions, copied_binaries, build_dir)
+
+ create_installer(houdini_versions, copied_binaries,
+ build_dir, installer_filename)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/deploy/installer_config.json b/deploy/installer_config.json
new file mode 100644
index 0000000..8f41be4
--- /dev/null
+++ b/deploy/installer_config.json
@@ -0,0 +1,19 @@
+{
+ "houdini_versions":{
+ "houdini18.5": {
+ "major": "18",
+ "minor": "5",
+ "patch": "759"
+ },
+ "houdini19.0": {
+ "major": "19",
+ "minor": "0",
+ "patch": "720"
+ },
+ "houdini19.5": {
+ "major": "19",
+ "minor": "5",
+ "patch": "303"
+ }
+ }
+}
\ No newline at end of file
diff --git a/deploy/palladio.json b/deploy/palladio.json
new file mode 100644
index 0000000..8a7ecb4
--- /dev/null
+++ b/deploy/palladio.json
@@ -0,0 +1,14 @@
+{
+ "enable": true,
+ "env": [
+ {
+ "PLD_PATH": [
+ {"houdini_version == '18.5'": "C:/ProgramData/ESRI/Palladio/houdini18.5"},
+ {"houdini_version == '19.0'": "C:/ProgramData/ESRI/Palladio/houdini19.0"},
+ {"houdini_version == '19.5'": "C:/ProgramData/ESRI/Palladio/houdini19.5"}
+ ],
+ "PATH": "$PLD_PATH"
+ }
+ ],
+ "path": "$PLD_PATH"
+}
\ No newline at end of file
diff --git a/deploy/resources/banner.png b/deploy/resources/banner.png
new file mode 100644
index 0000000..b93821f
Binary files /dev/null and b/deploy/resources/banner.png differ
diff --git a/deploy/resources/dialog.png b/deploy/resources/dialog.png
new file mode 100644
index 0000000..c3b253a
Binary files /dev/null and b/deploy/resources/dialog.png differ
diff --git a/deploy/resources/license.rtf b/deploy/resources/license.rtf
new file mode 100644
index 0000000..47ee2a3
Binary files /dev/null and b/deploy/resources/license.rtf differ
diff --git a/deploy/resources/palladio.ico b/deploy/resources/palladio.ico
new file mode 100644
index 0000000..cf3a051
Binary files /dev/null and b/deploy/resources/palladio.ico differ
diff --git a/deploy/templates/deployment.properties.in b/deploy/templates/deployment.properties.in
new file mode 100644
index 0000000..850436b
--- /dev/null
+++ b/deploy/templates/deployment.properties.in
@@ -0,0 +1,5 @@
+package_name = ${PACKAGE_NAME}
+package_version_base = ${PLD_VERSION_BASE}
+package_version = ${PLD_VERSION}
+package_file = ${PACKAGE_FILE_NAME}
+package_classifier = ${PLD_PKG_OS}
\ No newline at end of file
diff --git a/deploy/templates/palladio.wxs.in b/deploy/templates/palladio.wxs.in
new file mode 100644
index 0000000..db7ef13
--- /dev/null
+++ b/deploy/templates/palladio.wxs.in
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+ ProductIcon.ico
+
+
+
+
+
+
+
+
+
+ ${HOUDINI_DIRECTORY}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+${PALLADIO_FOR_HOUDINI_FEATURE}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/deploy/templates/palladio_directory.in b/deploy/templates/palladio_directory.in
new file mode 100644
index 0000000..cd30bc0
--- /dev/null
+++ b/deploy/templates/palladio_directory.in
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/deploy/templates/palladio_feature.in b/deploy/templates/palladio_feature.in
new file mode 100644
index 0000000..322e31a
--- /dev/null
+++ b/deploy/templates/palladio_feature.in
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 37c4067..fff13dd 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.13)
+cmake_minimum_required(VERSION 3.19)
project(palladio CXX)
@@ -20,10 +20,13 @@ set(HOUDINI_RELATIVE_PALLADIO_PATH "${HOUDINI_RELATIVE_PACKAGES_PATH}/palladio")
### versioning
-set(PLD_VERSION_MAJOR 2)
-set(PLD_VERSION_MINOR 0)
-set(PLD_VERSION_PATCH 0)
-set(PLD_VERSION_PRE "-beta.1") # set to empty string for final releases
+file(READ "${CMAKE_CURRENT_LIST_DIR}/../version.json" VERSION_FILE)
+
+string(JSON PLD_VERSION_MAJOR GET ${VERSION_FILE} "PLD_VERSION_MAJOR")
+string(JSON PLD_VERSION_MINOR GET ${VERSION_FILE} "PLD_VERSION_MINOR")
+string(JSON PLD_VERSION_PATCH GET ${VERSION_FILE} "PLD_VERSION_PATCH")
+string(JSON PLD_VERSION_PRE GET ${VERSION_FILE} "PLD_VERSION_PRE")
+
if (NOT PLD_VERSION_BUILD)
set(PLD_VERSION_BUILD DEV)
endif ()
diff --git a/version.json b/version.json
new file mode 100644
index 0000000..ffe6128
--- /dev/null
+++ b/version.json
@@ -0,0 +1,6 @@
+{
+ "PLD_VERSION_MAJOR": "2",
+ "PLD_VERSION_MINOR": "0",
+ "PLD_VERSION_PATCH": "0",
+ "PLD_VERSION_PRE": "-beta.1"
+}
\ No newline at end of file