1+ # Dragonfly: A Plugin for Environmental Analysis (GPL)
2+ # This file is part of Dragonfly.
3+ #
4+ # Copyright (c) 2019, Ladybug Tools.
5+ # You should have received a copy of the GNU General Public License
6+ # along with Dragonfly; If not, see <http://www.gnu.org/licenses/>.
7+ #
8+ # @license GPL-3.0+ <http://spdx.org/licenses/GPL-3.0+>
9+
10+ """
11+ This component updates the Ladybug Tools core libraries and grasshopper components
12+ to either the latest development version available (default) or to a specific
13+ version of the grasshopper plugin.
14+ _
15+ The input version_ does not need to be newer than the current installation and can
16+ be older but grasshopper plugin versions less than 0.3.0 are not supported.
17+ A list of all versions of the Grasshopper plugin and corresponding release notes
18+ can be found at: https://github.com/ladybug-tools/lbt-grasshopper/releases
19+ _
20+ This component can also overwrite the user libraries of standards (constructions,
21+ schedules, modifiers) with a completely fresh copy if clean_standards_ is set to True.
22+ -
23+
24+ Args:
25+ _update: Set to True to update your installation of Ladybug Tools to the
26+ latest development version or to be at the version specified below.
27+ version_: An optional text string for the version of the grasshopper
28+ plugin which you would like to update to. The input should contain
29+ only integers separated by two periods (eg. 1.0.0). The version does
30+ not need to be newer than the current installation and can be older
31+ but grasshopper plugin versions less than 0.3.0 are not supported.
32+ A list of all versions of the Grasshopper plugin can be found
33+ here - https://github.com/ladybug-tools/lbt-grasshopper/releases
34+ clean_standards_: Set to True to have any user libraries of standards
35+ (constructions, schedules, modifiers) overwritten with a
36+ completely fresh copy. If False or None, any existing standards
37+ will be left alone.
38+
39+ Returns:
40+ Vviiiiiz!: !!!
41+ """
42+
43+ ghenv .Component .Name = 'LB Versioner'
44+ ghenv .Component .NickName = 'Versioner'
45+ ghenv .Component .Message = '0.1.0'
46+ ghenv .Component .Category = 'Ladybug'
47+ ghenv .Component .SubCategory = '5 :: Version'
48+ ghenv .Component .AdditionalHelpFromDocStrings = '1'
49+
50+ try :
51+ from ladybug .futil import preparedir , nukedir , copy_file_tree , unzip_file
52+ from ladybug .config import folders
53+ except ImportError as e :
54+ raise ImportError ('\n Failed to import ladybug:\n \t {}' .format (e ))
55+
56+ try :
57+ from ladybug_rhino .download import download_file_by_name
58+ from ladybug_rhino .grasshopper import all_required_inputs , give_warning
59+ except ImportError as e :
60+ raise ImportError ('\n Failed to import ladybug_rhino:\n \t {}' .format (e ))
61+
62+ import os
63+ import subprocess
64+ from Grasshopper .Folders import UserObjectFolders
65+
66+
67+ def get_python_exe ():
68+ """Get the path to the Python installed in the ladybug_tools folder.
69+
70+ Will be None if Python is not installed.
71+ """
72+ py_update = os .path .join (folders .ladybug_tools_folder , 'python' )
73+ py_exe_file = os .path .join (py_update , 'python.exe' ) if os .name == 'nt' else \
74+ os .path .join (py_update , 'bin' , 'python3' )
75+ py_site_pack = os .path .join (py_update , 'Lib' , 'site-packages' ) if os .name == 'nt' else \
76+ os .path .join (py_update , 'lib' , 'python3.8' , 'site-packages' )
77+ if os .path .isfile (py_exe_file ):
78+ return py_exe_file , py_site_pack
79+ return None , None
80+
81+
82+ def get_gem_directory ():
83+ """Get the directory where measures distributed with Ladybug Tools are installed."""
84+ measure_folder = os .path .join (folders .ladybug_tools_folder , 'resources' , 'measures' )
85+ if not os .path .isdir (measure_folder ):
86+ os .makedirs (measure_folder )
87+ return measure_folder
88+
89+
90+ def get_standards_directory ():
91+ """Get the directory where Honeybee standards are installed."""
92+ hb_folder = os .path .join (folders .ladybug_tools_folder , 'resources' , 'standards' )
93+ if not os .path .isdir (hb_folder ):
94+ os .makedirs (hb_folder )
95+ return hb_folder
96+
97+
98+ def update_libraries_pip (python_exe , package_name , version = None , target = None ):
99+ """Change python libraries to be of a specific version using pip.
100+
101+ Args:
102+ python_exe: The path to the Python executable to be used for installation.
103+ package_name: The name of the PyPI package to install.
104+ version: An optional string for the version of the package to install.
105+ If None, the library will be updated to the latest version with -U.
106+ target: An optional target directory into which the package will be installed.
107+ """
108+ # build up the command using the inputs
109+ if version is not None :
110+ package_name = '{}=={}' .format (package_name , version )
111+ cmds = [python_exe , '-m' , 'pip' , 'install' , package_name ]
112+ if version is None :
113+ cmds .append ('-U' )
114+ if target is not None :
115+ cmds .extend (['--target' , target , '--upgrade' ])
116+
117+ # execute the command and print any errors
118+ print ('Installing "{}" version via pip' .format (package_name ))
119+ use_shell = True if os .name == 'nt' else False
120+ process = subprocess .Popen (
121+ cmds , shell = use_shell , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
122+ output = process .communicate ()
123+ stdout , stderr = output
124+ return stderr
125+
126+
127+ def download_repo_github (repo , target_directory , version = None ):
128+ """Download a repo of a particular version from from github.
129+
130+ Args:
131+ repo: The name of a repo to be downloaded (eg. 'lbt-grasshopper').
132+ target_directory: the directory where the library should be downloaded to.
133+ version: The version of the repository to download. If None, the most
134+ recent version will be downloaded. (Default: None)
135+ """
136+ # download files
137+ if version is None :
138+ url = "https://github.com/ladybug-tools/{}/archive/master.zip" .format (repo )
139+ else :
140+ url = "https://github.com/ladybug-tools/{}/archive/v{}.zip" .format (repo , version )
141+ zip_file = os .path .join (target_directory , '%s.zip' % repo )
142+ print 'Downloading "{}" github repository to: {}' .format (repo , target_directory )
143+ download_file_by_name (url , target_directory , zip_file )
144+
145+ #unzip the file
146+ unzip_file (zip_file , target_directory )
147+
148+ # try to clean up the downloaded zip file
149+ try :
150+ os .remove (zip_file )
151+ except :
152+ print 'Failed to remove downloaded zip file: {}.' .format (zip_file )
153+
154+ # return the directory where the unzipped files live
155+ if version is None :
156+ return os .path .join (target_directory , '{}-master' .format (repo ))
157+ else :
158+ return os .path .join (target_directory , '{}-{}' .format (repo , version ))
159+
160+
161+ def parse_lbt_gh_versions (lbt_gh_folder ):
162+ """Parse versions of compatible libs from a clone of the lbt-grasshopper repo.
163+
164+ Args:
165+ lbt_gh_folder: Path to the clone of the lbt-grasshopper repo
166+
167+ Returns:
168+ A dictionary of library versions formatted like so (but with actual version
169+ numbers in place of '0.0.0':
170+
171+ {
172+ 'lbt-dragonfly' = '0.0.0',
173+ 'ladybug-rhino' = '0.0.0',
174+ 'lbt-grasshopper' = '0.0.0',
175+ 'honeybee-openstudio-gem' = '0.0.0',
176+ 'honeybee-standards' = '0.0.0',
177+ 'honeybee-energy-standards' = '0.0.0',
178+ 'ladybug-grasshopper': '0.0.0',
179+ 'honeybee-grasshopper-core': '0.0.0',
180+ 'honeybee-grasshopper-radiance': '0.0.0',
181+ 'honeybee-grasshopper-energy': '0.0.0',
182+ 'dragonfly-grasshopper': '0.0.0'
183+ }
184+ """
185+ # set the names of the libraries to collect and the version dict
186+ version_dict = {
187+ 'lbt-dragonfly' : None ,
188+ 'ladybug-rhino' : None ,
189+ 'honeybee-standards' : None ,
190+ 'honeybee-energy-standards' : None ,
191+ 'honeybee-openstudio-gem' : None ,
192+ 'ladybug-grasshopper' : None ,
193+ 'honeybee-grasshopper-core' : None ,
194+ 'honeybee-grasshopper-radiance' : None ,
195+ 'honeybee-grasshopper-energy' : None ,
196+ 'dragonfly-grasshopper' : None
197+ }
198+ libs_to_collect = list (version_dict .keys ())
199+
200+ def search_versions (version_file ):
201+ """Search for version numbers within a .txt file."""
202+ with open (version_file ) as ver_file :
203+ for row in ver_file :
204+ try :
205+ library , version = row .strip ().split ('==' )
206+ if library in libs_to_collect :
207+ version_dict [library ] = version
208+ except Exception : # not a row with a ladybug tools library
209+ pass
210+
211+ # search files for versions
212+ requirements = os .path .join (lbt_gh_folder , 'requirements.txt' )
213+ dev_requirements = os .path .join (lbt_gh_folder , 'dev-requirements.txt' )
214+ ruby_requirements = os .path .join (lbt_gh_folder , 'ruby-requirements.txt' )
215+ search_versions (requirements )
216+ search_versions (dev_requirements )
217+ search_versions (ruby_requirements )
218+ return version_dict
219+
220+
221+ if all_required_inputs (ghenv .Component ) and _update is True :
222+ # ensure that Python has been installed in the ladybug_tools folder
223+ py_exe , py_lib = get_python_exe ()
224+ assert py_exe is not None , \
225+ 'No Python instalation was found at: {}.\n This is a requirement in ' \
226+ 'order to contine with installation' .format (
227+ os .path .join (folders .ladybug_tools_folder , 'python' ))
228+
229+ # get the compatiable versions of all the dependencies
230+ temp_folder = os .path .join (folders .ladybug_tools_folder , 'temp' )
231+ lbt_gh_folder = download_repo_github ('lbt-grasshopper' , temp_folder , version_ )
232+ ver_dict = parse_lbt_gh_versions (lbt_gh_folder )
233+ ver_dict ['lbt-grasshopper' ] = version_
234+
235+ # install the core libraries
236+ print 'Installing Ladybug Tools core Python libraries.'
237+ df_ver = ver_dict ['lbt-dragonfly' ]
238+ stderr = update_libraries_pip (py_exe , 'lbt-dragonfly[cli]' , df_ver )
239+ if os .path .isdir (os .path .join (py_lib , 'lbt_dragonfly-{}.dist-info' .format (df_ver ))):
240+ print 'Ladybug Tools core Python libraries successfully installed!\n '
241+ else :
242+ give_warning (ghenv .Component , stderr )
243+ print stderr
244+
245+ # install the library needed for interaction with Rhino
246+ print 'Installing ladybug-rhino Python library.'
247+ rh_ver = ver_dict ['ladybug-rhino' ]
248+ stderr = update_libraries_pip (py_exe , 'ladybug-rhino[cli]' , rh_ver )
249+ if os .path .isdir (os .path .join (py_lib , 'ladybug_rhino-{}.dist-info' .format (rh_ver ))):
250+ print 'Ladybug-rhino Python library successfully installed!\n '
251+ else :
252+ give_warning (ghenv .Component , stderr )
253+ print stderr
254+
255+ # install the grasshopper components
256+ print 'Installing Ladybug Tools Grasshopper components.'
257+ gh_ver = ver_dict ['lbt-grasshopper' ]
258+ uo_folder = UserObjectFolders [0 ]
259+ stderr = update_libraries_pip (py_exe , 'lbt-grasshopper' , gh_ver , uo_folder )
260+ lbgh_ver = ver_dict ['ladybug-grasshopper' ]
261+ if os .path .isdir (os .path .join (uo_folder , 'ladybug_grasshopper-{}.dist-info' .format (lbgh_ver ))):
262+ print 'Ladybug Tools Grasshopper components successfully installed!\n '
263+ else :
264+ give_warning (ghenv .Component , stderr )
265+ print stderr
266+
267+ # install the honeybee-openstudio ruby gem
268+ gem_ver = ver_dict ['honeybee-openstudio-gem' ]
269+ print 'Installing Honeybee-OpenStudio gem version {}.' .format (gem_ver )
270+ gem_dir = get_gem_directory ()
271+ base_folder = download_repo_github ('honeybee-openstudio-gem' , gem_dir , gem_ver )
272+ source_folder = os .path .join (base_folder , 'lib' )
273+ lib_folder = os .path .join (gem_dir , 'honeybee_openstudio_gem' , 'lib' )
274+ print 'Copying "honeybee_openstudio_gem" source code to {}\n ' .format (lib_folder )
275+ copy_file_tree (source_folder , lib_folder )
276+ nukedir (base_folder , True )
277+
278+ # install the standards libraries
279+ standards = ['honeybee-standards' , 'honeybee-energy-standards' ]
280+ stand_versions = [ver_dict ['honeybee-standards' ], ver_dict ['honeybee-energy-standards' ]]
281+ stand_dir = get_standards_directory ()
282+ if not clean_standards_ :
283+ new_standards , new_stand_versions = [], []
284+ for i , pkg_name in enumerate (standards ):
285+ lib_folder = os .path .join (stand_dir , pkg_name .replace ('-' , '_' ))
286+ if not os .path .isdir (lib_folder ):
287+ new_standards .append (standards [i ])
288+ new_stand_versions .append (stand_versions [i ])
289+ standards , stand_versions = new_standards , new_stand_versions
290+ if len (standards ) != 0 :
291+ print 'Installing Ladybug Tools standards libraries (constructions, schedules, etc.).'
292+ for pkg_name , ver in zip (standards , stand_versions ):
293+ stderr = update_libraries_pip (py_exe , pkg_name , ver , stand_dir )
294+ if os .path .isdir (os .path .join (stand_dir , '{}-{}.dist-info' .format (pkg_name .replace ('-' , '_' ), ver ))):
295+ print 'Ladybug Tools Grasshopper standards libraries successfully installed!\n '
296+ else :
297+ give_warning (ghenv .Component , stderr )
298+ print stderr
299+
300+ # delete the temp folder and give a completion message
301+ nukedir (temp_folder , True )
302+ print 'Update successful!'
303+ print 'Restart Grasshopper and Rhino to load the new components + library.'
304+ else : # give a message to the user about what to do
305+ print 'Make sure you are connected to the internet and set _update to True!'
0 commit comments