// // ASBuilder.swift // AVRsack // // Created by Matthias Neeracher on 11/24/14. // Copyright © 2014-2015 Aere Perennius. All rights reserved. // import Foundation class ASBuilder { var dir = NSURL() var task : Task? var continuation: (()->())? var termination : AnyObject? init() { termination = NotificationCenter.defaultCenter.addObserverForName(NSTaskDidTerminateNotification, object: nil, queue: nil, usingBlock: { (notification: NSNotification) in if notification.object as? NSTask == self.task { if self.task!.terminationStatus == 0 { if let cont = self.continuation { self.continuation = nil cont() } } else { self.continuation = nil } } }) } func finalize() { NotificationCenter.default.removeObserver(termination!) } func setProjectURL(url: NSURL) { dir = url.URLByDeletingLastPathComponent!.URLByStandardizingPath! } func stop() { task?.terminate() task?.waitUntilExit() } func cleanProject() { do { try FileManager.default.removeItem(at: dir.appendingPathComponent("build")!) } catch _ { } } func buildProject(board: String, files: ASFileTree) { let toolChain = (NSApplication.shared().delegate as! ASApplication).preferences.toolchainPath task = Task() task!.currentDirectoryPath = dir.path! task!.launchPath = Bundle.main.path(forResource: "BuildProject", ofType: "")! let fileManager = FileManager.default let libPath = (ASLibraries.instance().directories as NSArray).componentsJoined(by: ":") var args = [String]() if ASHardware.instance().boards[board] == nil { NSLog("Unable to find board %s\n", board); return } let boardProp = ASHardware.instance().boards[board]! let library = boardProp["library"]! let corePath = library+"/cores/"+boardProp["build.core"]! var variantPath : String? if fileManager.fileExists(atPath: corePath) { if let variantName = boardProp["build.variant"] { variantPath = library+"/variants/"+variantName if fileManager.fileExists(atPath: variantPath!) { args.append("variant="+variantName) } else { variantPath = nil } } } else { NSLog("Unable to find core %s\n", boardProp["build.core"]!) return } args.append("toolchain="+toolChain) args.append("project="+dir.lastPathComponent!) args.append("board="+board) args.append("mcu="+boardProp["build.mcu"]!) args.append("f_cpu="+boardProp["build.f_cpu"]!) args.append("max_size="+boardProp["upload.maximum_size"]!) args.append("core="+boardProp["build.core"]!) args.append("libs="+libPath) args.append("core_path="+corePath) if let varPath = variantPath { args.append("variant_path="+varPath) } args.append("usb_vid="+(boardProp["build.vid"] ?? "null")); args.append("usb_pid="+(boardProp["build.pid"] ?? "null")); args.append("--") args += files.paths task!.arguments = args; task!.launch() } enum Mode { case Upload case BurnBootloader case Interactive } func uploadProject(board: String, programmer: String, port: String, mode: Mode = .Upload) { let useProgrammer = mode != .Upload let interactive = mode == .Interactive let portPath = ASSerial.fileName(forPort: port) let toolChain = (NSApplication.shared().delegate as! ASApplication).preferences.toolchainPath task = Task() task!.currentDirectoryPath = dir.path! task!.launchPath = toolChain+"/bin/avrdude" let fileManager = FileManager.default var logOut : FileHandle if interactive { let inputPipe = Pipe() let outputPipe = Pipe() logOut = outputPipe.fileHandleForWriting task!.standardInput = inputPipe task!.standardOutput = outputPipe task!.standardError = outputPipe } else { ASSerialWin.portNeededForUpload(port: port) let logURL = dir.appendingPathComponent("build/upload.log") fileManager.createFileAtPath(logURL.path, contents: NSData(), attributes: nil) logOut = FileHandle(forWritingAtPath: logURL.path!)! task!.standardOutput = logOut task!.standardError = logOut } if ASHardware.instance().boards[board] == nil { NSLog("Unable to find board %s\n", board); return } let boardProp = ASHardware.instance().boards[board]! let progProp = ASHardware.instance().programmers[programmer] let hasBootloader = !useProgrammer && boardProp["upload.protocol"] != nil let leonardish = hasBootloader && (boardProp["bootloader.path"] ?? "").hasPrefix("caterina") let proto = hasBootloader ? boardProp["upload.protocol"] : progProp?["protocol"] let speed = hasBootloader ? boardProp["upload.speed"] : progProp?["speed"] let verbosity = UserDefaults.standard.integer(forKey: "UploadVerbosity") var args = Array(repeating: "-v", count: verbosity) args += [ "-C", toolChain+"/etc/avrdude.conf", "-p", boardProp["build.mcu"]!, "-c", proto!, "-P", portPath] switch mode { case .Upload: if hasBootloader { args += ["-D"] } args += ["-U", "flash:w:build/"+board+"/"+dir.lastPathComponent!+".hex:i"] continuation = { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2*NSEC_PER_SEC)), dispatch_get_main_queue(), { ASSerialWin.portAvailableAfterUpload(port) }) } case .BurnBootloader: var loaderArgs = args args += ["-e"] if let unlock = boardProp["bootloader.unlock_bits"] { args += ["-Ulock:w:"+unlock+":m"] } if let efuse = boardProp["bootloader.extended_fuses"] { args += ["-Uefuse:w:"+efuse+":m"] } let hfuse = boardProp["bootloader.high_fuses"]! let lfuse = boardProp["bootloader.low_fuses"]! args += ["-Uhfuse:w:"+hfuse+":m", "-Ulfuse:w:"+lfuse+":m"] var needPhase2 = false if let loaderPath = boardProp["bootloader.path"] { let loader = boardProp["library"]!+"/bootloaders/"+loaderPath+"/"+boardProp["bootloader.file"]! loaderArgs += ["-Uflash:w:"+loader+":i"] needPhase2 = true } if let lock = boardProp["bootloader.lock_bits"] { loaderArgs += ["-Ulock:w:"+lock+":m"] needPhase2 = true } if needPhase2 { let task2 = Task() task2.currentDirectoryPath = dir.path! task2.launchPath = toolChain+"/bin/avrdude" task2.arguments = loaderArgs task2.standardOutput = logOut task2.standardError = logOut continuation = { let cmdLine = task2.launchPath!+" "+(loaderArgs as NSArray).componentsJoined(by: " ")+"\n" logOut.write(cmdLine.data(using: String.Encoding.utf8, allowLossyConversion: true)!) task2.launch() self.continuation = { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2*NSEC_PER_SEC)), dispatch_get_main_queue(), { ASSerialWin.portAvailableAfterUpload(port) }) } } } case .Interactive: args += ["-t"] } if speed != nil { args.append("-b") args.append(speed!) } // // For Leonardo & the like, reset by opening port at 1200 baud // if leonardish { if verbosity > 0 { logOut.write("Opening port \(port) at 1200 baud\n".data(using: String.Encoding.utf8, allowLossyConversion: true)!) } if let dummyConnection = ASSerial.openPort(portPath, withSpeed: 1200) { usleep(50000) ASSerial.closePort(dummyConnection.fileDescriptor) sleep(1) for retry in 0 ..< 40 { usleep(250000) if (fileManager.fileExistsAtPath(portPath)) { if verbosity > 0 { logOut.write("Found port \(port) after \(retry) attempts.\n".data(using: String.Encoding.utf8, allowLossyConversion: true)!) } break; } } } } let cmdLine = task!.launchPath!+" "+(args as NSArray).componentsJoined(by: " ")+"\n" logOut.write(cmdLine.data(using: String.Encoding.utf8, allowLossyConversion: true)!) task!.arguments = args; task!.launch() if interactive { let intSpeed = (speed != nil) ? Int(speed!) ?? 19200 : 19200 ASSerialWin.showWindowWithPort(port: port, task:task!, speed:intSpeed) task = nil } } func disassembleProject(board: String) { let toolChain = (NSApplication.shared().delegate as! ASApplication).preferences.toolchainPath task = Task() task!.currentDirectoryPath = dir.path! task!.launchPath = toolChain+"/bin/avr-objdump" let fileManager = FileManager.default let logURL = dir.appendingPathComponent("build/disasm.log") fileManager.createFileAtPath(logURL.path!, contents: NSData(), attributes: nil) let logOut = FileHandle(forWritingAtPath: logURL.path!)! task!.standardOutput = logOut task!.standardError = logOut let showSource = UserDefaults.standard.bool(forKey: "ShowSourceInDisassembly") var args = showSource ? ["-S"] : [] args += ["-d", "build/"+board+"/"+dir.lastPathComponent!+".elf"] let cmdLine = task!.launchPath!+" "+(args as NSArray).componentsJoined(by: " ")+"\n" logOut.writeData(cmdLine.dataUsingEncoding(String.Encoding.utf8, allowLossyConversion: true)!) task!.arguments = args; task!.launch() } }