///|
enum QueryMode {
Cflags(@pc.FlagSelection?)
Libs(@pc.FlagSelection?)
Variable(String)
ModVersion
Exists
AtLeastVersion(String)
ExactVersion(String)
MaxVersion(String)
} derive(Eq)
///|
fn print_usage() -> Unit {
println(
"Usage: moon run cmd/query [--path ]... [options]",
)
println(
"Modes: --cflags, --libs, --variable=, --modversion, --exists, --atleast-version=, --exact-version=, --max-version=",
)
println(
" --cflags-only-I, --cflags-only-other, --libs-only-L, --libs-only-l, --libs-only-other",
)
println(
"Options: --define-variable==, --path , --sysroot=",
)
println(" --system-include-path=, --system-library-path=")
println(" --static, --dedupe-paths, --json, --explain")
}
///|
fn valid_variable_name(name : String) -> Bool {
!name.is_empty() &&
name
.iter()
.all(c => {
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '_' ||
c == '.' ||
c == '-'
})
}
///|
fn print_diagnostics(diagnostics : Array[@pc.Diagnostic]) -> Unit {
for problem in diagnostics {
println(
"\{problem.code} \{problem.location.source}:\{problem.location.line}:\{problem.location.column} \{problem.message}",
)
}
}
///|
fn print_flags(
result : @pc.FlagResult,
explain : Bool,
packages : @pc.PackageSet,
root : String,
include_private : Bool,
unavailable : Map[String, Array[@pc.Diagnostic]],
) -> Unit {
println(result.shell_text())
if explain {
let paths = packages.dependency_paths(root, include_private~, unavailable~)
for flag in result.flags {
let path = paths.path_to(flag.package_id)
let path_text = if path.is_empty() {
flag.package_id
} else {
path.join(" -> ")
}
println(
" \{flag.value} <- \{flag.package_id}:\{flag.field} [\{flag.location.source}:\{flag.location.line}; via \{path_text}]",
)
}
}
print_diagnostics(result.diagnostics)
}
///|
fn main {
let args = @env.args()
let positionals : Array[String] = []
let extra_paths : Array[String] = []
let overrides : Map[String, String] = Map([])
let mut mode : QueryMode? = None
let mut static_linking = false
let mut json = false
let mut explain = false
let mut dedupe_paths = false
let mut sysroot = ""
let system_include_paths : Array[String] = []
let system_library_paths : Array[String] = []
let mut invalid = false
let mut help = false
let mut i = 1
while i < args.length() {
match args[i] {
"--cflags" =>
if mode is Some(_) {
invalid = true
} else {
mode = Some(Cflags(None))
}
"--libs" =>
if mode is Some(_) {
invalid = true
} else {
mode = Some(Libs(None))
}
"--cflags-only-I" | "--cflags-only-other" as option =>
if mode is Some(_) {
invalid = true
} else {
let selection = if option == "--cflags-only-I" {
@pc.IncludePaths
} else {
@pc.OtherCflags
}
mode = Some(Cflags(Some(selection)))
}
"--libs-only-L" | "--libs-only-l" | "--libs-only-other" as option =>
if mode is Some(_) {
invalid = true
} else {
let selection = match option {
"--libs-only-L" => @pc.LibraryPaths
"--libs-only-l" => @pc.Libraries
_ => @pc.OtherLibs
}
mode = Some(Libs(Some(selection)))
}
"--modversion" =>
if mode is Some(_) {
invalid = true
} else {
mode = Some(ModVersion)
}
"--exists" =>
if mode is Some(_) {
invalid = true
} else {
mode = Some(Exists)
}
"--static" => static_linking = true
"--json" => json = true
"--explain" => explain = true
"--dedupe-paths" => dedupe_paths = true
"--path" =>
if i + 1 >= args.length() || args[i + 1].has_prefix("-") {
invalid = true
} else {
extra_paths.push(args[i + 1])
i = i + 1
}
"--help" | "-h" => help = true
option =>
if option.has_prefix("--atleast-version=") {
let version = option[18:].to_owned()
if mode is Some(_) || version.is_empty() {
invalid = true
} else {
mode = Some(AtLeastVersion(version))
}
} else if option.has_prefix("--exact-version=") {
let version = option[16:].to_owned()
if mode is Some(_) || version.is_empty() {
invalid = true
} else {
mode = Some(ExactVersion(version))
}
} else if option.has_prefix("--max-version=") {
let version = option[14:].to_owned()
if mode is Some(_) || version.is_empty() {
invalid = true
} else {
mode = Some(MaxVersion(version))
}
} else if option.has_prefix("--variable=") {
let name = option[11:].to_owned()
if mode is Some(_) || !valid_variable_name(name) {
invalid = true
} else {
mode = Some(Variable(name))
}
} else if option.has_prefix("--define-variable=") {
let definition = option[18:].to_owned()
match definition.find("=") {
Some(split) => {
let name = definition[:split].to_owned()
let value = definition[split + 1:].to_owned()
if !valid_variable_name(name) || name == "pcfiledir" {
invalid = true
} else {
overrides[name] = value
}
}
None => invalid = true
}
} else if option.has_prefix("--sysroot=") {
let path = option[10:].to_owned()
if path.is_empty() || !sysroot.is_empty() {
invalid = true
} else {
sysroot = path
}
} else if option.has_prefix("--system-include-path=") {
let path = option[22:].to_owned()
if path.is_empty() {
invalid = true
} else {
system_include_paths.push(path)
}
} else if option.has_prefix("--system-library-path=") {
let path = option[22:].to_owned()
if path.is_empty() {
invalid = true
} else {
system_library_paths.push(path)
}
} else if option.has_prefix("-") {
invalid = true
} else {
positionals.push(option)
}
}
i = i + 1
}
if help {
print_usage()
return
}
if invalid || positionals.length() != 2 || mode is None {
print_usage()
@sys.exit(2)
}
let cflags_mode = mode is Some(Cflags(_))
let libs_mode = mode is Some(Libs(_))
let flag_mode = cflags_mode || libs_mode
if static_linking && !flag_mode {
println("--static requires a compiler or linker flag mode")
@sys.exit(2)
}
if !flag_mode &&
(
dedupe_paths ||
json ||
explain ||
!sysroot.is_empty() ||
!system_include_paths.is_empty() ||
!system_library_paths.is_empty()
) {
println(
"flag path options, --json and --explain require a compiler or linker flag mode",
)
@sys.exit(2)
}
let directory = positionals[0]
let root = positionals[1]
let directories = [directory, ..extra_paths]
try {
let loaded = @loader.load_directories(directories, overrides~)
let root_diagnostics = loaded.diagnostics_for(root)
if !root_diagnostics.is_empty() {
if json {
let failed : @pc.FlagResult = {
flags: [],
diagnostics: root_diagnostics,
}
println(failed.json_text(pretty=true))
} else {
print_diagnostics(root_diagnostics)
}
@sys.exit(1)
}
match mode.unwrap() {
Cflags(selection) | Libs(selection) => {
let collected = if cflags_mode {
loaded.packages.collect_cflags(
root,
static_linking~,
unavailable=loaded.diagnostics_by_package,
)
} else {
loaded.packages.collect_libs(
root,
static_linking~,
unavailable=loaded.diagnostics_by_package,
)
}
let rooted = collected.with_sysroot(sysroot)
let filtered = rooted.filter_system_search_paths(
system_include_paths, system_library_paths,
)
let deduplicated = if dedupe_paths {
filtered.deduplicate_search_paths()
} else {
filtered
}
let result = match selection {
Some(group) => deduplicated.select(group)
None => deduplicated
}
if json {
println(result.json_text(pretty=true))
} else {
print_flags(
result,
explain,
loaded.packages,
root,
cflags_mode || static_linking,
loaded.diagnostics_by_package,
)
}
if !result.diagnostics.is_empty() {
@sys.exit(1)
}
}
ModVersion
| Variable(_)
| Exists
| AtLeastVersion(_)
| ExactVersion(_)
| MaxVersion(_) as metadata_query => {
let resolution = loaded.packages.resolve(
root,
unavailable=loaded.diagnostics_by_package,
)
if !resolution.diagnostics.is_empty() {
print_diagnostics(resolution.diagnostics)
@sys.exit(1)
}
let selected_id = resolution.order[resolution.order.length() - 1]
let selected = loaded.packages.get(selected_id).unwrap()
let version = selected.document.field("Version").unwrap().value
match metadata_query {
ModVersion => println(version)
Variable(name) =>
match selected.document.variable(name) {
Some(entry) => println(entry.value)
None => {
println("Variable not found: {name}")
@sys.exit(1)
}
}
Exists => ()
AtLeastVersion(required) =>
if @pc.compare_versions(version, required) < 0 {
@sys.exit(1)
}
ExactVersion(required) =>
if @pc.compare_versions(version, required) != 0 {
@sys.exit(1)
}
MaxVersion(required) =>
if @pc.compare_versions(version, required) > 0 {
@sys.exit(1)
}
_ => abort("unreachable metadata query mode")
}
}
}
} catch {
@fs.IOError(message) => {
println("Could not load \{directory}: \{message}")
@sys.exit(2)
}
}
}