binary-format supports uf2, nrf-dfu
This commit is contained in:
2
go.mod
2
go.mod
@@ -15,6 +15,8 @@ require (
|
||||
golang.org/x/tools v0.36.0
|
||||
)
|
||||
|
||||
require github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -12,6 +12,8 @@ github.com/goplus/mod v0.17.1 h1:ITovxDcc5zbURV/Wrp3/SBsYLgC1KrxY6pq1zMM2V94=
|
||||
github.com/goplus/mod v0.17.1/go.mod h1:iXEszBKqi38BAyQApBPyQeurLHmQN34YMgC2ZNdap50=
|
||||
github.com/qiniu/x v1.15.1 h1:avE+YQaowp8ZExjylOeSM73rUo3MQKBAYVxh4NJ8dY8=
|
||||
github.com/qiniu/x v1.15.1/go.mod h1:AiovSOCaRijaf3fj+0CBOpR1457pn24b0Vdb1JpwhII=
|
||||
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 h1:NVK+OqnavpyFmUiKfUMHrpvbCi2VFoWTrcpI7aDaJ2I=
|
||||
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
|
||||
@@ -760,7 +760,7 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
|
||||
|
||||
if orgApp != app {
|
||||
fmt.Printf("cross compile: %#v\n", ctx.crossCompile)
|
||||
err = firmware.MakeFirmwareImage(orgApp, app, ctx.crossCompile.BinaryFormat)
|
||||
err = firmware.MakeFirmwareImage(orgApp, app, ctx.crossCompile.BinaryFormat, ctx.crossCompile.FormatDetail)
|
||||
check(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ type Export struct {
|
||||
ClangBinPath string // Path to clang binary directory
|
||||
|
||||
BinaryFormat string // Binary format (e.g., "elf", "esp", "uf2")
|
||||
FormatDetail string // For uf2, it's uf2FamilyID
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -462,6 +463,7 @@ func useTarget(targetName string) (export Export, err error) {
|
||||
export.GOARCH = config.GOARCH
|
||||
export.ExtraFiles = config.ExtraFiles
|
||||
export.BinaryFormat = config.BinaryFormat
|
||||
export.FormatDetail = config.FormatDetail()
|
||||
|
||||
// Build environment map for template variable expansion
|
||||
envs := buildEnvMap(env.LLGoROOT())
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// From tinygo/builder/esp.go
|
||||
|
||||
package firmware
|
||||
|
||||
import (
|
||||
@@ -11,8 +13,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// From tinygo/builder/esp.go
|
||||
|
||||
type espImageSegment struct {
|
||||
addr uint32
|
||||
data []byte
|
||||
|
||||
@@ -7,6 +7,10 @@ import "strings"
|
||||
func BinaryExt(binaryFormat string) string {
|
||||
if strings.HasPrefix(binaryFormat, "esp") {
|
||||
return ".bin"
|
||||
} else if strings.HasPrefix(binaryFormat, "uf2") {
|
||||
return ".uf2"
|
||||
} else if strings.HasPrefix(binaryFormat, "nrf-dfu") {
|
||||
return ".zip"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ func TestBinaryExt(t *testing.T) {
|
||||
{"ESP32", "esp32", ".bin"},
|
||||
{"ESP8266", "esp8266", ".bin"},
|
||||
{"ESP32C3", "esp32c3", ".bin"},
|
||||
{"UF2", "uf2", ""},
|
||||
{"UF2", "uf2", ".uf2"},
|
||||
{"ELF", "elf", ""},
|
||||
{"Empty", "", ""},
|
||||
{"NRF-DFU", "nrf-dfu", ""},
|
||||
{"NRF-DFU", "nrf-dfu", ".zip"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -5,10 +5,15 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func MakeFirmwareImage(infile, outfile, format string) error {
|
||||
fmt.Printf("Creating firmware image from %s to %s with format %s\n", infile, outfile, format)
|
||||
// MakeFirmwareImage creates a firmware image from the given input file.
|
||||
func MakeFirmwareImage(infile, outfile, format, fmtDetail string) error {
|
||||
if strings.HasPrefix(format, "esp") {
|
||||
return makeESPFirmareImage(infile, outfile, format)
|
||||
} else if format == "uf2" {
|
||||
uf2Family := fmtDetail
|
||||
return convertELFFileToUF2File(infile, outfile, uf2Family)
|
||||
} else if format == "nrf-dfu" {
|
||||
return makeDFUFirmwareImage(infile, outfile)
|
||||
}
|
||||
return fmt.Errorf("unsupported firmware format: %s", format)
|
||||
}
|
||||
|
||||
118
internal/firmware/nrfutil.go
Normal file
118
internal/firmware/nrfutil.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// From tinygo/builder/nrfutil.go
|
||||
|
||||
package firmware
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/sigurn/crc16"
|
||||
)
|
||||
|
||||
// Structure of the manifest.json file.
|
||||
type jsonManifest struct {
|
||||
Manifest struct {
|
||||
Application struct {
|
||||
BinaryFile string `json:"bin_file"`
|
||||
DataFile string `json:"dat_file"`
|
||||
InitPacketData nrfInitPacket `json:"init_packet_data"`
|
||||
} `json:"application"`
|
||||
DFUVersion float64 `json:"dfu_version"` // yes, this is a JSON number, not a string
|
||||
} `json:"manifest"`
|
||||
}
|
||||
|
||||
// Structure of the init packet.
|
||||
// Source:
|
||||
// https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/master/lib/sdk11/components/libraries/bootloader_dfu/dfu_init.h#L47-L57
|
||||
type nrfInitPacket struct {
|
||||
ApplicationVersion uint32 `json:"application_version"`
|
||||
DeviceRevision uint16 `json:"device_revision"`
|
||||
DeviceType uint16 `json:"device_type"`
|
||||
FirmwareCRC16 uint16 `json:"firmware_crc16"`
|
||||
SoftDeviceRequired []uint16 `json:"softdevice_req"` // this is actually a variable length array
|
||||
}
|
||||
|
||||
// Create the init packet (the contents of application.dat).
|
||||
func (p nrfInitPacket) createInitPacket() []byte {
|
||||
buf := &bytes.Buffer{}
|
||||
binary.Write(buf, binary.LittleEndian, p.DeviceType) // uint16_t device_type;
|
||||
binary.Write(buf, binary.LittleEndian, p.DeviceRevision) // uint16_t device_rev;
|
||||
binary.Write(buf, binary.LittleEndian, p.ApplicationVersion) // uint32_t app_version;
|
||||
binary.Write(buf, binary.LittleEndian, uint16(len(p.SoftDeviceRequired))) // uint16_t softdevice_len;
|
||||
binary.Write(buf, binary.LittleEndian, p.SoftDeviceRequired) // uint16_t softdevice[1];
|
||||
binary.Write(buf, binary.LittleEndian, p.FirmwareCRC16)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Make a Nordic DFU firmware image from an ELF file.
|
||||
func makeDFUFirmwareImage(infile, outfile string) error {
|
||||
// Read ELF file as input and convert it to a binary image file.
|
||||
_, data, err := extractROM(infile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the zip file in memory.
|
||||
// It won't be very large anyway.
|
||||
buf := &bytes.Buffer{}
|
||||
w := zip.NewWriter(buf)
|
||||
|
||||
// Write the application binary to the zip file.
|
||||
binw, err := w.Create("application.bin")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = binw.Write(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the init packet.
|
||||
initPacket := nrfInitPacket{
|
||||
ApplicationVersion: 0xffff_ffff, // appears to be unused by the Adafruit bootloader
|
||||
DeviceRevision: 0xffff, // DFU_DEVICE_REVISION_EMPTY
|
||||
DeviceType: 0x0052, // ADAFRUIT_DEVICE_TYPE
|
||||
FirmwareCRC16: crc16.Checksum(data, crc16.MakeTable(crc16.CRC16_CCITT_FALSE)),
|
||||
SoftDeviceRequired: []uint16{0xfffe}, // DFU_SOFTDEVICE_ANY
|
||||
}
|
||||
|
||||
// Write the init packet to the zip file.
|
||||
datw, err := w.Create("application.dat")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = datw.Write(initPacket.createInitPacket())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the JSON manifest.
|
||||
manifest := &jsonManifest{}
|
||||
manifest.Manifest.Application.BinaryFile = "application.bin"
|
||||
manifest.Manifest.Application.DataFile = "application.dat"
|
||||
manifest.Manifest.Application.InitPacketData = initPacket
|
||||
manifest.Manifest.DFUVersion = 0.5
|
||||
|
||||
// Write the JSON manifest to the file.
|
||||
jsonw, err := w.Create("manifest.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enc := json.NewEncoder(jsonw)
|
||||
enc.SetIndent("", " ")
|
||||
err = enc.Encode(manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Finish the zip file.
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(outfile, buf.Bytes(), 0o666)
|
||||
}
|
||||
133
internal/firmware/objcopy.go
Normal file
133
internal/firmware/objcopy.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// From tinygo/builder/objcopy.go
|
||||
|
||||
package firmware
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// maxPadBytes is the maximum allowed bytes to be padded in a rom extraction
|
||||
// this value is currently defined by Nintendo Switch Page Alignment (4096 bytes)
|
||||
const maxPadBytes = 4095
|
||||
|
||||
// objcopyError is an error returned by functions that act like objcopy.
|
||||
type objcopyError struct {
|
||||
Op string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e objcopyError) Error() string {
|
||||
if e.Err == nil {
|
||||
return e.Op
|
||||
}
|
||||
return e.Op + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
type progSlice []*elf.Prog
|
||||
|
||||
func (s progSlice) Len() int { return len(s) }
|
||||
func (s progSlice) Less(i, j int) bool { return s[i].Paddr < s[j].Paddr }
|
||||
func (s progSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// extractROM extracts a firmware image and the first load address from the
|
||||
// given ELF file. It tries to emulate the behavior of objcopy.
|
||||
func extractROM(path string) (uint64, []byte, error) {
|
||||
f, err := elf.Open(path)
|
||||
if err != nil {
|
||||
return 0, nil, objcopyError{"failed to open ELF file to extract text segment", err}
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// The GNU objcopy command does the following for firmware extraction (from
|
||||
// the man page):
|
||||
// > When objcopy generates a raw binary file, it will essentially produce a
|
||||
// > memory dump of the contents of the input object file. All symbols and
|
||||
// > relocation information will be discarded. The memory dump will start at
|
||||
// > the load address of the lowest section copied into the output file.
|
||||
|
||||
// Find the lowest section address.
|
||||
startAddr := ^uint64(0)
|
||||
for _, section := range f.Sections {
|
||||
if section.Type != elf.SHT_PROGBITS || section.Flags&elf.SHF_ALLOC == 0 {
|
||||
continue
|
||||
}
|
||||
if section.Addr < startAddr {
|
||||
startAddr = section.Addr
|
||||
}
|
||||
}
|
||||
|
||||
progs := make(progSlice, 0, 2)
|
||||
for _, prog := range f.Progs {
|
||||
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 || prog.Off == 0 {
|
||||
continue
|
||||
}
|
||||
progs = append(progs, prog)
|
||||
}
|
||||
if len(progs) == 0 {
|
||||
return 0, nil, objcopyError{"file does not contain ROM segments: " + path, nil}
|
||||
}
|
||||
sort.Sort(progs)
|
||||
|
||||
var rom []byte
|
||||
for _, prog := range progs {
|
||||
romEnd := progs[0].Paddr + uint64(len(rom))
|
||||
if prog.Paddr > romEnd && prog.Paddr < romEnd+16 {
|
||||
// Sometimes, the linker seems to insert a bit of padding between
|
||||
// segments. Simply zero-fill these parts.
|
||||
rom = append(rom, make([]byte, prog.Paddr-romEnd)...)
|
||||
}
|
||||
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
|
||||
diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom)))
|
||||
if diff > maxPadBytes {
|
||||
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
|
||||
}
|
||||
// Pad the difference
|
||||
rom = append(rom, make([]byte, diff)...)
|
||||
}
|
||||
data, err := io.ReadAll(prog.Open())
|
||||
if err != nil {
|
||||
return 0, nil, objcopyError{"failed to extract segment from ELF file: " + path, err}
|
||||
}
|
||||
rom = append(rom, data...)
|
||||
}
|
||||
if progs[0].Paddr < startAddr {
|
||||
// The lowest memory address is before the first section. This means
|
||||
// that there is some extra data loaded at the start of the image that
|
||||
// should be discarded.
|
||||
// Example: ELF files where .text doesn't start at address 0 because
|
||||
// there is a bootloader at the start.
|
||||
return startAddr, rom[startAddr-progs[0].Paddr:], nil
|
||||
} else {
|
||||
return progs[0].Paddr, rom, nil
|
||||
}
|
||||
}
|
||||
|
||||
// objcopy converts an ELF file to a different (simpler) output file format:
|
||||
// .bin or .hex. It extracts only the .text section.
|
||||
func objcopy(infile, outfile, binaryFormat string) error {
|
||||
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Read the .text segment.
|
||||
_, data, err := extractROM(infile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write to the file, in the correct format.
|
||||
switch binaryFormat {
|
||||
case "bin":
|
||||
// The start address is not stored in raw firmware files (therefore you
|
||||
// should use .hex files in most cases).
|
||||
_, err := f.Write(data)
|
||||
return err
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
155
internal/firmware/uf2.go
Normal file
155
internal/firmware/uf2.go
Normal file
@@ -0,0 +1,155 @@
|
||||
// From tinygo/builder/uf2.go
|
||||
|
||||
package firmware
|
||||
|
||||
// This file converts firmware files from BIN to UF2 format before flashing.
|
||||
//
|
||||
// For more information about the UF2 firmware file format, please see:
|
||||
// https://github.com/Microsoft/uf2
|
||||
//
|
||||
//
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// convertELFFileToUF2File converts an ELF file to a UF2 file.
|
||||
func convertELFFileToUF2File(infile, outfile string, uf2FamilyID string) error {
|
||||
// Read the .text segment.
|
||||
targetAddress, data, err := extractROM(infile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output, _, err := convertBinToUF2(data, uint32(targetAddress), uf2FamilyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(outfile, output, 0644)
|
||||
}
|
||||
|
||||
// convertBinToUF2 converts the binary bytes in input to UF2 formatted data.
|
||||
func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byte, int, error) {
|
||||
blocks := split(input, 256)
|
||||
output := make([]byte, 0)
|
||||
|
||||
bl, err := newUF2Block(targetAddr, uf2FamilyID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
bl.SetNumBlocks(len(blocks))
|
||||
|
||||
for i := 0; i < len(blocks); i++ {
|
||||
bl.SetBlockNo(i)
|
||||
bl.SetData(blocks[i])
|
||||
|
||||
output = append(output, bl.Bytes()...)
|
||||
bl.IncrementAddress(bl.payloadSize)
|
||||
}
|
||||
|
||||
return output, len(blocks), nil
|
||||
}
|
||||
|
||||
const (
|
||||
uf2MagicStart0 = 0x0A324655 // "UF2\n"
|
||||
uf2MagicStart1 = 0x9E5D5157 // Randomly selected
|
||||
uf2MagicEnd = 0x0AB16F30 // Ditto
|
||||
)
|
||||
|
||||
// uf2Block is the structure used for each UF2 code block sent to device.
|
||||
type uf2Block struct {
|
||||
magicStart0 uint32
|
||||
magicStart1 uint32
|
||||
flags uint32
|
||||
targetAddr uint32
|
||||
payloadSize uint32
|
||||
blockNo uint32
|
||||
numBlocks uint32
|
||||
familyID uint32
|
||||
data []uint8
|
||||
magicEnd uint32
|
||||
}
|
||||
|
||||
// newUF2Block returns a new uf2Block struct that has been correctly populated
|
||||
func newUF2Block(targetAddr uint32, uf2FamilyID string) (*uf2Block, error) {
|
||||
var flags uint32
|
||||
var familyID uint32
|
||||
if uf2FamilyID != "" {
|
||||
flags |= flagFamilyIDPresent
|
||||
v, err := strconv.ParseUint(uf2FamilyID, 0, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
familyID = uint32(v)
|
||||
}
|
||||
return &uf2Block{magicStart0: uf2MagicStart0,
|
||||
magicStart1: uf2MagicStart1,
|
||||
magicEnd: uf2MagicEnd,
|
||||
targetAddr: targetAddr,
|
||||
flags: flags,
|
||||
familyID: familyID,
|
||||
payloadSize: 256,
|
||||
data: make([]byte, 476),
|
||||
}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
flagFamilyIDPresent = 0x00002000
|
||||
)
|
||||
|
||||
// Bytes converts the uf2Block to a slice of bytes that can be written to file.
|
||||
func (b *uf2Block) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 512))
|
||||
binary.Write(buf, binary.LittleEndian, b.magicStart0)
|
||||
binary.Write(buf, binary.LittleEndian, b.magicStart1)
|
||||
binary.Write(buf, binary.LittleEndian, b.flags)
|
||||
binary.Write(buf, binary.LittleEndian, b.targetAddr)
|
||||
binary.Write(buf, binary.LittleEndian, b.payloadSize)
|
||||
binary.Write(buf, binary.LittleEndian, b.blockNo)
|
||||
binary.Write(buf, binary.LittleEndian, b.numBlocks)
|
||||
binary.Write(buf, binary.LittleEndian, b.familyID)
|
||||
binary.Write(buf, binary.LittleEndian, b.data)
|
||||
binary.Write(buf, binary.LittleEndian, b.magicEnd)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// IncrementAddress moves the target address pointer forward by count bytes.
|
||||
func (b *uf2Block) IncrementAddress(count uint32) {
|
||||
b.targetAddr += b.payloadSize
|
||||
}
|
||||
|
||||
// SetData sets the data to be used for the current block.
|
||||
func (b *uf2Block) SetData(d []byte) {
|
||||
b.data = make([]byte, 476)
|
||||
copy(b.data[:], d)
|
||||
}
|
||||
|
||||
// SetBlockNo sets the current block number to be used.
|
||||
func (b *uf2Block) SetBlockNo(bn int) {
|
||||
b.blockNo = uint32(bn)
|
||||
}
|
||||
|
||||
// SetNumBlocks sets the total number of blocks for this UF2 file.
|
||||
func (b *uf2Block) SetNumBlocks(total int) {
|
||||
b.numBlocks = uint32(total)
|
||||
}
|
||||
|
||||
// split splits a slice of bytes into a slice of byte slices of a specific size limit.
|
||||
func split(input []byte, limit int) [][]byte {
|
||||
var block []byte
|
||||
output := make([][]byte, 0, len(input)/limit+1)
|
||||
for len(input) >= limit {
|
||||
// add all blocks
|
||||
block, input = input[:limit], input[limit:]
|
||||
output = append(output, block)
|
||||
}
|
||||
if len(input) > 0 {
|
||||
// add remaining block (that isn't full sized)
|
||||
output = append(output, input)
|
||||
}
|
||||
return output
|
||||
}
|
||||
@@ -29,6 +29,8 @@ type Config struct {
|
||||
|
||||
// Binary and firmware configuration
|
||||
BinaryFormat string `json:"binary-format"`
|
||||
// UF2 configuration
|
||||
UF2FamilyID string `json:"uf2-family-id"`
|
||||
|
||||
// Flash and deployment configuration
|
||||
FlashCommand string `json:"flash-command"`
|
||||
@@ -39,9 +41,6 @@ type Config struct {
|
||||
MSDVolumeName []string `json:"msd-volume-name"`
|
||||
MSDFirmwareName string `json:"msd-firmware-name"`
|
||||
|
||||
// UF2 configuration
|
||||
UF2FamilyID string `json:"uf2-family-id"`
|
||||
|
||||
// Device-specific configuration
|
||||
RP2040BootPatch bool `json:"rp2040-boot-patch"`
|
||||
|
||||
@@ -66,6 +65,13 @@ func (c *Config) IsEmpty() bool {
|
||||
return c.Name == "" && c.LLVMTarget == "" && c.GOOS == "" && c.GOARCH == ""
|
||||
}
|
||||
|
||||
func (c *Config) FormatDetail() string {
|
||||
if c.BinaryFormat == "uf2" {
|
||||
return c.UF2FamilyID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// HasInheritance returns true if this config inherits from other configs
|
||||
func (rc *RawConfig) HasInheritance() bool {
|
||||
return len(rc.Inherits) > 0
|
||||
|
||||
Reference in New Issue
Block a user