diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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 + + http://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. diff --git a/README.md b/README.md index 4a81012..9c01a71 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ -# ROS 2 Coobooks \ No newline at end of file +# ROS 2 Cookbooks + +Currently, this repo hosts the chef cookbook for setting up a Windows machine for ROS 2 development or binaries, but it may be home to more soon! + +## ROS 2 Windows + +See the cookbook level [README.md](cookbooks/ros2_windows/README.md) diff --git a/cookbooks/chefignore b/cookbooks/chefignore new file mode 100644 index 0000000..38e7379 --- /dev/null +++ b/cookbooks/chefignore @@ -0,0 +1,107 @@ +# Put files/directories that should be ignored in this file when uploading +# to a chef-server or supermarket. +# Lines that start with '# ' are comments. + +# OS generated files # +###################### +.DS_Store +Icon? +nohup.out +ehthumbs.db +Thumbs.db + +# SASS # +######## +.sass-cache + +# EDITORS # +########### +\#* +.#* +*~ +*.sw[a-z] +*.bak +REVISION +TAGS* +tmtags +*_flymake.* +*_flymake +*.tmproj +.project +.settings +mkmf.log + +## COMPILED ## +############## +a.out +*.o +*.pyc +*.so +*.com +*.class +*.dll +*.exe +*/rdoc/ + +# Testing # +########### +.watchr +.rspec +spec/* +spec/fixtures/* +test/* +features/* +examples/* +Guardfile +Procfile +.kitchen* +.rubocop.yml +spec/* +Rakefile +.travis.yml +.foodcritic +.codeclimate.yml + +# SCM # +####### +.git +*/.git +.gitignore +.gitmodules +.gitconfig +.gitattributes +.svn +*/.bzr/* +*/.hg/* +*/.svn/* + +# Berkshelf # +############# +Berksfile +Berksfile.lock +cookbooks/* +tmp + +# Policyfile # +############## +Policyfile.rb +Policyfile.lock.json + +# Cookbooks # +############# +CONTRIBUTING* +CHANGELOG* +TESTING* +MAINTAINERS.toml + +# Strainer # +############ +Colanderfile +Strainerfile +.colander +.strainer + +# Vagrant # +########### +.vagrant +Vagrantfile diff --git a/cookbooks/ros2_windows/Berksfile b/cookbooks/ros2_windows/Berksfile new file mode 100644 index 0000000..60446bf --- /dev/null +++ b/cookbooks/ros2_windows/Berksfile @@ -0,0 +1,5 @@ +# frozen_string_literal: true +source 'https://supermarket.chef.io' + +# Checks metadata.rb for dependencies +metadata diff --git a/cookbooks/ros2_windows/README.md b/cookbooks/ros2_windows/README.md new file mode 100644 index 0000000..4be28b5 --- /dev/null +++ b/cookbooks/ros2_windows/README.md @@ -0,0 +1,83 @@ +# ROS 2 Windows Cookbook + +This cookbook configures a host machine for ROS 2 development or binary installation. + +## Windows Install + +This walkthrough will install the following: +* chef/chef-solo +* chocolatey +* git + +In powershell, install chef: +``` +> . { iwr -useb https://omnitruck.cinc.sh/install.ps1 } | iex; install +``` + +In same shell, install chocolatey and git +``` +> Set-ExecutionPolicy Bypass -Scope Process -Force;. { iwr -useb https://chocolatey.org/install.ps1 } | iex +> choco install -y git +> restart-computer +``` + +After the computer restarts, in the cmd shell (not powershell) clone this repo: +``` +> git clone https://github.com/ros-infrastructure/ros2-cookbooks +``` + +Run `berks` to get the cookbook dependencies +``` +cd ros2-cookbooks/coobooks/ros2_windows +berks vendor .. +``` + +Run chef-solo to configure machine. +``` +> c:\cinc-project\cinc\bin\cinc-solo -c ros2-cookbooks\.chef\solo.rb -j ros2_windows.json +``` + +Type yes to accept the standard Chef licenses. + +Debug any issues that arise until running chef-solo completes successfully. + +## Configuration options + +The file `ros2_windows.json` may be modified for your own installation. +Please check the available attributes in [cookbooks/ros2_windows/attributes](cookbooks/ros2_windows/attributes) + +For example, the following will configure the installation to setup the machine for development with connext and opensplice. + +Adjust the `rti_connext` parameters to match your installation files. + +```json +{ + "default_attributes": { + "ros2_windows": { + "download_sources": "false", + "vs_version": "buildtools", + "ros2_ws": "C:/ci", + "install_connext": "true", + "install_opensplice": "true", + "rti_connext": { + "target_platform": "x64Win64", + "min_vs_version": "2017", + "license_file": "C:\\TEMP\\rticonnextdds-license\\rti_license.dat", + "installer_dir": "C:\\TEMP\\rticonnextdds-src", + "version": "5.3.1", + "edition": "pro", + "openssl_version": "1.0.2n" + } + } + }, + "run_list": ["role[ros2-development]"] +} +``` + +## Automating Qt installation + +For the most part the Qt installation is fully automated. +However, downloading Qt binaries requires creating a login. +If you already have a login and would like this process to complete successfully on a fresh machine, add the file at `${HOME}\AppData\Roaming\Qt\qtaccount.ini` from a previously setup Windows machine to the files directory, before deploying this to a new machine. +If you skip this step, the Qt installer will pause and require you to create a login before continuing. +Importantly, on a headless setup, you will not be able to see this GUI feedback and your installation will pause indefinitely. diff --git a/cookbooks/ros2_windows/attributes/ros2.rb b/cookbooks/ros2_windows/attributes/ros2.rb new file mode 100644 index 0000000..77f3b16 --- /dev/null +++ b/cookbooks/ros2_windows/attributes/ros2.rb @@ -0,0 +1,18 @@ +# github.com/ros2/ros2 branch version, "master", "dashing", "dashing-release", etc. +# Only used if downloading sources +default['ros2_windows']['source']['ros2.repos'] = 'master' + +# Location of ros2 workspace +default['ros2_windows']['ros2_ws'] = 'C:\\dev\\ros2_ws' + +# ROS 2 binary versions, dashing/eloquent/foxy etc +default['ros2_windows']['release_version'] = 'eloquent' + +# Binary build version, 'release' or 'debug' +default['ros2_windows']['build_type'] = 'release' + +# Current binary locations, update as new releases become available +default['ros2_windows']['eloquent']['release'] = 'https://github.com/ros2/ros2/releases/download/release-eloquent-20191122/ros2-eloquent-20191122-windows-release-amd64.zip' +default['ros2_windows']['eloquent']['debug'] = 'https://github.com/ros2/ros2/releases/download/release-eloquent-20191122/ros2-eloquent-20191122-windows-debug-amd64.zip' +default['ros2_windows']['dashing']['release'] = 'https://github.com/ros2/ros2/releases/download/release-dashing-20200319/ros2-dashing-20200319-windows-amd64.zip' +default['ros2_windows']['dashing']['debug'] = 'https://github.com/ros2/ros2/releases/download/release-dashing-20200319/ros2-dashing-20200319-windows-debug-amd64.zip' diff --git a/cookbooks/ros2_windows/attributes/rti_connext.rb b/cookbooks/ros2_windows/attributes/rti_connext.rb new file mode 100644 index 0000000..d7ee0a7 --- /dev/null +++ b/cookbooks/ros2_windows/attributes/rti_connext.rb @@ -0,0 +1,18 @@ +# MAJOR.MINOR.PATCH version of rti, match to your install package +default['ros2_windows']['rti_connext']['version'] = nil + +# Edition of your connext installer ('evaluation', 'pro') +default['ros2_windows']['rti_connext']['edition'] = nil + +# Match these to the values in your install package +default['ros2_windows']['rti_connext']['target_platform'] = 'x64Win64' +default['ros2_windows']['rti_connext']['min_vs_version'] = '2017' + +# Path to your rti_connext installer file +default['ros2_windows']['rti_connext']['license_file'] = nil + +# Path to the directory container your installer packages +default['ros2_windows']['rti_connext']['installer_dir'] = nil + +# Openssl version used specifically for rti-connext, match to the installer package +default['ros2_windows']['rti_connext']['openssl_version'] = nil diff --git a/cookbooks/ros2_windows/attributes/visual_studio.rb b/cookbooks/ros2_windows/attributes/visual_studio.rb new file mode 100644 index 0000000..b0a5238 --- /dev/null +++ b/cookbooks/ros2_windows/attributes/visual_studio.rb @@ -0,0 +1,2 @@ +# Visual studio version, "buildtools" or "community" +default['ros2_windows']['vs_version'] = 'buildtools' diff --git a/cookbooks/ros2_windows/files/qt-installer.qs b/cookbooks/ros2_windows/files/qt-installer.qs new file mode 100644 index 0000000..120419b --- /dev/null +++ b/cookbooks/ros2_windows/files/qt-installer.qs @@ -0,0 +1,237 @@ +/* eslint-disable no-alert, no-console, no-var, linebreak-style, prefer-arrow-callback, + prefer-template, no-restricted-syntax, quote-props, prefer-destructuring */ +// eslint disables were chosen because they are incompatible with the Qt JavaScript Engine + +// Copyright 2016 Ben Lau +// +// 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 +// +// http://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. + +// File adapted and modified from the following examples +// https://github.com/benlau/qtci/blob/master/bin/extract-qt-installer +// Modified from shell script to .qs file, and adapted to specific ros2/ci use + +// Command line usage +// qt-unified-windows-x86-3.1.1-online.exe --verbose --script qt-installer.qs [Option=Value] +// Options: +// MsvcVersion=[2015,2017,2019,...] +// ErrorLogArgName='C:\Path\To\Writeable\Logfile' + +/* global installer:writeable, gui:writeable, buttons, QMessageBox */ + +var DefaultMsvcVersion = '2019'; +var BuildToolsPrefix = 'win64_msvc'; +var BuildToolsSuffix = '_64'; +var Qt5ComponentPrefix = 'qt.qt5.'; +var ErrorLogArgName = 'ErrorLogname'; +var MsvcVersionArgName = 'MsvcVersion'; + +var CheckCategory = function CheckCategory(gui, category, shouldCheck) { + var page = gui.pageWidgetByObjectName('ComponentSelectionPage'); + var checkBox = gui.findChild(page, category); + if (checkBox) { + if (checkBox.checked !== shouldCheck) { + checkBox.click(); + } + } +}; + +var FindLatestCompatibleVersion = function FindLatestCompatibleVersion(regex, filterFn) { + var versions = []; + var components = installer.components(); + var id; + var matchResult; + + for (id in components) { + if (Object.prototype.hasOwnProperty.call(components, id)) { + // Documentation about QtQmL regex https://doc.qt.io/qt-5/qregexp.html + matchResult = components[id].name.match(regex); + if (matchResult && filterFn(matchResult[1])) { + console.log('Match result: ' + matchResult[1]); + versions.push(matchResult[1]); + } + } + } + if (versions.length === 0) { + return null; + } + // We want latest, so sort in descending order + versions.sort(function DescendingOrder(a, b) { return b - a; }); + return versions[0]; +}; + +var FindMostRecentLTS = function FindMostRecentLTS() { + var regex = '(?:qt.qt5.)(\\d+)$'; + // Match component names of the form qt.qt5.5xyz and capture just the 5xyz + return FindLatestCompatibleVersion(regex, function IncludeAll() { return true; }); +}; + +var FindMostRecentBuildTools = function FindMostRecentBuildTools(prefix, desiredMsvcYear) { + var regex = '(?:' + prefix + '.' + BuildToolsPrefix + ')(\\d{4})(?:' + BuildToolsSuffix + ')$'; + var filter = function LessThanOrEqualTo(value) { return Number(value) <= desiredMsvcYear; }; + return FindLatestCompatibleVersion(regex, filter); +}; + +var SelectQtComponent = function SelectQtComponent() { + var widget = gui.currentPageWidget(); + + var latestVersion = FindMostRecentLTS(); + var targetBuildVersion = installer.value(MsvcVersionArgName, DefaultMsvcVersion); + + var prefix; + var buildToolsVersion; + var componentId; + var emptyDiskSpace; + + if (!latestVersion) { + throw new Error('Finding latest version failed'); + } + prefix = Qt5ComponentPrefix + latestVersion; + + console.log('Target MSVC Version ' + targetBuildVersion); + buildToolsVersion = FindMostRecentBuildTools(prefix, targetBuildVersion); + if (!buildToolsVersion) { + throw new Error('Finding a component compatibile with the desired buildtools version (' + targetBuildVersion + '}) failed'); + } + console.log('Available Buildtools version: ' + buildToolsVersion); + + // Should be of the form qt.qt5.5126.win64_msvc2017_64 + componentId = prefix + '.' + BuildToolsPrefix + buildToolsVersion + BuildToolsSuffix; + console.log('Attempting to check ' + componentId); + widget.deselectAll(); + emptyDiskSpace = installer.requiredDiskSpace(); + widget.selectComponent(componentId); + if (emptyDiskSpace === installer.requiredDiskSpace()) { + throw new Error('Selecting component ' + componentId + ' failed'); + } + return { 'latestVersion': latestVersion, 'buildToolsVersion': buildToolsVersion }; +}; + +var GetDirectoryFromVersion = function GetDirectoryFromVersion(version) { + // The versions are of the form 5123, where the first character is the 4/5 version + // and the last character is the minor version (even if 0), and the middle characters + // are the major version + var versionString = String(version); + var length = version.length; + if (length < 3 || length > 4) { + throw new Error('Assertion failed: (version.length < 3 || version.length > 4)'); + } + return versionString[0] + '.' + versionString.slice(1, length - 1) + '.' + versionString[length - 1]; +}; + + +function Controller() { + installer.installationFinished.connect(function ClickNext() { + gui.clickButton(buttons.NextButton); + }); + // If any message boxes need to be rejected, they'll need custom callbacks + installer.autoAcceptMessageBoxes(); + installer.setMessageBoxAutomaticAnswer('OverwriteTargetDirectory', QMessageBox.Yes); + installer.setMessageBoxAutomaticAnswer('installationErrorWithRetry', QMessageBox.Ignore); +} + +Controller.prototype.WelcomePageCallback = function WelcomePageCallback() { + // Connects with Qt over internet + gui.clickButton(buttons.NextButton, 3000); +}; + +Controller.prototype.CredentialsPageCallback = function CredentialsPageCallback() { + gui.clickButton(buttons.CommitButton); +}; + +Controller.prototype.ComponentSelectionPageCallback = function ComponentSelectionPageCallback() { + var page = gui.pageWidgetByObjectName('ComponentSelectionPage'); + var fetchButton = gui.findChild(page, 'FetchCategoryButton'); + var path = installer.value(ErrorLogArgName, '%temp%\\installer.err'); + var qt5Path; + // These should be the defaults, but just in case... + CheckCategory(gui, 'LTS', false); + CheckCategory(gui, 'Archive', false); + CheckCategory(gui, 'Latest releases', true); + CheckCategory(gui, 'Preview', false); + if (fetchButton) { + // Refresh components if any of the checkboxes above changed + fetchButton.click(); + } + + try { + this.installedVersion = SelectQtComponent(); + if (typeof this.installedVersion !== 'undefined') { + qt5Path = '@TargetDir@\\' + GetDirectoryFromVersion(this.installedVersion.latestVersion) + + '\\msvc' + this.installedVersion.buildToolsVersion + '_64'; + console.log('Setting Qt5_DIR Environment Variable to ' + qt5Path); + installer.performOperation('EnvironmentVariable', ['Qt5_DIR', qt5Path, true, false]); + } + } catch (err) { + // Cancel install if any error is encountered + console.log(err.fileName + ':' + err.lineNumber + ' ' + err.message); + console.log('Writing error to file ' + path); + installer.performOperation('AppendFile', [path, err.message]); + gui.clickButton(buttons.CancelButton, 5000); + throw err; + } + + gui.clickButton(buttons.NextButton); +}; + +Controller.prototype.IntroductionPageCallback = function IntroductionPageCallback() { + console.log('Retrieving meta information from remote repository'); + gui.clickButton(buttons.NextButton); +}; + +Controller.prototype.TargetDirectoryPageCallback = function TargetDirectoryPageCallback() { + // Default location is C:\Qt\Qt5.12.6 + gui.clickButton(buttons.NextButton); +}; + +Controller.prototype.ObligationsPageCallback = function ObligationsPageCallback() { + var widget = gui.currentPageWidget(); + widget.obligationsAgreement.click(); + for (var id in widget.obligationsAgreement) { + console.log(id); + } + + gui.clickButton(buttons.NextButton); +}; + +Controller.prototype.LicenseAgreementPageCallback = function LicenseAgreementPageCallback() { + var widget = gui.currentPageWidget(); + if (widget != null) { + widget.AcceptLicenseRadioButton.setChecked(true); + } + gui.clickButton(buttons.NextButton); +}; + +Controller.prototype.ReadyForInstallationPageCallback = function ReadyForInstallPageCallback() { + gui.clickButton(buttons.CommitButton); +}; + +Controller.prototype.FinishedPageCallback = function FinishedPageCallback() { + var widget = gui.currentPageWidget(); + if (widget.LaunchQtCreatorCheckBoxForm) { + // No this form for minimal platform + widget.LaunchQtCreatorCheckBoxForm.launchQtCreatorCheckBox.setChecked(false); + } + + gui.clickButton(buttons.FinishButton); +}; + +// Telemetry disabled +Controller.prototype.DynamicTelemetryPluginFormCallback = function DynamicTelemetryFormCallback() { + var page = gui.pageWidgetByObjectName('DynamicTelemetryPluginForm'); + page.statisticGroupBox.disableStatisticRadioButton.setChecked(true); + gui.clickButton(buttons.NextButton); +}; + +Controller.prototype.StartMenuDirectoryPageCallback = function StartMenuDirectoryPageCallback() { + gui.clickButton(buttons.NextButton); +}; diff --git a/cookbooks/ros2_windows/files/qt-maintenance.qs b/cookbooks/ros2_windows/files/qt-maintenance.qs new file mode 100644 index 0000000..ddf2431 --- /dev/null +++ b/cookbooks/ros2_windows/files/qt-maintenance.qs @@ -0,0 +1,223 @@ +/* eslint-disable no-alert, no-console, no-var, linebreak-style, prefer-arrow-callback, + prefer-template, no-restricted-syntax, quote-props, prefer-destructuring */ +// eslint disables were chosen because they are incompatible with the Qt JavaScript Engine + +// Copyright 2016 Ben Lau +// +// 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 +// +// http://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. + +// File adapted and modified from the following examples +// https://github.com/benlau/qtci/blob/master/bin/extract-qt-installer +// Modified from shell script to .qs file, and adapted to specific ros2/ci use + +// Command line usage +// qt-unified-windows-x86-3.1.1-online.exe --verbose --script qt-installer.qs [Option=Value] +// Options: +// MsvcVersion=[2015,2017,2019,...] +// ErrorLogArgName='C:\Path\To\Writeable\Logfile' + +/* global installer:writeable, gui:writeable, buttons, QMessageBox */ + +var DefaultMsvcVersion = '2019'; +var BuildToolsPrefix = 'win64_msvc'; +var BuildToolsSuffix = '_64'; +var Qt5ComponentPrefix = 'qt.qt5.'; +var ErrorLogArgName = 'ErrorLogname'; +var MsvcVersionArgName = 'MsvcVersion'; + +var CheckCategory = function CheckCategory(gui, category, shouldCheck) { + var page = gui.pageWidgetByObjectName('ComponentSelectionPage'); + var checkBox = gui.findChild(page, category); + if (checkBox) { + if (checkBox.checked !== shouldCheck) { + checkBox.click(); + } + } +}; + +var FindLatestCompatibleVersion = function FindLatestCompatibleVersion(regex, filterFn) { + var versions = []; + var components = installer.components(); + var id; + var matchResult; + + for (id in components) { + if (Object.prototype.hasOwnProperty.call(components, id)) { + // Documentation about QtQmL regex https://doc.qt.io/qt-5/qregexp.html + matchResult = components[id].name.match(regex); + if (matchResult && filterFn(matchResult[1])) { + console.log('Match result: ' + matchResult[1]); + versions.push(matchResult[1]); + } + } + } + if (versions.length === 0) { + return null; + } + // We want latest, so sort in descending order + versions.sort(function DescendingOrder(a, b) { return b - a; }); + return versions[0]; +}; + +var FindMostRecentLTS = function FindMostRecentLTS() { + var regex = '(?:qt.qt5.)(\\d+)$'; + // Match component names of the form qt.qt5.5xyz and capture just the 5xyz + return FindLatestCompatibleVersion(regex, function IncludeAll() { return true; }); +}; + +var FindMostRecentBuildTools = function FindMostRecentBuildTools(prefix, desiredMsvcYear) { + var regex = '(?:' + prefix + '.' + BuildToolsPrefix + ')(\\d{4})(?:' + BuildToolsSuffix + ')$'; + var filter = function LessThanOrEqualTo(value) { return Number(value) <= desiredMsvcYear; }; + return FindLatestCompatibleVersion(regex, filter); +}; + +var SelectQtComponent = function SelectQtComponent() { + var widget = gui.currentPageWidget(); + + var latestVersion = FindMostRecentLTS(); + var targetBuildVersion = installer.value(MsvcVersionArgName, DefaultMsvcVersion); + + var prefix; + var buildToolsVersion; + var componentId; + var beforeDiskSpace; + var selectedComponent; + + if (!latestVersion) { + throw new Error('Finding latest version failed'); + } + prefix = Qt5ComponentPrefix + latestVersion; + + console.log('Target MSVC Version ' + targetBuildVersion); + buildToolsVersion = FindMostRecentBuildTools(prefix, targetBuildVersion); + if (!buildToolsVersion) { + throw new Error('Finding a component compatibile with the desired buildtools version (' + targetBuildVersion + '}) failed'); + } + console.log('Available Buildtools version: ' + buildToolsVersion); + + // Should be of the form qt.qt5.5126.win64_msvc2017_64 + componentId = prefix + '.' + BuildToolsPrefix + buildToolsVersion + BuildToolsSuffix; + console.log('Attempting to check ' + componentId); + + // We're only installing one component, deselecting will cause everything else to be uninstalled. + // If this gets run on systems that need more Qt stuff installed, this logic should be adjusted to + // just uninstall older msvc components. + beforeDiskSpace = installer.requiredDiskSpace(); + widget.deselectAll(); + widget.selectComponent(componentId); + selectedNewComponent = (beforeDiskSpace !== installer.requiredDiskSpace()); + return { + 'selectedNewComponent': selectedNewComponent, + 'latestVersion': latestVersion, + 'buildToolsVersion': buildToolsVersion + }; +}; + +var GetDirectoryFromVersion = function GetDirectoryFromVersion(version) { + // The versions are of the form 5123, where the first character is the 4/5 version + // and the last character is the minor version (even if 0), and the middle characters + // are the major version + var versionString = String(version); + var length = version.length; + if (length < 3 || length > 4) { + throw new Error('Assertion failed: (version.length < 3 || version.length > 4)'); + } + return versionString[0] + '.' + versionString.slice(1, length - 1) + '.' + versionString[length - 1]; +}; + + +function Controller() { + installer.installationFinished.connect(function ClickNext() { + // gui.clickButton(buttons.NextButton); + }); + // If any message boxes need to be rejected, they'll need custom callbacks + installer.autoAcceptMessageBoxes(); + installer.setMessageBoxAutomaticAnswer('OverwriteTargetDirectory', QMessageBox.Yes); + installer.setMessageBoxAutomaticAnswer('installationErrorWithRetry', QMessageBox.Ignore); +} + +Controller.prototype.WelcomePageCallback = function WelcomePageCallback() { + // Connects with Qt over internet + gui.clickButton(buttons.NextButton, 3000); +}; + +Controller.prototype.CredentialsPageCallback = function CredentialsPageCallback() { + gui.clickButton(buttons.CommitButton); +}; + +Controller.prototype.IntroductionPageCallback = function IntroductionPageCallback() { + console.log('Retrieving meta information from remote repository'); + var widget = gui.currentPageWidget(); + widget.findChild('PackageManagerRadioButton').checked = true; + gui.clickButton(buttons.NextButton); +}; + +Controller.prototype.ComponentSelectionPageCallback = function ComponentSelectionPageCallback() { + var page = gui.pageWidgetByObjectName('ComponentSelectionPage'); + var fetchButton = gui.findChild(page, 'FetchCategoryButton'); + var path = installer.value(ErrorLogArgName, '%temp%\\installer.err'); + var qt5Path; + // Check archive so deselection works + CheckCategory(gui, 'LTS', false); + CheckCategory(gui, 'Archive', true); + CheckCategory(gui, 'Latest releases', true); + CheckCategory(gui, 'Preview', false); + if (fetchButton) { + // Refresh components if any of the checkboxes above changed + fetchButton.click(); + } + + try { + this.installedVersion = SelectQtComponent(); + if (typeof this.installedVersion !== 'undefined') { + if (this.installedVersion.selectedNewComponent) { + qt5Path = '@TargetDir@\\' + GetDirectoryFromVersion(this.installedVersion.latestVersion) + + '\\msvc' + this.installedVersion.buildToolsVersion + '_64'; + console.log('Setting Qt5_DIR Environment Variable to ' + qt5Path); + installer.performOperation('EnvironmentVariable', ['Qt5_DIR', qt5Path, true, false]); + } else { + // Installer won't let us do anything if component selections don't change. + + gui.clickButton(buttons.CancelButton); + } + } + } catch (err) { + // Cancel install if any error is encountered + console.log(err.fileName + ':' + err.lineNumber + ' ' + err.message); + console.log('Writing error to file ' + path); + installer.performOperation('AppendFile', [path, err.message]); + throw err; + } + + gui.clickButton(buttons.NextButton); +}; + +Controller.prototype.ReadyForInstallationPageCallback = function ReadyForInstallPageCallback() { + console.log('ReadyForInstallPageCallback'); + gui.clickButton(buttons.CommitButton); +}; + +Controller.prototype.FinishedPageCallback = function FinishedPageCallback() { + var widget = gui.currentPageWidget(); + if (widget.LaunchQtCreatorCheckBoxForm) { + widget.LaunchQtCreatorCheckBoxForm.launchQtCreatorCheckBox.setChecked(false); + } + + gui.clickButton(buttons.FinishButton); +}; + +Controller.prototype.PerformInstallationPageCallback = function PerformInstallationPageCallback() { + console.log('In perform installation callback'); + var page = gui.currentPageWidget(); + page.setAutomatedPageSwitchEnabled(true); +}; diff --git a/cookbooks/ros2_windows/metadata.rb b/cookbooks/ros2_windows/metadata.rb new file mode 100644 index 0000000..4c6949f --- /dev/null +++ b/cookbooks/ros2_windows/metadata.rb @@ -0,0 +1,23 @@ +name 'ros2_windows' +maintainer 'Stephen Brawner' +maintainer_email 'brawner@gmail.com' +license 'All Rights Reserved' +description 'Installs/Configures ros2_windows' +long_description 'Installs/Configures ros2_windows' +version '0.1.0' +chef_version '>= 12.1' if respond_to?(:chef_version) + +# The `issues_url` points to the location where issues for this cookbook are +# tracked. A `View Issues` link will be displayed on this cookbook's page when +# uploaded to a Supermarket. +# +# issues_url 'https://github.com/osrf/chef-osrf/issues' + +# The `source_url` points to the development repository for this cookbook. A +# `View Source` link will be displayed on this cookbook's page when uploaded to +# a Supermarket. +# +# source_url 'https://github.com/osrf/chef-osrf' +depends 'chocolatey' +depends 'seven_zip' +depends 'windows' diff --git a/cookbooks/ros2_windows/recipes/chocolatey_installs.rb b/cookbooks/ros2_windows/recipes/chocolatey_installs.rb new file mode 100644 index 0000000..2ecbbaa --- /dev/null +++ b/cookbooks/ros2_windows/recipes/chocolatey_installs.rb @@ -0,0 +1,31 @@ +execute 'cppcheck' do + command 'choco install -y git cmake curl vcredist2013 vcredist140 patch cppcheck' +end + +windows_env 'PATH' do + key_name 'PATH' + value 'C:\\Program Files\\Git\\cmd;C:\\Program Files\\CMake\\bin' + delim ';' + action :modify +end + +custom_chocolatey_packages = { + 'asio' => 'asio.1.12.1', + 'bullet' => 'bullet.2.89.0', + 'cunit' => 'cunit.2.1.3', + 'eigen' => 'eigen.3.3.4', + 'log4cxx' => 'log4cxx.0.10.0', + 'tinyxml-usestl' => 'tinyxml-usestl.2.6.2', + 'tinyxml2' => 'tinyxml2.6.0.0' +} + +custom_chocolatey_packages.each do |name, pkg| + remote_file "#{pkg}.nupkg" do + source "https://github.com/ros2/choco-packages/releases/download/2020-02-24/#{pkg}.nupkg" + end + + chocolatey_package 'custom_packages' do + package_name "#{name}" + source '.\\' + end +end diff --git a/cookbooks/ros2_windows/recipes/opencv.rb b/cookbooks/ros2_windows/recipes/opencv.rb new file mode 100644 index 0000000..7c208fc --- /dev/null +++ b/cookbooks/ros2_windows/recipes/opencv.rb @@ -0,0 +1,18 @@ +seven_zip_archive 'open_cv_zip' do + path 'C:\\opencv' + source 'https://github.com/ros2/ros2/releases/download/opencv-archives/opencv-3.4.6-vc16.VS2019.zip' + overwrite true +end + +windows_env 'OpenCV_DIR' do + key_name 'OpenCV_DIR' + value 'C:\\opencv' + action :create +end + +windows_env 'PATH' do + key_name 'PATH' + value 'C:\\opencv' + delim ';' + action :modify +end diff --git a/cookbooks/ros2_windows/recipes/opensplice.rb b/cookbooks/ros2_windows/recipes/opensplice.rb new file mode 100644 index 0000000..97a7303 --- /dev/null +++ b/cookbooks/ros2_windows/recipes/opensplice.rb @@ -0,0 +1,11 @@ +seven_zip_archive 'opensplice' do + path 'C:\opensplice' + source 'https://github.com/ADLINK-IST/opensplice/releases/download/OSPL_V6_9_190925OSS_RELEASE/PXXX-VortexOpenSplice-6.9.190925OSS-HDE-x86_64.win-vs2019-installer.zip' + overwrite true +end + +windows_env 'OSPL_HOME' do + key_name 'OSPL_HOME' + value 'C:\\opensplice\\HDE\\x86_64.win64' + action :create +end diff --git a/cookbooks/ros2_windows/recipes/openssl.rb b/cookbooks/ros2_windows/recipes/openssl.rb new file mode 100644 index 0000000..282df66 --- /dev/null +++ b/cookbooks/ros2_windows/recipes/openssl.rb @@ -0,0 +1,17 @@ +windows_package 'openssl' do + source 'https://slproweb.com/download/Win64OpenSSL-1_0_2u.exe' + options '/VERYSILENT' +end + +windows_env 'OPENSSL_CONF' do + key_name 'OPENSSL_CONF' + value 'C:\\OpenSSL-Win64\\bin\\openssl.cfg' + action :create +end + +windows_env 'PATH' do + key_name 'PATH' + value 'C:\\OpenSSL-Win64\\bin' + delim ';' + action :modify +end diff --git a/cookbooks/ros2_windows/recipes/pip_installs.rb b/cookbooks/ros2_windows/recipes/pip_installs.rb new file mode 100644 index 0000000..418d1a4 --- /dev/null +++ b/cookbooks/ros2_windows/recipes/pip_installs.rb @@ -0,0 +1,51 @@ +required_pip_packages = %w[ + pydot + PyQt5 + vcstool + colcon-common-extensions + catkin_pkg + cryptography + EmPy + ifcfg + lark-parser + lxml + netifaces + numpy + opencv-python + pyparsing + pyyaml + pytest + pytest-mock + coverage + mock +] + +development_pip_packages = %w[ + flake8 + flake8-blind-except + flake8-builtins + flake8-class-newline + flake8-comprehensions + flake8-deprecated + flake8-docstrings + flake8-import-order + flake8-quotes + mypy + pep8 + pydocstyle +] + +# Use explicit location because python may not be on the PATH if chef-solo has not been run before +execute 'pip_update' do + command 'C:\Python37\python.exe -m pip install -U pip setuptools' +end + +execute 'pip_required' do + command 'C:\Python37\python.exe -m pip install -U ' + required_pip_packages.join(' ') +end + +if node['ros2_windows']['development'] == true + execute 'pip_additional' do + command 'C:\Python37\python.exe -m pip install -U ' + development_pip_packages.join(' ') + end +end diff --git a/cookbooks/ros2_windows/recipes/python.rb b/cookbooks/ros2_windows/recipes/python.rb new file mode 100644 index 0000000..3712ccf --- /dev/null +++ b/cookbooks/ros2_windows/recipes/python.rb @@ -0,0 +1,4 @@ +windows_package 'python' do + source 'https://www.python.org/ftp/python/3.7.6/python-3.7.6-amd64.exe' + options '/quiet TargetDir=C:\\Python37 PrependPath=1 Include_debug=1 Include_symbols=1' +end diff --git a/cookbooks/ros2_windows/recipes/qt5.rb b/cookbooks/ros2_windows/recipes/qt5.rb new file mode 100644 index 0000000..2789435 --- /dev/null +++ b/cookbooks/ros2_windows/recipes/qt5.rb @@ -0,0 +1,56 @@ +directory 'AppData_Qt' do + path File.join(Dir.home(), "AppData\\Roaming\\Qt") +end + +# Installing Qt5 requires an account. This file contains a username and secret account token +cookbook_file 'qtaccount.ini' do + path File.join(Dir.home(), "AppData\\Roaming\\Qt\\qtaccount.ini") + source 'qtaccount.ini' + action :create_if_missing + + # If the deploying user did not add this file to `files` continue anyway. + ignore_failure true +end + +cookbook_file 'qt-installer.qs' do + source 'qt-installer.qs' +end + +cookbook_file 'qt-maintenance.qs' do + source 'qt-maintenance.qs' +end + +# Install Qt5 with automated install script, no msvc2019 version exists but 2017 is compatible +# Updater/silentUpdate options did not work for me. +# Install scripts finds and installs the most recent LTS version +error_filename = File.join(Dir.home(), "qt_install.err") + +windows_package 'Qt Maintenance' do + source 'c:\\Qt\\MaintenanceTool.exe' + installer_type :custom + # I couldn't find documentation, but return codes don't seem to correspond with an actual failure. + # Instead error information is written to the ErrorLogname below + returns [0, 1, 3] + options '--script qt-maintenance.qs MsvcVersion=2019 ErrorLogname="' + error_filename + '"' + timeout 2000 + only_if {::File.exist?('c:\\Qt\\MaintenanceTool.exe')} +end + +windows_package 'Qt Install' do + source 'http://download.qt.io/official_releases/online_installers/qt-unified-windows-x86-online.exe' + installer_type :custom + # I couldn't find documentation, but return codes don't seem to correspond with an actual failure. + # Instead error information is written to the ErrorLogname below + returns [0, 1, 3] + options '--script qt-installer.qs MsvcVersion=2019 ErrorLogname="' + error_filename + '"' + timeout 2000 + not_if {::File.exist?('c:\\Qt\\MaintenanceTool.exe')} +end + +ruby_block 'error_exist?' do + block do + if ::File.exist?(error_filename) + raise 'Error install Qt5, see ' + error_filename + end + end +end diff --git a/cookbooks/ros2_windows/recipes/ros2.rb b/cookbooks/ros2_windows/recipes/ros2.rb new file mode 100644 index 0000000..126e2b1 --- /dev/null +++ b/cookbooks/ros2_windows/recipes/ros2.rb @@ -0,0 +1,12 @@ +include_recipe 'chocolatey' + +# Using seven_zip also for general zip files because it can download and extract in a single resource +include_recipe 'seven_zip' +include_recipe 'ros2_windows::python' +include_recipe 'ros2_windows::pip_installs' +include_recipe 'ros2_windows::opencv' +include_recipe 'ros2_windows::openssl' +include_recipe 'ros2_windows::chocolatey_installs' +include_recipe 'ros2_windows::visual_studio' +include_recipe 'ros2_windows::qt5' +include_recipe 'ros2_windows::xmllint' diff --git a/cookbooks/ros2_windows/recipes/ros2_binaries.rb b/cookbooks/ros2_windows/recipes/ros2_binaries.rb new file mode 100644 index 0000000..2dd3d68 --- /dev/null +++ b/cookbooks/ros2_windows/recipes/ros2_binaries.rb @@ -0,0 +1,7 @@ +release_version = node['ros2_windows']['release_version'] +build_type = node['ros2_windows']['build_type'] + +seven_zip_archive 'ros2' do + source node['ros2_windows'][release_version][build_type] + path node['ros2_windows']['ros2_ws'] +end diff --git a/cookbooks/ros2_windows/recipes/ros2_sources.rb b/cookbooks/ros2_windows/recipes/ros2_sources.rb new file mode 100644 index 0000000..70f4e91 --- /dev/null +++ b/cookbooks/ros2_windows/recipes/ros2_sources.rb @@ -0,0 +1,22 @@ +directory 'ros2_ws' do + path node['ros2_windows']['ros2_ws'] + action :create + recursive true +end + +ros2_src_dir = File.join(node['ros2_windows']['ros2_ws'], 'src') +directory 'ros2_ws_src' do + path ros2_src_dir + action :create + recursive true +end + +ros2_repos_path = File.join(node['ros2_windows']['ros2_ws'], 'ros2.repos') +remote_file 'ros2.repos' do + source "https://raw.githubusercontent.com/ros2/ros2/#{node['ros2_windows']['source']['ros2.repos']}/ros2.repos" + path ros2_repos_path +end + +execute 'vcs_import' do + command "C:\\Python37\\Scripts\\vcs.exe import --input #{ros2_repos_path} #{ros2_src_dir}" +end diff --git a/cookbooks/ros2_windows/recipes/rti_connext.rb b/cookbooks/ros2_windows/recipes/rti_connext.rb new file mode 100644 index 0000000..5611766 --- /dev/null +++ b/cookbooks/ros2_windows/recipes/rti_connext.rb @@ -0,0 +1,160 @@ +bin_dir_installed = (ENV.key?('RTI_OPENSSL_BIN') && Dir.exists?(ENV['RTI_OPENSSL_BIN'])) +lib_dir_installed = (ENV.key?('RTI_OPENSSL_LIB') && Dir.exists?(ENV['RTI_OPENSSL_LIB'])) +if bin_dir_installed != lib_dir_installed + Chef::Log.fatal( + 'RTI Connext has already been installed, but the environment is not properly configured. ' + + 'Uninstall Connext and retry installation.') + raise +end + +if bin_dir_installed && lib_dir_installed + log 'Previous installation exists' do + message 'RTI Connext has been previously installed, skipping.' + end + return +end + +connext_params = node['ros2_windows']['rti_connext'] + +include_recipe 'seven_zip' + +output="#{Chef::JSONCompat.to_json_pretty(node.to_hash)}" + +# These will fail if the rti_connext parameters have not been specified because the defaults are 'nil' +if connext_params['license_file'].nil? + Chef::Log.fatal("License file has not been specified"); + raise +end + +unless File.exists?(connext_params['license_file']) + Chef::Log.fatal("License file location does not exist: #{connext_params['license_file']}") + raise +end + +if connext_params['installer_dir'].nil? + Chef::Log.fatal("Installer directory does not exist"); + raise +end + +unless Dir.exists?(connext_params['installer_dir']) + Chef::Log.fatal("Installer directory does not exist: #{connext_params['installer_dir']}") + raise +end + +if connext_params['version'].nil? + Chef::Log.fatal("Version is nil, requires MAJOR.MINOR.PATCH (e.g. '5.3.1')") + raise +end + +if connext_params['edition'].nil? + Chef::Log.fatal("Edition is nil, requires one of ('evaluation', 'pro')") + raise +end + +if connext_params['min_vs_version'].nil? + Chef::Log.fatal("Minimum Visual Studio version is required (e.g. '2017')") + raise +end + +if connext_params['openssl_version'].nil? + Chef::Log.fatal("OpenSSL version is required (e.g. '1.0.2n')") + raise +end + +host_installer_filename = "rti_connext_dds-#{connext_params['version']}-#{connext_params['edition']}-host-#{connext_params['target_platform']}.exe" +host_installer_path = File.join(connext_params['installer_dir'], host_installer_filename) + +target_platform_vs_version = connext_params['target_platform'] + 'VS' + connext_params['min_vs_version'] + +openssl_installer_zip = File.join(connext_params['installer_dir'], + "openssl-#{connext_params['openssl_version']}-target-#{target_platform_vs_version}.zip") + +openssl_installer_path = File.join(connext_params['installer_dir'], + "openssl-#{connext_params['openssl_version']}-host-#{connext_params['target_platform']}.rtipkg") + +target_installer_filename = "rti_connext_dds-#{connext_params['version']}-#{connext_params['edition']}-target-#{target_platform_vs_version}.rtipkg" +target_installer_path = File.join(connext_params['installer_dir'], + target_installer_filename) + +rtipkginstall_bat = File.join(ENV['ProgramFiles'], + "rti_connext_dds-#{connext_params['version']}", + 'bin', + 'rtipkginstall.bat') + +security_plugins_host_filename = "rti_security_plugins-#{connext_params['version']}-host-#{connext_params['target_platform']}.rtipkg" +security_plugins_host_path = File.join(connext_params['installer_dir'], security_plugins_host_filename) + +security_plugins_target_filename = "rti_security_plugins-#{connext_params['version']}-target-#{target_platform_vs_version}.rtipkg" + +security_plugins_target_path = File.join(connext_params['installer_dir'], security_plugins_target_filename) + +rti_openssl_base_dir = File.join('C:/connext', + "openssl-#{connext_params['openssl_version']}", + target_platform_vs_version, + 'release') + +log 'connext_install_message' do + message 'Using the following paths for RTI Connext install: ' + + 'License file: ' + connext_params['license_file'] + + ', Host installer: ' + host_installer_path + + ', Target installer: ' + target_installer_path + + ', Openssl installer: ' + openssl_installer_path + + ', Host security plugins installer: ' + security_plugins_host_path + + ', Target security plugins installer: ' + security_plugins_target_path + + ', Installed openssl dir: ' + rti_openssl_base_dir + + ', rtipkg batch script: ' + rtipkginstall_bat + level :info +end + +directory "C:/connext" do + path "C:/connext" + action :create +end + +powershell_script 'copy_license_file' do + code "copy #{connext_params['license_file']} C:/connext/" +end + +seven_zip_archive 'openssl_zip' do + path 'C:/connext/' + source openssl_installer_zip + overwrite false +end + +windows_env 'RTI_LICENSE_FILE' do + key_name 'RTI_LICENSE_FILE' + value 'C:/connext/rti_license.dat' + action :create +end + +powershell_script 'rti_connext' do + code host_installer_path + ' --mode unattended --unattendedmodeui minimalWithDialogs --prefix "' + ENV['ProgramFiles'] + '"' +end + +execute 'rtipkginstall_openssl' do + command "\"#{rtipkginstall_bat}\" #{openssl_installer_path}" +end + +execute 'rtipkginstall_rti_connext_dds_target' do + command "\"#{rtipkginstall_bat}\" #{target_installer_path}" +end + +execute 'rtipkginstall_rti_security_plugins_host' do + command "\"#{rtipkginstall_bat}\" #{security_plugins_host_path}" +end + +execute 'rtipkginstall_rti_security_plugins_target' do + command "\"#{rtipkginstall_bat}\" #{security_plugins_target_path}" +end + +windows_env 'RTI_OPENSSL_BIN' do + key_name 'RTI_OPENSSL_BIN' + value File.join(rti_openssl_base_dir, 'bin') + action :create +end + +windows_env 'RTI_OPENSSL_LIB' do + key_name 'RTI_OPENSSL_LIB' + value File.join(rti_openssl_base_dir, 'lib') + action :create +end diff --git a/cookbooks/ros2_windows/recipes/visual_studio.rb b/cookbooks/ros2_windows/recipes/visual_studio.rb new file mode 100644 index 0000000..9df46b6 --- /dev/null +++ b/cookbooks/ros2_windows/recipes/visual_studio.rb @@ -0,0 +1,54 @@ +# For more information about command line arguments, see: +# https://docs.microsoft.com/en-us/visualstudio/install/use-command-line-parameters-to-install-visual-studio?view=vs-2019 + +# If BuildTools is not already installed, install it +packages_to_install = %w[ + Microsoft.Net.Component.4.8.SDK + Microsoft.VisualStudio.Workload.VCTools + Microsoft.Component.MSBuild + Microsoft.VisualStudio.Component.VC.CLI.Support +] + +if node['ros2_windows']['vs_version'] != 'buildtools' then + packages_to_install += ['Microsoft.VisualStudio.Workload.NativeDesktop',] +end + +package_arguments = packages_to_install.map{ |pkg| '--add ' + pkg}.join(' ') +base_options = '--quiet --wait --norestart --includeRecommended ' + +installer_options = base_options + package_arguments + +visual_studio_source = 'https://aka.ms/vs/16/release/vs_%s.exe' % node['ros2_windows']['vs_version'] + +vs_version_camel_case = { + 'buildtools' => 'BuildTools', + 'community' => 'Community', + 'professional' => 'Professional', + 'enterprise' => 'Enterprise' +}[node['ros2_windows']['vs_version']] + +visual_studio_path = 'c:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\%s' % vs_version_camel_case +windows_package 'Update VS' do + source visual_studio_source + installer_type :custom + returns [0, 3010] + options 'update ' + installer_options + timeout 1200 + only_if {::Dir.exist?(visual_studio_path)} +end + +windows_package 'Install vs_buildtools' do + source visual_studio_source + installer_type :custom + action :install + returns [0, 1605, 1614, 1641, 3010] + options installer_options + timeout 1200 + not_if {::Dir.exist?(visual_studio_path)} +end + +windows_env 'VCTargetsPath' do + key_name 'VCTargetsPath' + value File.join(visual_studio_path, 'MSBuild\\Microsoft\\VC\\v160\\') + action :create +end diff --git a/cookbooks/ros2_windows/recipes/xmllint.rb b/cookbooks/ros2_windows/recipes/xmllint.rb new file mode 100644 index 0000000..55ee445 --- /dev/null +++ b/cookbooks/ros2_windows/recipes/xmllint.rb @@ -0,0 +1,24 @@ +seven_zip_archive 'libxml2' do + path 'C:\\xmllint' + source 'https://www.zlatkovic.com/pub/libxml/64bit/libxml2-2.9.3-win32-x86_64.7z' + overwrite true +end + +seven_zip_archive 'zlib' do + path 'C:\\xmllint' + source 'https://www.zlatkovic.com/pub/libxml/64bit/zlib-1.2.8-win32-x86_64.7z' + overwrite true +end + +seven_zip_archive 'iconv' do + path 'C:\\xmllint' + source 'https://www.zlatkovic.com/pub/libxml/64bit/iconv-1.14-win32-x86_64.7z' + overwrite true +end + +windows_env 'PATH' do + key_name 'PATH' + value 'C:\\xmllint\\bin' + delim ';' + action :modify +end diff --git a/roles/ros2-binaries.json b/roles/ros2-binaries.json new file mode 100644 index 0000000..c9c07de --- /dev/null +++ b/roles/ros2-binaries.json @@ -0,0 +1,19 @@ +{ + "name": "ros2-binaries", + "description": "Install necessary ROS 2 dependencies and ROS 2 binaries", + "json_class": "Chef::Role", + "default_attributes": { + "ros2_windows": { + "development": false, + "release_version": "eloquent", + "vs_version": "buildtools", + "ros2_ws": "C:\\dev\\ros2_eloquent", + "install_connext": false, + "install_opensplice": false, + } + }, + "chef_type": "role", + "run_list": [ + "recipe[ros2_windows::ros2]" + ], +} diff --git a/roles/ros2-debug-binaries.json b/roles/ros2-debug-binaries.json new file mode 100644 index 0000000..f0d2abf --- /dev/null +++ b/roles/ros2-debug-binaries.json @@ -0,0 +1,14 @@ +{ + "name": "ros2-binaries", + "description": "Install necessary ROS 2 dependencies and ROS 2 debug binaries", + "json_class": "Chef::Role", + "default_attributes": { + "ros2_windows": { + "build_type": "debug", + } + }, + "chef_type": "role", + "run_list": [ + "role[ros2-binaries]" + ], +} diff --git a/roles/ros2-development.json b/roles/ros2-development.json new file mode 100644 index 0000000..df7254b --- /dev/null +++ b/roles/ros2-development.json @@ -0,0 +1,20 @@ +{ + "name": "ros2-development", + "description": "Install necessary dependencies and set up a machine for ROS 2 development", + "json_class": "Chef::Role", + "default_attributes": { + "ros2_windows": { + "development": "true", + "download_sources": "true", + "vs_version": "buildtools", + "source": { + "ros2.repos": "master" + }, + "ros2_ws": "C:/dev/ros2_ws", + "install_connext": "false", + "install_opensplice": "true" + } + }, + "chef_type": "role", + "run_list": [ "recipe[ros2_windows::ros2]" ] +} diff --git a/ros2_windows.json b/ros2_windows.json new file mode 100644 index 0000000..b1eba06 --- /dev/null +++ b/ros2_windows.json @@ -0,0 +1 @@ +{ "run_list": ["role[ros2-development]"]}