From 9b770623a7e06e01a508cdcb51803cd976f62dbe Mon Sep 17 00:00:00 2001 From: Matthias Neeracher Date: Fri, 28 Nov 2014 14:18:53 +0100 Subject: [PATCH] Start work on ASBuilder --- AVRsack.xcodeproj/project.pbxproj | 26 ++++++-- AVRsack/ASBuilder.swift | 40 ++++++++++++ AVRsack/ASFileTree.swift | 18 +++++- AVRsack/ASHardware.swift | 51 ++++++++++++++- AVRsack/ASProjDoc.swift | 15 ++++- AVRsack/BuildProject | 100 ++++++++++++++++++++++++++++++ AVRsack/Defaults.plist | 6 ++ 7 files changed, 245 insertions(+), 11 deletions(-) create mode 100644 AVRsack/ASBuilder.swift create mode 100755 AVRsack/BuildProject diff --git a/AVRsack.xcodeproj/project.pbxproj b/AVRsack.xcodeproj/project.pbxproj index 5713a97..61f200e 100644 --- a/AVRsack.xcodeproj/project.pbxproj +++ b/AVRsack.xcodeproj/project.pbxproj @@ -13,6 +13,8 @@ 9501D8091A17025C0034C530 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9501D8081A17025C0034C530 /* Images.xcassets */; }; 9501D80C1A17025C0034C530 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9501D80A1A17025C0034C530 /* MainMenu.xib */; }; 9501D8181A17025C0034C530 /* AVRsackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9501D8171A17025C0034C530 /* AVRsackTests.swift */; }; + 951CD1741A23C9FC0066C1A1 /* ASBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951CD1731A23C9FC0066C1A1 /* ASBuilder.swift */; }; + 951CD1771A2615000066C1A1 /* BuildProject in Resources */ = {isa = PBXBuildFile; fileRef = 951CD1761A2615000066C1A1 /* BuildProject */; }; 95468DDF1A228BE600668EE2 /* ASHardware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95468DDE1A228BE600668EE2 /* ASHardware.swift */; }; 95468DE31A228E1300668EE2 /* Defaults.plist in Resources */ = {isa = PBXBuildFile; fileRef = 95468DE21A228E1300668EE2 /* Defaults.plist */; }; 95BF80EB1A185C9E0004A693 /* ASFileTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BF80EA1A185C9E0004A693 /* ASFileTree.swift */; }; @@ -69,6 +71,8 @@ 9501D8111A17025C0034C530 /* AVRsackTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AVRsackTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 9501D8161A17025C0034C530 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9501D8171A17025C0034C530 /* AVRsackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVRsackTests.swift; sourceTree = ""; }; + 951CD1731A23C9FC0066C1A1 /* ASBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASBuilder.swift; sourceTree = ""; }; + 951CD1761A2615000066C1A1 /* BuildProject */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; fileEncoding = 4; path = BuildProject; sourceTree = ""; }; 95468DDE1A228BE600668EE2 /* ASHardware.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASHardware.swift; sourceTree = ""; }; 95468DE21A228E1300668EE2 /* Defaults.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Defaults.plist; sourceTree = ""; }; 958D76811A17DA1C00917D96 /* AVRsack-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AVRsack-Bridging-Header.h"; sourceTree = ""; }; @@ -120,11 +124,7 @@ 9501D7FE1A17025C0034C530 /* AVRsack */ = { isa = PBXGroup; children = ( - 958D76811A17DA1C00917D96 /* AVRsack-Bridging-Header.h */, - 9501D8011A17025C0034C530 /* ASApplication.swift */, - 9501D8031A17025C0034C530 /* ASProjDoc.swift */, - 95BF80EA1A185C9E0004A693 /* ASFileTree.swift */, - 95468DDE1A228BE600668EE2 /* ASHardware.swift */, + 951CD1751A2614C40066C1A1 /* Source */, 95BF80EC1A185CD90004A693 /* Resources */, 9501D7FF1A17025C0034C530 /* Supporting Files */, 95EA32671A17BAA600F66EB0 /* Frameworks */, @@ -136,6 +136,7 @@ isa = PBXGroup; children = ( 9501D8001A17025C0034C530 /* Info.plist */, + 958D76811A17DA1C00917D96 /* AVRsack-Bridging-Header.h */, ); name = "Supporting Files"; sourceTree = ""; @@ -157,6 +158,19 @@ name = "Supporting Files"; sourceTree = ""; }; + 951CD1751A2614C40066C1A1 /* Source */ = { + isa = PBXGroup; + children = ( + 9501D8011A17025C0034C530 /* ASApplication.swift */, + 9501D8031A17025C0034C530 /* ASProjDoc.swift */, + 95BF80EA1A185C9E0004A693 /* ASFileTree.swift */, + 95468DDE1A228BE600668EE2 /* ASHardware.swift */, + 951CD1731A23C9FC0066C1A1 /* ASBuilder.swift */, + 951CD1761A2615000066C1A1 /* BuildProject */, + ); + name = Source; + sourceTree = ""; + }; 95BF80EC1A185CD90004A693 /* Resources */ = { isa = PBXGroup; children = ( @@ -302,6 +316,7 @@ 9501D8071A17025C0034C530 /* ASProjDoc.xib in Resources */, 9501D8091A17025C0034C530 /* Images.xcassets in Resources */, 9501D80C1A17025C0034C530 /* MainMenu.xib in Resources */, + 951CD1771A2615000066C1A1 /* BuildProject in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -321,6 +336,7 @@ files = ( 9501D8041A17025C0034C530 /* ASProjDoc.swift in Sources */, 95468DDF1A228BE600668EE2 /* ASHardware.swift in Sources */, + 951CD1741A23C9FC0066C1A1 /* ASBuilder.swift in Sources */, 9501D8021A17025C0034C530 /* ASApplication.swift in Sources */, 95BF80EB1A185C9E0004A693 /* ASFileTree.swift in Sources */, ); diff --git a/AVRsack/ASBuilder.swift b/AVRsack/ASBuilder.swift new file mode 100644 index 0000000..a9aef6d --- /dev/null +++ b/AVRsack/ASBuilder.swift @@ -0,0 +1,40 @@ +// +// ASBuilder.swift +// AVRsack +// +// Created by Matthias Neeracher on 11/24/14. +// Copyright © 2014 Aere Perennius. All rights reserved. +// + +import Foundation + +class ASBuilder { + var dir = NSURL() + var task : NSTask? + + func setProjectURL(url: NSURL) { + dir = url.URLByDeletingLastPathComponent!.standardizedURL! + } + + func buildProject(board: String, files: ASFileTree) { + task = NSTask() + task!.currentDirectoryPath = dir.path! + task!.launchPath = NSBundle.mainBundle().pathForResource("BuildProject", ofType: "")! + + let libPath = (ASLibraries.instance().directories as NSArray).componentsJoinedByString(":") + var args = [NSString]() + let boardProp = ASHardware.instance().boards[board]! + args.append("board="+board) + args.append("mcu="+boardProp["build.mcu"]) + args.append("f_cpu="+boardProp["build.f_cpu"]) + args.append("core="+boardProp["build.core"]) + args.append("variant="+boardProp["build.variant"]) + args.append("libs="+libPath) + args.append("--") + args += files.paths + task!.arguments = args; + task!.launch() + + files.paths + } +} \ No newline at end of file diff --git a/AVRsack/ASFileTree.swift b/AVRsack/ASFileTree.swift index 464a1ef..88a94de 100644 --- a/AVRsack/ASFileTree.swift +++ b/AVRsack/ASFileTree.swift @@ -76,6 +76,9 @@ class ASFileNode { assertionFailure("Undefined item type in file hierarchy") } } + func paths(rootPath: NSString) -> [NSString] { + return [NSString]() + } } class ASFileGroup : ASFileNode { @@ -109,7 +112,6 @@ class ASFileGroup : ASFileNode { child.apply(closure) } } - func childrenPropertyList(rootPath: NSString) -> [AnyObject] { return children.map() { (node) in node.propertyList(rootPath) } } @@ -117,6 +119,13 @@ class ASFileGroup : ASFileNode { return [kTypeKey: kNodeType, kNameKey: name, kExpandedKey: expanded, kChildrenKey: childrenPropertyList(rootPath)] } + override func paths(rootPath: NSString) -> [NSString] { + var allPaths = [NSString]() + for child in children { + allPaths += child.paths(rootPath) + } + return allPaths + } } class ASProject : ASFileGroup { @@ -168,10 +177,12 @@ class ASFileItem : ASFileNode { let resComp = Array(count: relCount-matchComp, repeatedValue: "..")+pathComp[matchComp.. AnyObject { return [kTypeKey: kNodeTypeFile, kKindKey: type.rawValue, kPathKey: relativePath(rootPath)] } + override func paths(rootPath: NSString) -> [NSString] { + return [relativePath(rootPath)] + } } class ASFileTree : NSObject, NSOutlineViewDataSource { @@ -197,6 +208,9 @@ class ASFileTree : NSObject, NSOutlineViewDataSource { func readPropertyList(prop: NSDictionary) { root = ASFileNode.readPropertyList(prop, rootURL:dir) as ASProject } + var paths : [NSString] { + return root.paths(dir.path!) + } // MARK: Outline Data Source func outlineView(outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int { diff --git a/AVRsack/ASHardware.swift b/AVRsack/ASHardware.swift index a72866d..42928c7 100644 --- a/AVRsack/ASHardware.swift +++ b/AVRsack/ASHardware.swift @@ -8,7 +8,22 @@ import Foundation -typealias ASPropertyEntry = [String: String] +class ASPropertyEntry { + private var storage = [String: String]() + subscript(key: String) -> String { + get { + if let value = storage[key] { + return value + } else { + return "" + } + } + set (newValue) { + storage[key] = newValue + } + } +} + typealias ASProperties = [String: ASPropertyEntry] private func subdirectories(path: NSString) -> [NSString] { @@ -26,7 +41,7 @@ private func subdirectories(path: NSString) -> [NSString] { return subDirs } -let hardwareInstance = ASHardware() +private let hardwareInstance = ASHardware() class ASHardware { class func instance() -> ASHardware { return hardwareInstance } let directories = [NSString]() @@ -89,4 +104,34 @@ class ASHardware { } } } -} \ No newline at end of file +} + +private let librariesInstance = ASLibraries() +class ASLibraries { + class func instance() -> ASLibraries { return librariesInstance } + let directories = [NSString]() + let libraries = [NSString]() + init() { + // + // Gather hardware directories + // + let userDefaults = NSUserDefaults.standardUserDefaults() + let fileManager = NSFileManager.defaultManager() + if let arduinoPath = userDefaults.stringForKey("Arduino") { + let arduinoLibrariesPath = arduinoPath + "/Contents/Resources/Java/libraries" + let dirs = subdirectories(arduinoLibrariesPath) + if dirs.count > 0 { + directories.append(arduinoLibrariesPath) + libraries += dirs + } + } + for sketchDir in userDefaults.objectForKey("Sketchbooks") as [NSString] { + let librariesPath = sketchDir + "/libraries" + let dirs = subdirectories(librariesPath) + if dirs.count > 0 { + directories.append(librariesPath) + libraries += dirs + } + } + } +} diff --git a/AVRsack/ASProjDoc.swift b/AVRsack/ASProjDoc.swift index 1abd9fe..548ada1 100644 --- a/AVRsack/ASProjDoc.swift +++ b/AVRsack/ASProjDoc.swift @@ -13,11 +13,15 @@ private var keyboardHandler : ACEKeyboardHandler = .Ace class ASProjDoc: NSDocument, NSOutlineViewDelegate { @IBOutlet weak var editor : ACEView! @IBOutlet weak var outline : NSOutlineView! - let files : ASFileTree = ASFileTree() + let files = ASFileTree() + let builder = ASBuilder() var mainEditor : ASFileNode? var currentTheme : UInt = 0 var fontSize : UInt = 12 var themeObserver : AnyObject? + var board : String = "uno" + var programmer : String = "" + var port : String = "" let kVersionKey = "Version" let kCurVersion = 1.0 @@ -25,6 +29,9 @@ class ASProjDoc: NSDocument, NSOutlineViewDelegate { let kThemeKey = "Theme" let kFontSizeKey = "FontSize" let kBindingsKey = "Bindings" + let kBoardKey = "Board" + let kProgrammerKey = "Programmer" + let kPortKey = "Port" // MARK: Initialization / Finalization @@ -51,6 +58,9 @@ class ASProjDoc: NSDocument, NSOutlineViewDelegate { themeObserver = NSNotificationCenter.defaultCenter().addObserverForName(kBindingsKey, object: nil, queue: nil, usingBlock: { (NSNotification) in self.editor.setKeyboardHandler(keyboardHandler) }) + board = userDefaults.stringForKey(kBoardKey)! + programmer = userDefaults.stringForKey(kProgrammerKey)! + port = userDefaults.stringForKey(kPortKey)! } override func finalize() { saveCurEditor() @@ -123,6 +133,7 @@ class ASProjDoc: NSDocument, NSOutlineViewDelegate { success = importProject(url.URLByDeletingLastPathComponent!, error: outError) if success { files.setProjectURL(fileURL!) + builder.setProjectURL(fileURL!) fileURL = projectURL success = writeToURL(projectURL, ofType: "Project", forSaveOperation: .SaveAsOperation, originalContentsURL: nil, error: outError) } @@ -136,6 +147,7 @@ class ASProjDoc: NSDocument, NSOutlineViewDelegate { return false } files.setProjectURL(fileURL!) + builder.setProjectURL(fileURL!) let projectData : NSDictionary = NSPropertyListSerialization.propertyListFromData(data, mutabilityOption: .Immutable, format: nil, errorDescription: nil) as NSDictionary let projectVersion = projectData[kVersionKey] as Double assert(projectVersion <= floor(kCurVersion+1.0), "Project version too new for this app") @@ -228,6 +240,7 @@ class ASProjDoc: NSDocument, NSOutlineViewDelegate { // MARK: Build / Upload @IBAction func buildProject(AnyObject) { + builder.buildProject(board, files: files) } } diff --git a/AVRsack/BuildProject b/AVRsack/BuildProject new file mode 100755 index 0000000..6a9cdd5 --- /dev/null +++ b/AVRsack/BuildProject @@ -0,0 +1,100 @@ +#!/usr/bin/ruby +# +# BuildProject board=... mcu=... -- FILE1 FILE2 FILE3 +# +# AVRsack +# +# Created by Matthias Neeracher on 11/26/14. +# Copyright © 2014 Aere Perennius. All rights reserved. +# + +require 'fileutils.rb' + +BUILD = { + 'board' => 'uno', + 'mcu' => 'atmega328p', + 'f_cpu' => 16000000, + 'core' => 'arduino', + 'variant' => 'standard', +} + +def parseArguments + while ARGV.length > 0 do + param = ARGV.shift + break if param == '--' + param =~ /(\S+?)=(\S*)/ + BUILD[$1] = $2 + end +end + +def createBuildDirectory + $BUILD_DIR = "build/#{BUILD['board']}" + $SKETCH_DIR = "#{$BUILD_DIR}/sketch" + FileUtils::mkdir_p "#{$SKETCH_DIR}", :verbose => true +end + +def parseInoFiles + $LIBPATH = BUILD['libs'].split(':').reverse + $LIBRARIES = [] + ARGV.each_index do |arg| + if ARGV[arg] =~ /\.ino$/ + outName = "#{$SKETCH_DIR}/"+File.basename(ARGV[arg], '.ino')+".cpp" + outFile = File.open(outName, 'w') + File.open(ARGV[arg], 'r') do |ino| + contents = ino.read + # Find protypes: + prototypes = contents.dup + # - Strip comments, quoted strings, and preprocessor directives + prototypes.gsub!(%r{'(?:[^']|\\')+'|"(?:[^"]|\\")*"|//.*?$|/\*.*?\*/|^\s*?#.*?$}m, ' ') + # Collapse braces + while prototypes.sub!(/(\{)(?:[^{}]+|\{[^{}]*\})/m, '\1') do + end + existingProto = {} + prototypes.scan(/[\w\[\]\*]+\s+[&\[\]\*\w\s]+\([&,\[\]\*\w\s]*\)(?=\s*;)/) {|p| + existingProto[smashSpaces(p)] = true + } + proto = [] + prototypes.scan(/[\w\[\]\*]+\s+[&\[\]\*\w\s]+\([&,\[\]\*\w\s]*\)(?=\s*{)/) {|p| + p = smashSpaces(p) + proto << p unless existingProto[p] + } + contents.each_line do |line| + if line =~ /^\s*#include\s+[<"](.*)[">]\s*(#.*)?$/ + addLibrary($1) + end + end + end + outFile.close + ARGV[arg] = outName + end + end +end + +def smashSpaces(s) + return s.gsub(/(\W)\s+(\W)/, '\1\2').gsub(/\s+/, ' ') +end + +def addLibrary(header) + $LIBPATH.each do |path| + Dir.glob("#{path}/*").each do |lib| + if File.exists?("#{lib}/#{header}") + $LIBRARIES << lib + end + end + end +end + +parseArguments +createBuildDirectory +parseInoFiles + +File.open("#{$BUILD_DIR}/Rakefile", 'w') do |rakeFile| + rakeFile.print < + Port + + Programmer + arduino + Board + uno Bindings Ace FontSize