diff --git a/lib/openstudio/analysis/formulation.rb b/lib/openstudio/analysis/formulation.rb index c80b489..4e21120 100644 --- a/lib/openstudio/analysis/formulation.rb +++ b/lib/openstudio/analysis/formulation.rb @@ -38,6 +38,7 @@ class Formulation attr_accessor :initialize_worker_timeout attr_accessor :run_workflow_timeout attr_accessor :upload_results_timeout + # the attributes below are used for packaging data into the analysis zip file attr_reader :weather_files @@ -46,6 +47,7 @@ class Formulation attr_reader :worker_finalizes attr_reader :libraries attr_reader :server_scripts + attr_reader :gem_files # Create an instance of the OpenStudio::Analysis::Formulation # @@ -72,6 +74,7 @@ def initialize(display_name) # Analysis Zip attributes @weather_files = SupportFiles.new + @gem_files = SupportFiles.new @seed_models = SupportFiles.new @worker_inits = SupportFiles.new @worker_finalizes = SupportFiles.new @@ -321,11 +324,18 @@ def to_hash(version = 1) h[:analysis][:download_reports] = @download_reports h[:analysis][:download_osw] = @download_osw h[:analysis][:download_osm] = @download_osm - + + # If there are Gemfiles, then set the hash to use the :gemfile. The zip file method will + # add them to the root of the zip file. + if @gem_files.size.positive? + h[:analysis][:gemfile] = true + else + h[:analysis][:gemfile] = false + end + #-BLB I dont think this does anything. server_scripts are run if they are in #the /scripts/analysis or /scripts/data_point directories #but nothing is ever checked in the OSA. - # h[:analysis][:server_scripts] = {} # This is a hack right now, but after the initial hash is created go back and add in the objective functions @@ -335,6 +345,8 @@ def to_hash(version = 1) h[:analysis][:problem][:algorithm][:objective_functions] = ofs end + + h else raise "Version #{version} not defined for #{self.class} and #{__method__}" @@ -710,6 +722,12 @@ def add_directory_to_zip_osa(zipfile, local_directory, relative_zip_directory) end end + puts 'Adding Gemfiles' + @gem_files.each do |f| + puts " Adding #{f[:file]}" + zf.add(File.basename(f[:file]), f[:file]) + end + ## Measures puts 'Adding Measures' added_measures = [] @@ -826,6 +844,12 @@ def add_directory_to_zip(zipfile, local_directory, relative_zip_directory) file.close end end + + puts 'Adding Gemfiles' + @gem_files.each do |f| + puts " Adding #{f}" + zf.add(File.basename(f), f) + end ## Measures puts 'Adding Measures' diff --git a/lib/openstudio/analysis/version.rb b/lib/openstudio/analysis/version.rb index db90eb3..faf0b37 100644 --- a/lib/openstudio/analysis/version.rb +++ b/lib/openstudio/analysis/version.rb @@ -7,6 +7,6 @@ module OpenStudio module Analysis # format should be ^.*\-{1}[a-z]+[0-9]+ # for example: -rc1, -beta6, -customusecase0 - VERSION = '1.3.6'.freeze + VERSION = '1.3.7'.freeze end end diff --git a/lib/openstudio/analysis/workflow.rb b/lib/openstudio/analysis/workflow.rb index 7f43ced..15cb842 100644 --- a/lib/openstudio/analysis/workflow.rb +++ b/lib/openstudio/analysis/workflow.rb @@ -184,6 +184,29 @@ def find_measure(instance_name) end alias find_workflow_step find_measure + # Move a measure by its name to after another measure by its name, + # error if there is no measure with the name. If no after measure + # name is passed, then it will be at the beginning + # + # @params measure_name [String] instance name of the measure + # @params after_measure_name [String] instance name of the measure to move after + def move_measure_after(measure_name, after_measure_name=nil) + measure = self.find_measure(measure_name) + raise "Could not find measure with name #{measure_name}" unless measure + + if after_measure_name.nil? + # put the measure at the beginning + @items.insert(0, @items.delete(measure)) + else + after_measure = self.find_measure(after_measure_name) + raise "Could not find measure with name #{after_measure_name}" unless after_measure + + # the index will be the index of the after measure plus 1 or the len of the list + idx = [@items.index(after_measure)+1, @items.length-1].min + @items.insert(idx, @items.delete(measure)) + end + end + # Return all the variables in the analysis as an array. The list that is returned is read only. # # @return [Array] All variables in the workflow diff --git a/spec/files/gem_files/Gemfile b/spec/files/gem_files/Gemfile new file mode 100644 index 0000000..4161ff2 --- /dev/null +++ b/spec/files/gem_files/Gemfile @@ -0,0 +1,32 @@ +# gems in this file are packaged into openstudio.exe +# gems listed here should not have binary components +# gems listed here must be able to read resource files from the embedded files location +# need to adjust hard coded paths in embedded_help.rb when adding new gems +source 'http://rubygems.org' +ruby "~> 2.7.0" + +# Specify gem's dependencies in openstudio-gems.gemspec, this is what consumers of the gem will read +gemspec + +# Specify specific gem source/location (e.g. github branch) for running bundle in this directory +# This is needed if the version of the gem you want to use is not on rubygems + +# Bug in addressable to 2.8.1 and patched version has an issue https://github.com/NREL/OpenStudio/issues/4870 +gem 'addressable', '= 2.8.1' + +gem 'openstudio-extension', '= 0.7.1' +gem 'openstudio-workflow', '= 2.3.1' +gem 'openstudio-standards', '= 0.4.0' +gem 'tbd', :github => 'rd2/tbd', :ref => 'v3.2.2' +gem 'openstudio_measure_tester', '= 0.3.2' +gem 'urbanopt-reporting', '~> 0.9.1' + +group :native_ext do + gem 'pycall', '= 1.2.1', :github => 'NREL/pycall.rb', :ref => '5d60b274ac646cdb422a436aad98b40ef8b902b8' + gem 'jaro_winkler', '= 1.5.4', :github => 'jmarrec/jaro_winkler', :ref => 'f1ca425fdef06603e5c65b09c5b681f805e1e297' + gem 'sqlite3', :github => 'jmarrec/sqlite3-ruby', :ref => 'MSVC_support' + # You need ragel available (version 6.x, eg `ragel_installer/6.10@bincrafters/stable` from conan) + gem 'oga', '3.2' + # gem 'cbor', '0.5.9.6' # Cbor will require a ton of patching, so disabling it in favor of msgpack (cbor is a fork of msgpack anyways) + gem 'msgpack', '1.4.2' +end diff --git a/spec/files/gem_files/openstudio-gems.gemspec b/spec/files/gem_files/openstudio-gems.gemspec new file mode 100644 index 0000000..f24e1ca --- /dev/null +++ b/spec/files/gem_files/openstudio-gems.gemspec @@ -0,0 +1,35 @@ +$static = true + +Gem::Specification.new do |spec| + spec.name = 'openstudio-gems' + spec.version = '3.6.0' + spec.authors = ['Nicholas Long', 'Dan Macumber', 'Katherine Fleming'] + spec.email = ['nicholas.long@nrel.gov', 'daniel.macumber@nrel.gov', 'katherine.fleming@nrel.gov'] + + spec.summary = 'Build openstudio-gems for OpenStudio CLI and coordinate dependencies for OpenStudio Extension Gems' + spec.description = 'Build openstudio-gems for OpenStudio CLI and coordinate dependencies for OpenStudio Extension Gems' + spec.homepage = 'https://openstudio.net' + + spec.files = [] + spec.bindir = '' + spec.executables = [] + spec.require_paths = ['lib'] + + # gem version is specified in gemspec, gem source/location (e.g. github branch) can be specified in Gemfile + # runtime dependency versions can be loosened while in development on branches if needed + # runtime dependency versions should be specified as exact versions when merged to master or develop + spec.add_dependency 'openstudio-extension', '0.7.1' + spec.add_dependency 'openstudio-workflow', '2.3.1' + spec.add_dependency 'openstudio_measure_tester', '~> 0.3.2' + + spec.add_dependency 'parallel', '1.19.1' + + # 20200324 TJC json native is enabled for all platforms and is included when building ruby + #spec.add_dependency 'json_pure', '2.2' + + # development dependencies need not be specified so strictly + # these will not be enforced by consumers of this spec + # bundle version is parsed by build_openstudio_gems.rb, specify all three numbers + spec.add_development_dependency 'bundler', '>= 2.1.0' + spec.add_development_dependency 'rake', '~> 13.0' +end diff --git a/spec/openstudio/server_scripts_spec.rb b/spec/openstudio/server_scripts_spec.rb index 0a0046b..ad8a110 100644 --- a/spec/openstudio/server_scripts_spec.rb +++ b/spec/openstudio/server_scripts_spec.rb @@ -35,7 +35,20 @@ @s.clear expect(@s.size).to eq 0 + end + + it 'should add a Gemfile' do + # test with an entire analysis workflow + osa = OpenStudio::Analysis.create('Name of an analysis') + osa.gem_files.add('spec/files/gem_files/Gemfile') + osa.gem_files.add('spec/files/gem_files/openstudio-gems.gemspec') + + os_json = osa.to_hash + puts JSON.pretty_generate(os_json) + expect(os_json[:analysis][:gemfile]).to be true + + # not sure how to test with the zip... end - end \ No newline at end of file +end \ No newline at end of file diff --git a/spec/openstudio/workflow_spec.rb b/spec/openstudio/workflow_spec.rb index b8b3332..a952f7e 100644 --- a/spec/openstudio/workflow_spec.rb +++ b/spec/openstudio/workflow_spec.rb @@ -62,6 +62,41 @@ expect(m.name).to eq 'thermostat_2' end + it 'should find move a workflow step to after a named step' do + @w.clear + + p = 'spec/files/measures/SetThermostatSchedules' + m1 = @w.add_measure_from_path('thermostat_1', 'thermostat', p) + m2 = @w.add_measure_from_path('thermostat_2', 'thermostat 2', p) + m3 = @w.add_measure_from_path('thermostat_3', 'thermostat 3', p) + m4 = @w.add_measure_from_path('thermostat_4', 'thermostat 4', p) + + measure_names = @w.measures.map(&:name) + expect(measure_names).to eq ['thermostat_1', 'thermostat_2', 'thermostat_3', 'thermostat_4'] + # find the index where the measure is in the workflow + @w.move_measure_after('thermostat_4', 'thermostat_2') + # @w.measures.insert(@w.measures.index(m2), @w.measures.delete_at(@w.measures.index(m4))) + # should return thermostat, thermostat_2, thermostat_4, thermostat_3 + + measure_names = @w.measures.map(&:name) + expect(measure_names).to eq ['thermostat_1', 'thermostat_2', 'thermostat_4', 'thermostat_3'] + + # now move it to the end + @w.move_measure_after('thermostat_4', 'thermostat_3') + measure_names = @w.measures.map(&:name) + expect(measure_names).to eq ['thermostat_1', 'thermostat_2', 'thermostat_3', 'thermostat_4'] + + # now move it to the beginning -- do not say the after measure + @w.move_measure_after('thermostat_4') + measure_names = @w.measures.map(&:name) + expect(measure_names).to eq ['thermostat_4', 'thermostat_1', 'thermostat_2', 'thermostat_3'] + + # TODO: verify that errors are thrown when measures do not exist + + end + + + it 'should find a workflow step and make a variable' do @w.clear