Start work on ASBuilder

This commit is contained in:
Matthias Neeracher 2014-11-28 14:18:53 +01:00 committed by Matthias Neeracher
parent 7ae5a4b215
commit 9b770623a7
7 changed files with 245 additions and 11 deletions

View File

@ -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 = "<group>"; };
9501D8171A17025C0034C530 /* AVRsackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVRsackTests.swift; sourceTree = "<group>"; };
951CD1731A23C9FC0066C1A1 /* ASBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASBuilder.swift; sourceTree = "<group>"; };
951CD1761A2615000066C1A1 /* BuildProject */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; fileEncoding = 4; path = BuildProject; sourceTree = "<group>"; };
95468DDE1A228BE600668EE2 /* ASHardware.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASHardware.swift; sourceTree = "<group>"; };
95468DE21A228E1300668EE2 /* Defaults.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Defaults.plist; sourceTree = "<group>"; };
958D76811A17DA1C00917D96 /* AVRsack-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AVRsack-Bridging-Header.h"; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -157,6 +158,19 @@
name = "Supporting Files";
sourceTree = "<group>";
};
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 = "<group>";
};
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 */,
);

40
AVRsack/ASBuilder.swift Normal file
View File

@ -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
}
}

View File

@ -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..<pathCount]
return "/".join(resComp)
}
override func propertyList(rootPath: NSString) -> 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 {

View File

@ -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 {
}
}
}
}
}
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
}
}
}
}

View File

@ -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)
}
}

100
AVRsack/BuildProject Executable file
View File

@ -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 <<END_RAKE
LIBRARIES=#{$LIBRARIES.join(':')}
FILES = [
#{ARGV.join('\n')}
]
END_RAKE
end

View File

@ -2,6 +2,12 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Port</key>
<string></string>
<key>Programmer</key>
<string>arduino</string>
<key>Board</key>
<string>uno</string>
<key>Bindings</key>
<string>Ace</string>
<key>FontSize</key>