Лето в этом году выдалось жаркое, но пока ни о каких отпусках речи не идёт. Пока солнечно, тепло и сухо самое время снимать панорамы улиц (или ГрадоГлядъ, как я это называю). Однако, не смотря на хорошую плотность работы, ничего принципиально нового, о чём интересно было бы рассказать, не произошло. Поэтому я покажу концепт, над которым работаю уже какое-то время, и который лежит в стороне от моих основных языков программирования.
Дистанционное управление
Итак, у меня уже давно есть конструктор LEGO Mindstorms, который довольно бодро взаимодействует через bluetooth с компьютером или с таким же конструктором.
А в апреле я обновил телефон на HTC Desire под управлением операционной системы Андроид, которая уже давно привлекла моё внимание, но всё руки не доходили.
В итоге появились повод, возможность и желание «пощупать» Андроид. И вот, что из этого получилось:
Секреты?
Для тех, кто соберётся заниматься чем-то подобным, могу поделиться небольшим секретом. Стандартный метод получения BluetoothSocket, рекомендованный командой Гугла, не работает, а нужен небольшой хак:
/* Не работает:
* socket = device.createRfcommSocketToServiceRecord(MY_UUID)
*/
Method m = device.getClass().getMethod("createRfcommSocket",
new Class[] { int.class });
socket = (BluetoothSocket)m.invoke(device, Integer.valueOf(1));
Вот и всё, что я могу рассказать :) Если я ещё буду писать про Андроид, то придётся сделать подсветку кода для него.
Моя любимая система контроля версий имеет огромное количество инструментов. Как-то раз я участвовал в опросе, после которого выяснилось, что даже из самых популярных инструментов я использую от силы 10%.
Но иногда возникают ситуации, единственно продуктивным выходом из которых бывает изучение и использование нового для себя инструмента. О двух таких случаях я сегодня и расскажу.
Внезапные просьбы: git stash
Бывает так, что пока я работаю над нововведениями в программу, текущая стабильная её версия активно используется. При активном использовании, конечно же, могут возникнуть ошибки или пожелания что-то изменить. Бывает так, что при этом я нахожусь в середине тестирования какого-то новшества, и всё настолько сыро, что я даже не могу сделать коммит.
Итак, я нахожусь в середине правок на ветке extremely_experimental, а мне необходимо внести правки в ветку master. Вот, как это делается:
git stash save
git checkout master
После первой команды всё наши изменения, которые нельзя было закоммитить, сохранены и текущая ветка приведена в состояние до правок. После этого мы можем сменить ветку и внести наши правки. После того, как ошибки исправлены, нововведения сделаны и тесты проходят, мы можем вернуться обратно к нашим правкам.
Но скорее всего все или некоторые из сделанных изменений понадобятся нам в нашей экспериментальной ветке. После перехода на неё:
git checkout extremely_experimental
Если нам нужны все изменения, то:
git merge master
Если только некоторые, то:
git cherry-pick ...
После этого вернём наши правки:
git stash pop
Если возникли конфликты, то правим их и делаем:
git stash drop
git reset --mixed
Последнее нужно для того, чтобы вынести наши правки из индекса, т.к. при конфликте они не выходят оттуда самостоятельно.
Конечно же таких незавершённых правок может быть несколько, но это я оставлю на самостоятельное изучение пытливому читателю.
Неизвестно, когда сломалось: git bisect
Бывает так, что вдруг обнаруживается ошибка, про которую точно известно, что давным давно её не было. Так бывает в больших проектах, в непокрытых тестами областях. Бывает так, что обнаружить, в чём же дело, быстро не удаётся.
Хорошая новость в том, что это и не обязательно. Нужно просто начать процесс:
git bisect start
git bisect bad
Так мы обозначили, что текущий коммит содержит ошибку. После этого, либо мы знаем, как называется коммит, в котором ошибки ещё не было, или находим его.
git bisect good v2.3.1
или
git checkout ...
git bisect good
После этого за нас всё будет делать git. Он будет перемещать нас по истории, а мы будем проверять, есть эта ошибка или нет, и сообщать об этом:
git bisect good
или
git bisect bad
В конце концов нам сообщат, какой именно коммит всё поломал. Название инструмента подсказывает нам, что на тестирование нам всегда предоставляется коммит, который находится посередине между плохим и хорошим. Таким образом, мы просматриваем не все N коммитов в истории ошибки, а всего лишь log2N.
После того, как мы выяснили, в чём причина, убрать следы, которые оставил после себя git bisect можно так:
Основной целью этого блога является сбор в одном удобном месте необходимых мне по работе знаний и фишек. Однако, именно потому что это активно используемые в работе решения, со временем появляется более продуктивный или более правильный способ сделать то, о чём написано почти в каждой статье.
Иногда я просто ошибаюсь. Трудно представить что-то более полезное для опыта, нежели набивание шишек. Будет хорошо, если проведение работ над ошибками станет доброй традицией. Итак, в этом году.
namespace :version do
[:major, :minor, :patch].each_with_index do |subv, index|
desc "Bump #{subv} in version"
task :"bump_#{subv}" do
unless `git status` =~ /nothing to commit/
raise "There are uncommitted changes. Failed to proceed."
end
appxml = YAML.load_file('airake.yml')["appxml_path"]
str = File.read(appxml)
msg = nil
new_version = nil
if str.gsub! /<version>(.*)<\/version>/ do |matched|
old_version = $1
major, minor, patch = old_version.split(".").map(&:to_i)
eval("#{subv} += 1")
new_version = [major, minor, patch].fill(0, index+1).join(".")
msg = "Version bump #{old_version} => #{new_version}"
puts msg
"<version>#{new_version}</version>"
end.nil?
raise "Cannot detect current version.\nMake sure appxml file contains <version>X.X.X</version> tag."
else
File.open(appxml, "w") do |f|
f.write str
end
puts `git commit -am "#{msg}"`
puts `git tag v#{new_version}`
end
end
end
end
Теперь rake version:bump_minor делает из 0.1.6 не 0.2.6, а 0.2.0, как и должно быть.
Мимоходом
Тем временем я сменил тарифный план у своего провайдера на (ve). И незаметно перенёс сайт. Посмотрим, как работает на собственном опыте. Работа по ssh, как была, так и осталась основным способом администрирования, а необходимость лазить в plesk пропала, потому что его теперь нет :)
Как-то раз мне попался очень злой git-репозиторий, который отказывался работать, если я оставлял пробелы в конце строк. Есть такая версия, что git заточен под отправку патчей по почте, и что пробелы в концах строк могут навредить в таком процессе.
Тогда я просто отключил эту проверку, а недавно подумал, почему бы мне не использовать эти мощности в мирных целях.
git hooks
Для множества различных целей у git есть хуки. (Как бы их перевести нормально?) Они находятся в каждом репозитории в папке:
.git/hooks
И имеют говорящие названия. Используются они для соблюдения форматов и соглашений, для оповещений, для проверки и т.п. Почему бы не возложить на них корректорские функции?
Использование pre-commit для удаления пробелов на концах строк
Поскольку я, опять же, фанат руби, то и скрипты благо есть такая возможность напишу на руби. Создаём файлик .git/hooks/pre-commit:
#!/usr/bin/env ruby
Dir.glob("*.{txt,rb}").each do |p|
lines = File.readlines(p)
if lines.inject(false) { |memo, line| line.gsub!(/\s+$/, "") || memo }
File.open(p, "w") do |f|
f.puts lines.join("\n")
end
system "git add #{p}"
end
end
Как видно, этот скрипт ищет файлы *.txt и *.rb в корневом каталоге репозитория, и если в них есть пробелы в конце строк, перезаписывает их и добавляет в индекс для коммита.
Не забыть сделать его запускаемым:
chmod +x .git/hooks/pre-commit
Теперь у нас в распоряжении автоматический помощник-редактор, который удаляет пробелы в конце строк.
Уже некоторое время назад обнаружил гениальный инструмент. Правда только недавно опробовал его на своих рабочих проектах и зафанател ещё больше. Подготовка к докладу на секции Яндекса про панорамы на РИФе не позволила мне поделиться этим ранее. Исправляю ошибку.
Я люблю руби. И, естественно, rake, как инструмент, продолжающий славные традиции make в руби и с помощью руби. Так же я питаю нежные чувства к ActionScript. Мне нравится AIR, который позволяет писать действительно кросс-платформенные приложения довольно быстро. Так же я неплохо отношусь к TDD, как к одному из способов разработки.
Какова же была моя радость найти инструмент, который всё это объединяет! Хотя ему уже пара лет, он по-прежнему прекрасен.
Установка составляющих
Предполагаю, что ruby, rubygems и rake уже установлены у тех, кто читает этот блог.
Так же потребуется установить java для того, чтобы на ней работал компилятор:
sudo apt-get install sun-java6-jre
После установки, независимо от того, используете вы Flex3 или Flex4, нужно переписать содержимое AIR SDK поверх Flex SDK. Мне не совсем понятен сакральный смысл этих действий, но иначе ничего не работает.
Привѣтъ, Мiръ!
Создание пустого air-приложения теперь просто:
airake airake_hello_world
Чтобы запустить его, однако, следует исправить в src/AirakeHelloWorld-app.xml и test/Test-app.xml:
Если вы решили использовать Flex4, то вам необходимо отредактировать сгенерированное приложение, чтобы запустить его. Это связано с изменениями в стилях. Поэтому проще просто удалить всё содержимое тэга WindowedApplication в файле src/AirakeHelloWorld.mxml.
Про использование TDD в ActionScript я уже писал, поэтому подробно останавливаться не буду. Для примера в код на github включён тривиальный тест. Запуск тестирования происходит привычным образом:
rake test
Документация, если вы пишете правильные комментарии ASDoc, тоже запускается привычным образом:
rake docs
Так же делается всё остальное: запуск приложения в отладочном режиме, генерирование сертификата, упаковка релиза приложения. Для того, чтобы это всё узнать, используйте:
rake -T
Использование rake
Конечно, вся прелесть rake не только в привычных и коротких командах для разработки, но и в том, что можно создавать свои сценарии. Например, вот как могла бы выглядеть работа с версиями приложения. Добавим в файл raketasks/version.rake следующий код:
require 'yaml'
desc "Print out current version"
task :version do
if md = File.read(YAML.load_file('airake.yml')["appxml_path"]).match(/<version>(.*)<\/version>/)
puts "Current version is #{md[1]}"
else
raise "Cannot detect current version.\nMake sure appxml file contains <version>X.X.X</version> tag."
end
end
namespace :version do
[:major, :minor, :patch].each do |subv|
desc "Bump #{subv} in version"
task :"bump_#{subv}" do
unless `git status` =~ /nothing to commit/
raise "There are uncommitted changes. Failed to proceed."
end
appxml = YAML.load_file('airake.yml')["appxml_path"]
str = File.read(appxml)
msg = nil
new_version = nil
if str.gsub! /<version>(.*)<\/version>/ do |matched|
old_version = $1
major, minor, patch = old_version.split(".").map(&:to_i)
eval("#{subv} += 1")
new_version = [major, minor, patch].join(".")
msg = "Version bump #{old_version} => #{new_version}"
puts msg
"<version>#{new_version}<\/version>"
end.nil?
raise "Cannot detect current version.\nMake sure appxml file contains <version>X.X.X</version> tag."
else
File.open(appxml, "w") do |f|
f.write str
end
puts `git commit -am "#{msg}"`
puts `git tag v#{new_version}`
end
end
end
end
Инструкция по работе с airake, которая во многом повторена в этой статье с добавлением манипуляций, чтобы всё заработало.
Документация по FlexUnit. Не уверен, что в поставке airake идёт самая последняя версия, но ничего не мешает написать rake task для обновления версии FlexUnit :)