///|
fn serial_child_flags(value : String) -> String raise MakeError {
let words = makeflag_words(value)
let arguments = inherited_make_arguments(words, include_assignments=false)
let flags : Array[String] = []
let assignments : Array[String] = []
for argument in arguments {
if !argument.has_prefix("-j") && !argument.has_prefix("--jobs") {
flags.push(argument)
}
}
for (name, operator, value) in inherited_make_assignments(words) {
assignments.push(name + operator + value)
}
flags.push("-j1")
if !assignments.is_empty() {
flags.push("--")
flags.append(assignments)
}
flags.map(escape_makeflag).join(" ")
}
///|
fn apply_makefile_flags(
settings : Settings,
arguments : Array[String],
) -> Settings raise MakeError {
let mut jobs = settings.jobs
let mut silent = settings.silent
let mut dry_run = settings.dry_run
let mut always_make = settings.always_make
let mut keep_going = settings.keep_going
for argument in arguments {
match argument {
"-s" | "--silent" => silent = true
"-n" | "--just-print" => dry_run = true
"-B" | "--always-make" => always_make = true
"-k" | "--keep-going" => keep_going = true
"-j" | "--jobs" => jobs = 0
value if value.has_prefix("-j") || value.has_prefix("--jobs=") => {
let count = value[if value.has_prefix("--") { 7 } else { 2 }:].to_owned()
if !positive_job_count(count) {
raise MakeError("MAKEFLAGS jobs requires a positive integer")
}
jobs = @string.parse_int(count, base=10) catch {
_ => raise MakeError("MAKEFLAGS jobs requires a positive integer")
}
}
value if parse_assignment(value) is Some(_) => ()
_ => raise MakeError("unsupported MAKEFLAGS option '\{argument}'")
}
}
{ ..settings, jobs, silent, dry_run, always_make, keep_going, }
}
///|
priv struct Jobserver {
reader : &@io.Reader
writer : &@io.Writer
close_handles : () -> Unit
implicit : @semaphore.Semaphore
acquiring : @async.Mutex
}
///|
async fn Jobserver::open(authentication : String) -> Jobserver {
if !authentication.has_prefix("fifo:") {
raise MakeError(
"jobserver descriptor pipes and named semaphores require public descriptor-import or semaphore APIs; use FIFO authentication",
)
}
let path = authentication[5:].to_owned()
if path == "" {
raise MakeError("jobserver FIFO path is empty")
}
if @async.platform is Windows {
raise MakeError("FIFO jobserver inheritance is unavailable on Windows")
}
if @fs.kind(path) != Pipe {
raise MakeError("jobserver authentication does not name a FIFO")
}
let reader = @fs.open(path, mode=ReadOnly, create_mode=OpenExisting)
errdefer reader.close()
if reader.kind() != Pipe {
raise MakeError("jobserver FIFO changed while opening")
}
let writer = @fs.open(path, mode=WriteOnly, create_mode=OpenExisting)
errdefer writer.close()
if writer.kind() != Pipe {
raise MakeError("jobserver FIFO changed while opening")
}
{
reader,
writer,
close_handles: () => {
reader.close()
writer.close()
},
implicit: @semaphore.Semaphore(1),
acquiring: @async.Mutex(),
}
}
///|
fn Jobserver::close(self : Jobserver) -> Unit {
(self.close_handles)()
}
///|
async fn[T] Jobserver::with_slot(self : Jobserver, action : async () -> T) -> T {
// Only one read can wait on this handle. The implicit slot is raced against
// the FIFO so a child finishing can unblock work even when no token arrives.
self.acquiring.acquire()
let mut locked = true
defer (if locked { self.acquiring.release() })
let mut implicit_owned = false
let mut token : Bytes? = None
defer @async.protect_from_cancel(() => {
if implicit_owned {
self.implicit.release()
}
if token is Some(value) {
token = None
self.writer.write(value)
}
})
@async.any([
() => {
self.implicit.acquire()
implicit_owned = true
},
() => {
let byte = FixedArray::make(1, b'\x00')
if self.reader.read(byte, max_len=1) != 1 {
raise MakeError("jobserver FIFO ended while waiting for a token")
}
token = Some(byte.unsafe_reinterpret_as_bytes())
},
])
// Both waiters may acquire before cancellation is observed. Keep the free
// implicit slot and return the exact extra byte before starting the recipe.
if implicit_owned && token is Some(value) {
token = None
@async.protect_from_cancel(() => self.writer.write(value))
}
locked = false
self.acquiring.release()
action()
}
///|
fn makeflag_words(value : String) -> Array[String] raise MakeError {
let words : Array[String] = []
let word = StringBuilder()
let mut escaped = false
for char in value {
if escaped {
word.write_char(char)
escaped = false
} else if char == '\\' {
escaped = true
} else if char is (' ' | '\t' | '\n') {
if !word.is_empty() {
words.push(word.to_string())
word.reset()
}
} else {
word.write_char(char)
}
}
if escaped {
raise MakeError("MAKEFLAGS ends with an incomplete escape")
}
if !word.is_empty() {
words.push(word.to_string())
}
words
}
///|
fn inherited_jobserver(words : Array[String]) -> String? raise MakeError {
let mut authentication = None
for token in inherited_make_tokens(words) {
if token is Authentication(value) {
authentication = Some(value)
}
}
authentication
}
///|
fn escape_makeflag(value : String) -> String {
let result = StringBuilder()
for char in value {
if char is ('\\' | ' ' | '\t' | '\n') {
result.write_char('\\')
}
result.write_char(char)
}
result.to_string()
}
///|
priv enum MakeFlagToken {
Option(String)
Assignment(String, (String, String, String))
Authentication(String)
}
///|
fn inherited_make_tokens(
words : Array[String],
) -> Array[MakeFlagToken] raise MakeError {
let result : Array[MakeFlagToken] = []
let mut assignments = false
let mut index = 0
while index < words.length() {
let word = words[index]
if word == "--" {
assignments = true
} else if assignments || (word.contains("=") && !word.has_prefix("--")) {
guard parse_assignment(word) is Some(assignment) else {
raise MakeError("invalid inherited variable assignment")
}
result.push(Assignment(word, assignment))
} else if word.has_prefix("--jobserver-auth=") {
result.push(Authentication(word[17:].to_owned()))
} else if word == "--jobserver-auth" {
index += 1
guard words.get(index) is Some(value) else {
raise MakeError("--jobserver-auth requires a value")
}
result.push(Authentication(value))
} else if word == "--no-print-directory" ||
word == "--print-directory" ||
word == "--no-builtin-rules" ||
word == "--no-builtin-variables" {
()
} else if word == "--jobs" &&
words.get(index + 1) is Some(value) &&
positive_job_count(value) {
result.push(Option("-j" + value))
index += 1
} else if word.has_prefix("--") {
result.push(Option(word))
} else if word != "" {
let flags = if word.has_prefix("-") { word[1:].to_owned() } else { word }
let chars = flags.iter().collect()
let mut cursor = 0
while cursor < chars.length() {
let char = chars[cursor]
if char == 'j' {
let number = String::from_array(chars[cursor + 1:])
if number != "" {
result.push(Option("-j" + number))
} else if words.get(index + 1) is Some(value) &&
positive_job_count(value) {
result.push(Option("-j" + value))
index += 1
} else {
result.push(Option("-j"))
}
break
} else if char is ('k' | 's' | 'n' | 'B') {
result.push(Option("-" + char.to_string()))
} else if char is ('w' | 'r' | 'R') {
()
} else {
raise MakeError("unsupported inherited make flag '\{char}'")
}
cursor += 1
}
}
index += 1
}
result
}
///|
fn inherited_make_arguments(
words : Array[String],
include_assignments~ : Bool = true,
) -> Array[String] raise MakeError {
let result : Array[String] = []
for token in inherited_make_tokens(words) {
match token {
Option(value) => result.push(value)
Assignment(value, _) => if include_assignments { result.push(value) }
Authentication(_) => ()
}
}
result
}
///|
fn inherited_make_assignments(
words : Array[String],
) -> Array[(String, String, String)] raise MakeError {
let result : Array[(String, String, String)] = []
for token in inherited_make_tokens(words) {
if token is Assignment(_, assignment) {
result.push(assignment)
}
}
result
}