diff --git a/README.md b/README.md index e020537..ba36c5a 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,9 @@ The loomlib rake tasks make the following assumptions about the layout of a proj #### lib +`lib/` is for the library code, which will be packaged into a `.loomlib` file for installation into a LoomSDK.
+Support for test tasks comes from `loomlib.rake`. + ├─lib │ ├─assets │ ├─bin @@ -60,6 +63,9 @@ This is used to name the loomlib that gets compiled (and anticipates a correspon #### test +`test/` is for unit tests of the library code. The tests are not packaged with the loomlib; they are run from a separate test runner app.
+Support for test tasks comes from `loomlib.rake`. + └─test ├─assets ├─bin @@ -74,10 +80,30 @@ This is used to name the loomlib that gets compiled (and anticipates a correspon * the test application wil be built into `test/bin/` * the tests have their own loom config file at `test/loom.config` -* the tests have their own loom build file at `test/src/Foo.build` +* the tests have their own loom build file at `test/src/FooTest.build` * the test application source code is under `test/src/app/` * the specification source code is under `test/src/spec/` +##### demo + +`demo/` is for a functional demonstration app. It may be GUI or commandline.
+Support for demo tasks comes from `loomlib_demo.rake`. + + └─test + ├─assets + ├─bin + │ └─FooTest.loom + ├─loom.config + └─src + ├─demo + │ └─FooDemo.ls + └─FooDemo.build + +* the demo application wil be built into `test/bin/` +* the demo shares its loom config file with the test app at `test/loom.config` +* the demo has its own loom build file at `test/src/FooDemo.build` +* the demo source code is under `test/src/demo/` + ## installation @@ -100,31 +126,48 @@ LIB_NAME = 'Foo' LIB_VERSION_FILE = File.join('lib', 'src', 'com', 'bar', 'Foo.ls') ``` -Then load the tasks: +Then load the tasks you want to use: ```ruby load(File.join(ENV['HOME'], '.loom', 'tasks', 'loomlib.rake')) +load(File.join(ENV['HOME'], '.loom', 'tasks', 'loomlib_demo.rake')) # optional ``` -> Note: your whole Rakefile may be just those three lines if there isn't anything else you need to do +> Note: your whole Rakefile may be just those three or four lines if there isn't anything else you need to do Now run `rake` to execute the default task, which will print the list of available tasks and some useful info: Foo v1.2.3 Rakefile running on Ruby 2.1.1 (lib=sprint33, test=sprint33) - rake clean # Remove any temporary products - rake clobber # Remove any generated file - rake lib:build # builds Foo.loomlib for the SDK specified in lib/loom.config - rake lib:install # installs Foo.loomlib into the SDK specified in lib/loom.config - rake lib:release # prepares sdk-specific Foo.loomlib for release - rake lib:show # lists libs installed for the SDK specified in lib/loom.config - rake lib:uninstall # removes Foo.loomlib from the SDK specified in lib/loom.config - rake set[sdk] # sets the provided SDK version into lib/loom.config and test/loom.config - rake test:build # builds FooTest.loom with the SDK specified in test/loom.config - rake test:ci # runs FooTest.loom for CI - rake test:run # runs FooTest.loom - (using loomlib.rake v1.0.0) - -The Rake tasks are defined with dependencies and modification triggers, so you can just run `rake test:run` every time you edit a source file, and the library and test app will be rebuilt as needed automatically. + rake clean # removes intermediate files to ensure a clean build + rake clobber # removes all generated artifacts to restore project to checkout-like state + rake demo:build # builds FooDemo.loom for sprint33 SDK + rake demo:cli[options] # executes FooDemo.loom as a commandline app, with options + rake demo:gui # launches FooDemo.loom as a GUI app + rake lib:build # builds Foo.loomlib for sprint33 SDK + rake lib:install # installs Foo.loomlib into sprint33 SDK + rake lib:release # prepares sdk-specific Foo.loomlib for release, and updates version in README + rake lib:show # lists libs installed for sprint33 SDK + rake lib:uninstall # removes Foo.loomlib from sprint33 SDK + rake set[sdk] # sets the provided SDK version into lib/loom.config and test/loom.config + rake test # shorthand for rake test:run + rake test:build # builds FooTest.loom against sprint33 SDK + rake test:ci # runs FooTest.loom for CI + rake test:run # runs FooTest.loom for the console + (using loomtasks v1.1.0) + + use `rake -D` for more detailed task descriptions + +If you are looking for more detail on any of the tasks, use `rake -D`: + +```console +$ rake -D set +rake set[sdk] + sets the provided SDK version into lib/loom.config and test/loom.config + lib/loom.config defines which SDK will be used to compile the loomlib, and also where to install it + test/loom.config defines which SDK will be used to compile the test app and demo app +``` + +The Rake tasks are defined with dependencies and modification triggers, so you can just run `rake test` every time you edit a source file, and the library and test app will be rebuilt as needed automatically. ## contributing diff --git a/Rakefile b/Rakefile index 0bc77e5..b4d1163 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,3 @@ -# encoding: utf-8 require File.join(File.dirname(__FILE__), 'lib', 'tasks', 'support') include LoomTasks @@ -12,23 +11,27 @@ def installed_tasks_dir() File.join(Dir.home, '.loom', 'tasks') end - task :default => :list_targets task :list_targets do |t, args| - a = "#{File.basename(File.dirname(__FILE__))} Rakefile" + a = "loomtasks v#{VERSION} Rakefile" b = "running on Ruby #{RUBY_VERSION}" puts "#{a} #{b}" system("rake -T") puts '' + puts 'use `rake -D` for more detailed task descriptions' + puts '' end -desc "installs rake tasks for Loom" +desc [ + "installs rake task files for Loom", + "task files are installed to #{installed_tasks_dir}" +].join("\n") task :install do |t, args| Dir.mkdir(installed_tasks_dir) unless Dir.exists?(installed_tasks_dir) - cmd = "cp lib/tasks/*.rake lib/tasks/*.rb #{installed_tasks_dir}" - try(cmd, "failed to install tasks") + FileUtils.cp_r(Dir.glob(File.join('lib', 'tasks', '*.rake')), installed_tasks_dir) + FileUtils.cp_r(Dir.glob(File.join('lib', 'tasks', '*.rb')), installed_tasks_dir) puts "[#{t.name}] task completed, tasks installed to #{installed_tasks_dir}" puts '' @@ -36,11 +39,14 @@ end namespace :list do - desc "lists tasks available to install" + desc [ + "lists task files available to install", + "task files from this project are in #{available_tasks_dir}" + ].join("\n") task :available do |t, args| if Dir.exists?(available_tasks_dir) - cmd = "ls -1 #{available_tasks_dir}/" - try(cmd, "failed to list contents of #{available_tasks_dir} directory") + puts("available tasks in #{available_tasks_dir}") + Dir.glob("#{available_tasks_dir}/*").each { |f| puts(File.basename(f)) } else puts "[#{t.name}] no tasks are installed at #{available_tasks_dir}" end @@ -48,11 +54,14 @@ namespace :list do puts '' end - desc "lists currently installed tasks" + desc [ + "lists currently installed task files", + "installed task files are in #{installed_tasks_dir}" + ].join("\n") task :installed do |t, args| if Dir.exists?(installed_tasks_dir) - cmd = "ls -1 #{installed_tasks_dir}/" - try(cmd, "failed to list contents of #{installed_tasks_dir} directory") + puts("installed tasks in #{installed_tasks_dir}") + Dir.glob("#{installed_tasks_dir}/*").each { |f| puts(File.basename(f)) } else puts "[#{t.name}] no tasks are installed at #{installed_tasks_dir}" end @@ -62,7 +71,11 @@ namespace :list do end -desc "removes the tasks folder from the Loom SDK" +desc [ + "removes the tasks folder from the Loom home directory", + "the task folder is #{installed_tasks_dir}", + "the entire tasks folder is removed, so use with caution if you happened to put anything in there", +].join("\n") task :uninstall do |t, args| FileUtils.rm_r(installed_tasks_dir) if Dir.exists?(installed_tasks_dir) diff --git a/lib/tasks/loomlib.rake b/lib/tasks/loomlib.rake index 143f76c..e9bbf59 100644 --- a/lib/tasks/loomlib.rake +++ b/lib/tasks/loomlib.rake @@ -1,4 +1,3 @@ -# encoding: utf-8 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # loomlib.rake - common Rake tasks for loomlib projects @@ -8,8 +7,6 @@ # # load(File.join(ENV['HOME'], '.loom', 'tasks', 'loomlib.rake')) -LOOMLIB_VERSION = '1.0.0' - require 'etc' require 'fileutils' require 'json' @@ -67,9 +64,21 @@ end @lib_loom_config = nil @test_loom_config = nil -CLEAN.include ['lib/build/**', 'test/bin/**'] +CLEAN.include ['lib/build/**', 'test/bin/**', 'TEST-*.xml'] +Rake::Task[:clean].clear_comments() +Rake::Task[:clean].add_description([ + "removes intermediate files to ensure a clean build", + "running now would delete #{CLEAN.length} files:\n #{CLEAN.join("\n ")}", +].join("\n")) + CLOBBER.include ['lib/build', 'test/bin', 'releases'] -Rake::Task[:clobber].enhance ['lib:uninstall'] +Rake::Task[:clobber].enhance(['lib:uninstall']) +Rake::Task[:clobber].clear_comments() +Rake::Task[:clobber].add_description([ + "removes all generated artifacts to restore project to checkout-like state", + "uninstalls the library from the current lib sdk (#{lib_config['sdk_version']})", + "removes the following folders:\n #{CLOBBER.join("\n ")}", +].join("\n")) task :default => :list_targets @@ -81,7 +90,9 @@ task :list_targets => :check_consts do |t, args| d = "test=#{test_config['sdk_version']}" puts "#{a} #{b} (#{c}, #{d})" system("rake -T") - puts "(using #{File.basename(__FILE__)} v#{LOOMLIB_VERSION})" + puts "(using loomtasks v#{LoomTasks::VERSION})" + puts '' + puts 'use `rake -D` for more detailed task descriptions' puts '' end @@ -93,10 +104,6 @@ end LIBRARY = "lib/build/#{const_lib_name}.loomlib" -FileList['lib/src/**/*.ls'].each do |src| - file LIBRARY => src -end - file LIBRARY do |t, args| puts "[file] creating #{t.name}..." @@ -104,39 +111,51 @@ file LIBRARY do |t, args| Dir.chdir('lib') do Dir.mkdir('build') unless Dir.exists?('build') - cmd = "#{sdk_root}/#{sdk_version}/tools/lsc #{const_lib_name}.build" + cmd = "#{lsc(sdk_version)} #{const_lib_name}.build" try(cmd, "failed to compile .loomlib") end puts '' end +FileList['lib/src/**/*.ls'].each do |src| + file LIBRARY => src +end -APP = "test/bin/#{const_lib_name}Test.loom" -FileList['test/src/**/*.ls'].each do |src| - file APP => src -end +APP = "test/bin/#{const_lib_name}Test.loom" file APP => LIBRARY do |t, args| puts "[file] creating #{t.name}..." sdk_version = test_config['sdk_version'] - file_installed = "#{sdk_root}/#{sdk_version}/libs/#{const_lib_name}.loomlib" + file_installed = File.join(libs_path(sdk_version), "#{const_lib_name}.loomlib") Rake::Task['lib:install'].invoke unless FileUtils.uptodate?(file_installed, [LIBRARY]) Dir.chdir('test') do Dir.mkdir('bin') unless Dir.exists?('bin') - cmd = "#{sdk_root}/#{sdk_version}/tools/lsc #{const_lib_name}Test.build" + cmd = "#{lsc(sdk_version)} #{const_lib_name}Test.build" try(cmd, "failed to compile .loom") end puts '' end +FileList['test/src/app/*.ls'].each do |src| + file APP => src +end -desc "sets the provided SDK version into lib/loom.config and test/loom.config" +FileList['test/src/spec/*.ls'].each do |src| + file APP => src +end + + +desc [ + "sets the provided SDK version into lib/loom.config and test/loom.config", + "lib/loom.config defines which SDK will be used to compile the loomlib, and also where to install it", + "test/loom.config defines which SDK will be used to compile the test app and demo app", +].join("\n") task :set, [:sdk] => 'lib:uninstall' do |t, args| args.with_defaults(:sdk => 'sprint33') sdk_version = args.sdk @@ -153,19 +172,29 @@ end namespace :lib do - desc "builds #{const_lib_name}.loomlib for the SDK specified in lib/loom.config" + desc [ + "builds #{const_lib_name}.loomlib for #{lib_config['sdk_version']} SDK", + "the SDK is specified in test/loom.config", + "you can change the SDK with rake set[sdk]", + "the .loomlib binary is created in lib/build", + ].join("\n") task :build => LIBRARY do |t, args| puts "[#{t.name}] task completed, find .loomlib in lib/build/" puts '' end - desc "prepares sdk-specific #{const_lib_name}.loomlib for release" + desc [ + "prepares sdk-specific #{const_lib_name}.loomlib for release, and updates version in README", + "the version value will be read from #{LIB_VERSION_FILE}", + "it must match this regex: #{lib_version_regex}", + ].join("\n") task :release => LIBRARY do |t, args| lib = "lib/build/#{const_lib_name}.loomlib" sdk = lib_config['sdk_version'] ext = '.loomlib' release_dir = 'releases' + puts "[#{t.name}] updating README to reference version #{lib_version}" update_readme_version() Dir.mkdir(release_dir) unless Dir.exists?(release_dir) @@ -177,27 +206,29 @@ namespace :lib do puts '' end - desc "installs #{const_lib_name}.loomlib into the SDK specified in lib/loom.config" + desc [ + "installs #{const_lib_name}.loomlib into #{lib_config['sdk_version']} SDK", + "this makes it available to reference in .build files of any project targeting #{lib_config['sdk_version']}", + ].join("\n") task :install => LIBRARY do |t, args| - lib = "lib/build/#{const_lib_name}.loomlib" sdk_version = lib_config['sdk_version'] - libs_path = "#{sdk_root}/#{sdk_version}/libs" + lib = File.join('lib', 'build', "#{const_lib_name}.loomlib") - cmd = "cp #{lib} #{libs_path}" - try(cmd, "failed to install lib") + FileUtils.cp(lib, libs_path(sdk_version)) puts "[#{t.name}] task completed, #{const_lib_name}.loomlib installed for #{sdk_version}" puts '' end - desc "removes #{const_lib_name}.loomlib from the SDK specified in lib/loom.config" + desc [ + "removes #{const_lib_name}.loomlib from #{lib_config['sdk_version']} SDK", + ].join("\n") task :uninstall do |t, args| sdk_version = lib_config['sdk_version'] - lib = "#{sdk_root}/#{sdk_version}/libs/#{const_lib_name}.loomlib" + lib = File.join(libs_path(sdk_version), "#{const_lib_name}.loomlib") if (File.exists?(lib)) - cmd = "rm -f #{lib}" - try(cmd, "failed to remove lib") + FileUtils.rm_r(lib) puts "[#{t.name}] task completed, #{const_lib_name}.loomlib removed from #{sdk_version}" else puts "[#{t.name}] nothing to do; no #{const_lib_name}.loomlib found in #{sdk_version} sdk" @@ -205,12 +236,17 @@ namespace :lib do puts '' end - desc "lists libs installed for the SDK specified in lib/loom.config" + desc [ + "lists libs installed for #{lib_config['sdk_version']} SDK", + "the SDK is specified in test/loom.config", + "you can change the SDK with rake set[sdk]", + ].join("\n") task :show do |t, args| sdk_version = lib_config['sdk_version'] - cmd = "ls -1 #{sdk_root}/#{sdk_version}/libs" - try(cmd, "failed to list contents of #{sdk_version} libs directory") + libs_dir = File.join(sdk_root, sdk_version, 'libs') + puts("installed libs in #{libs_dir}") + Dir.glob("#{libs_dir}/*").each { |f| puts(File.basename(f)) } puts '' end @@ -219,32 +255,49 @@ end namespace :test do - desc "builds #{const_lib_name}Test.loom with the SDK specified in test/loom.config" + desc [ + "builds #{const_lib_name}Test.loom against #{test_config['sdk_version']} SDK", + "the SDK is specified in test/loom.config", + "you can change the SDK with rake set[sdk]", + "the .loom binary is created in test/bin", + ].join("\n") task :build => APP do |t, args| puts "[#{t.name}] task completed, find .loom in test/bin/" puts '' end - desc "runs #{const_lib_name}Test.loom" + desc [ + "runs #{const_lib_name}Test.loom for the console", + "the test runner will print short-form results to stdout", + ].join("\n") task :run => APP do |t, args| puts "[#{t.name}] running #{t.prerequisites[0]}..." sdk_version = test_config['sdk_version'] - cmd = "#{sdk_root}/#{sdk_version}/tools/loomexec test/bin/#{const_lib_name}Test.loom --format ansi" + cmd = "#{loomexec(sdk_version)} test/bin/#{const_lib_name}Test.loom --format ansi" try(cmd, "failed to run .loom") puts '' end - desc "runs #{const_lib_name}Test.loom for CI" + desc [ + "runs #{const_lib_name}Test.loom for CI", + "in CI mode, the test runner will print long-form results to stdout and generate jUnit compatible reports", + "the jUnit xml report files are written to the project root, as TEST-*.xml", + ].join("\n") task :ci => APP do |t, args| puts "[#{t.name}] running #{t.prerequisites[0]}..." sdk_version = test_config['sdk_version'] - cmd = "#{sdk_root}/#{sdk_version}/tools/loomexec test/bin/#{const_lib_name}Test.loom --format junit --format console" + cmd = "#{loomexec(sdk_version)} test/bin/#{const_lib_name}Test.loom --format junit --format console" try(cmd, "failed to run .loom") puts '' end end + +desc [ + "shorthand for rake test:run", +].join("\n") +task :test => 'test:run' diff --git a/lib/tasks/loomlib_demo.rake b/lib/tasks/loomlib_demo.rake new file mode 100644 index 0000000..72ba305 --- /dev/null +++ b/lib/tasks/loomlib_demo.rake @@ -0,0 +1,95 @@ + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# loomlib_demo.rake - adds support for a GUI demo of the loomlib +# +# usage +# add the following to your project's Rakefile: +# +# load(File.join(ENV['HOME'], '.loom', 'tasks', 'loomlib_demo.rake')) + +require 'fileutils' + +require File.join(File.dirname(__FILE__), 'support') +include LoomTasks + +DEMO = "test/bin/#{const_lib_name}Demo.loom" + +FileList['test/src/demo/*.ls'].each do |src| + file DEMO => src +end + +file DEMO => LIBRARY do |t, args| + puts "[file] creating #{t.name}..." + + sdk_version = test_config['sdk_version'] + file_installed = "#{sdk_root}/#{sdk_version}/libs/#{const_lib_name}.loomlib" + + Rake::Task['lib:install'].invoke unless FileUtils.uptodate?(file_installed, [LIBRARY]) + + Dir.chdir('test') do + Dir.mkdir('bin') unless Dir.exists?('bin') + cmd = "#{lsc(sdk_version)} #{const_lib_name}Demo.build" + try(cmd, "failed to compile .loom") + end + + puts '' +end + +namespace :demo do + + desc [ + "builds #{const_lib_name}Demo.loom for #{test_config['sdk_version']} SDK", + "the SDK is specified in test/loom.config", + "you can change the SDK with rake set[sdk]", + "the .loom binary is created in test/bin", + ].join("\n") + task :build => DEMO do |t, args| + puts "[#{t.name}] task completed, find .loom in test/bin/" + puts '' + end + + desc [ + "launches #{const_lib_name}Demo.loom as a GUI app", + "use this launcher if your demo application class extends loom.Application", + "if your demo is a cli app, remove this task from the list with Rake::Task['demo:gui'].clear", + ].join("\n") + task :gui => DEMO do |t, args| + puts "[#{t.name}] launching #{t.prerequisites[0]}..." + + sdk_version = test_config['sdk_version'] + + Dir.chdir('test') do + loomlib = "bin/#{const_lib_name}Demo.loom" + abort("could not find '#{loomlib}'' to launch") unless File.exists?(loomlib) + + # loomlaunch expects to find Main.loom, so we make a launchable copy here + FileUtils.cp(loomlib, 'bin/Main.loom') + + # capture the interrupt signal from a quit app + begin + cmd = loomlaunch(sdk_version) + try(cmd, "failed to launch .loom") + rescue Exception => e + puts ' (quit)' + end + end + end + + desc [ + "executes #{const_lib_name}Demo.loom as a commandline app, with options", + "use this launcher if your demo application class extends system.application.ConsoleApplication", + "if your demo is a gui app, remove this task from the list with Rake::Task['demo:cli'].clear", + ].join("\n") + task :cli, [:options] => DEMO do |t, args| + args.with_defaults(:options => '') + puts "[#{t.name}] executing #{t.prerequisites[0]}..." + + sdk_version = test_config['sdk_version'] + + cmd = "#{loomexec(sdk_version)} test/bin/#{const_lib_name}Demo.loom #{args.options}" + try(cmd, "failed to run .loom") + + puts '' + end + +end diff --git a/lib/tasks/support.rb b/lib/tasks/support.rb index d873a06..05b8784 100644 --- a/lib/tasks/support.rb +++ b/lib/tasks/support.rb @@ -1,6 +1,11 @@ +# encoding: utf-8 + +require 'rbconfig' module LoomTasks + VERSION = '1.1.0' + EXIT_OK = 0 def exec_with_echo(cmd) @@ -14,14 +19,39 @@ def fail(message) abort("✘ #{message}") end - def sdk_root() - File.join(Dir.home, '.loom', 'sdks') - end - def try(cmd, failure_message) fail(failure_message) if (exec_with_echo(cmd) != EXIT_OK) end + def loomexec(sdk_version) + File.join(sdk_root, sdk_version, 'tools', 'loomexec') + end + + def loomlaunch_win(sdk_version) + exe = File.join(sdk_root, sdk_version, 'bin', 'LoomDemo.exe') + %(start "Loom" #{exe} ProcessID #{Process.pid}) + end + + def loomlaunch_osx(sdk_version) + File.join(sdk_root, sdk_version, 'bin', 'LoomDemo.app', 'Contents', 'MacOS', 'LoomDemo') + end + + def loomlaunch(sdk_version) + # needs to be run in the project root + # magically, the launcher loads bin/Main.loom from the current working directory + return loomlaunch_osx(sdk_version) if osx? + return loomlaunch_win(sdk_version) if windows? + end + + def lsc(sdk_version) + # needs to be run in the project root + File.join(sdk_root, sdk_version, 'tools', 'lsc') + end + + def sdk_root() + File.join(Dir.home, '.loom', 'sdks') + end + def parse_loom_config(file) JSON.parse(File.read(file)) end @@ -35,7 +65,15 @@ def lib_version_regex() end def lib_version() - File.open(lib_version_file, 'r') { |f| f.read.scan(lib_version_regex).first[0] } + File.open(lib_version_file, 'r') do |f| + matches = f.read.scan(lib_version_regex) + raise("No version const defined in #{lib_version_file}") if matches.empty? + matches.first[0] + end + end + + def libs_path(sdk_version) + File.join(sdk_root, sdk_version, 'libs') end def readme_version_regex() @@ -53,4 +91,15 @@ def update_readme_version() ) end + def windows? + return false if RUBY_PLATFORM =~ /cygwin/ # i386-cygwin + return true if ENV['OS'] == 'Windows_NT' + false + end + + def osx? + return true if RUBY_PLATFORM =~ /darwin/ + false + end + end